source: src/mlx/web.py@ 749:9a902bc9af01

Last change on this file since 749:9a902bc9af01 was 746:277ecb58fe9b, checked in by István Váradi <ivaradi@…>, 9 years ago

When so configured, RPC calls are used to communicate with the MAVA website (re #283)

File size: 43.2 KB
Line 
1
2import const
3import util
4import rpc
5import rpccommon
6
7from common import MAVA_BASE_URL
8
9import threading
10import sys
11import urllib
12import urllib2
13import hashlib
14import time
15import datetime
16import codecs
17import traceback
18import xml.sax
19import xmlrpclib
20import HTMLParser
21
22#---------------------------------------------------------------------------------------
23
24## @package mlx.web
25#
26# Web interface.
27#
28# This module implements a thread that can perform (HTTP) requests
29# asynchronously. When the request is performed, a callback is called. The main
30# interface is the \ref Handler class. Each of its functions creates a \ref
31# Request subclass instance and puts it to the request queue. The handler
32# thread then takes the requests one by one, and executes them.
33#
34# This module also defines some data classes the contents of which are
35# retrieved or sent via HTTP. \ref BookedFlight contains data of a flight
36# booked on the MAVA website, \ref Fleet and \ref Plane represents the MAVA
37# fleet and the gates at Ferihegy and \ref NOTAM is a NOTAM.
38
39#---------------------------------------------------------------------------------------
40
41def readline(f):
42 """Read a line from the given file.
43
44 The line is stripped and empty lines are discarded."""
45 while True:
46 line = f.readline()
47 if not line: return ""
48 line = line.strip()
49 if line:
50 return line
51
52#---------------------------------------------------------------------------------------
53
54class BookedFlight(object):
55 """A flight that was booked."""
56 TYPECODE2TYPE = { "736" : const.AIRCRAFT_B736,
57 "73G" : const.AIRCRAFT_B737,
58 "738" : const.AIRCRAFT_B738,
59 "73H" : const.AIRCRAFT_B738C,
60 "733" : const.AIRCRAFT_B733,
61 "734" : const.AIRCRAFT_B734,
62 "735" : const.AIRCRAFT_B735,
63 "DH4" : const.AIRCRAFT_DH8D,
64 "762" : const.AIRCRAFT_B762,
65 "763" : const.AIRCRAFT_B763,
66 "CR2" : const.AIRCRAFT_CRJ2,
67 "F70" : const.AIRCRAFT_F70,
68 "LI2" : const.AIRCRAFT_DC3,
69 "TU3" : const.AIRCRAFT_T134,
70 "TU5" : const.AIRCRAFT_T154,
71 "YK4" : const.AIRCRAFT_YK40,
72 "146" : const.AIRCRAFT_B462 }
73
74 TYPE2TYPECODE = { const.AIRCRAFT_B736 : "736",
75 const.AIRCRAFT_B737 : "73G",
76 const.AIRCRAFT_B738 : "738",
77 const.AIRCRAFT_B738C : "73H",
78 const.AIRCRAFT_B733 : "733",
79 const.AIRCRAFT_B734 : "734",
80 const.AIRCRAFT_B735 : "735",
81 const.AIRCRAFT_DH8D : "DH4",
82 const.AIRCRAFT_B762 : "762",
83 const.AIRCRAFT_B763 : "763",
84 const.AIRCRAFT_CRJ2 : "CR2",
85 const.AIRCRAFT_F70 : "F70",
86 const.AIRCRAFT_DC3 : "LI2",
87 const.AIRCRAFT_T134 : "TU3",
88 const.AIRCRAFT_T154 : "TU5",
89 const.AIRCRAFT_YK40 : "YK4",
90 const.AIRCRAFT_B462 : "146" }
91
92 @staticmethod
93 def getDateTime(date, time):
94 """Get a datetime object from the given textual date and time."""
95 return datetime.datetime.strptime(date + " " + time,
96 "%Y-%m-%d %H:%M:%S")
97
98 def __init__(self, id = None):
99 """Construct a booked flight with the given ID."""
100 self.id = id
101
102 def readFromWeb(self, f):
103 """Read the data of the flight from the web via the given file
104 object."""
105 self.callsign = readline(f)
106
107 date = readline(f)
108 print "web.BookedFlight.readFromWeb: date:", date
109 if date=="0000-00-00": date = "0001-01-01"
110
111 self.departureICAO = readline(f)
112 self.arrivalICAO = readline(f)
113
114 self._readAircraftType(f)
115 self.tailNumber = readline(f)
116 self.numPassengers = int(readline(f))
117 self.numCrew = int(readline(f))
118 self.bagWeight = int(readline(f))
119 self.cargoWeight = int(readline(f))
120 self.mailWeight = int(readline(f))
121 self.route = readline(f)
122
123 departureTime = readline(f)
124 self.departureTime = BookedFlight.getDateTime(date, departureTime)
125
126 arrivalTime = readline(f)
127 self.arrivalTime = BookedFlight.getDateTime(date, arrivalTime)
128 if self.arrivalTime<self.departureTime:
129 self.arrivalTime += datetime.timedelta(days = 1)
130
131 if not readline(f)==".NEXT.":
132 raise Exception("Invalid line in flight data")
133
134 def readFromFile(self, f):
135 """Read the data of the flight from a file via the given file
136 object."""
137 date = None
138 departureTime = None
139 arrivalTime = None
140
141 line = f.readline()
142 lineNumber = 0
143 while line:
144 lineNumber += 1
145 line = line.strip()
146
147 hashIndex = line.find("#")
148 if hashIndex>=0: line = line[:hashIndex]
149 if line:
150 equalIndex = line.find("=")
151 lineOK = equalIndex>0
152
153 if lineOK:
154 key = line[:equalIndex].strip()
155 value = line[equalIndex+1:].strip().replace("\:", ":")
156
157 lineOK = key and value
158
159 if lineOK:
160 if key=="callsign": self.callsign = value
161 elif key=="date": date = value
162 elif key=="dep_airport": self.departureICAO = value
163 elif key=="dest_airport": self.arrivalICAO = value
164 elif key=="planecode": self.aircraftType = \
165 self._decodeAircraftType(value)
166 elif key=="planetype": self.aircraftTypeName = value
167 elif key=="tail_nr": self.tailNumber = value
168 elif key=="passenger": self.numPassengers = int(value)
169 elif key=="crew": self.numCrew = int(value)
170 elif key=="bag": self.bagWeight = int(value)
171 elif key=="cargo": self.cargoWeight = int(value)
172 elif key=="mail": self.mailWeight = int(value)
173 elif key=="flight_route": self.route = value
174 elif key=="departure_time": departureTime = value
175 elif key=="arrival_time": arrivalTime = value
176 elif key=="foglalas_id":
177 self.id = None if value=="0" else value
178 else: lineOK = False
179
180 if not lineOK:
181 print "web.BookedFlight.readFromFile: line %d is invalid" % \
182 (lineNumber,)
183
184 line = f.readline()
185
186 if date is not None:
187 if departureTime is not None:
188 self.departureTime = BookedFlight.getDateTime(date,
189 departureTime)
190 if arrivalTime is not None:
191 self.arrivalTime = BookedFlight.getDateTime(date,
192 arrivalTime)
193
194 d = dir(self)
195 for attribute in ["callsign", "departureICAO", "arrivalICAO",
196 "aircraftType", "tailNumber",
197 "numPassengers", "numCrew",
198 "bagWeight", "cargoWeight", "mailWeight",
199 "route", "departureTime", "arrivalTime"]:
200 if attribute not in d:
201 raise Exception("Attribute %s could not be read" % (attribute,))
202
203 if "aircraftTypeName" not in d:
204 self.aircraftTypeName = \
205 BookedFlight.TYPE2TYPECODE[self.aircraftType]
206
207 def writeIntoFile(self, f):
208 """Write the flight into a file."""
209 print >> f, "callsign=%s" % (self.callsign,)
210 date = self.departureTime.date()
211 print >> f, "date=%04d-%02d-%0d" % (date.year, date.month, date.day)
212 print >> f, "dep_airport=%s" % (self.departureICAO,)
213 print >> f, "dest_airport=%s" % (self.arrivalICAO,)
214 print >> f, "planecode=%s" % \
215 (BookedFlight.TYPE2TYPECODE[self.aircraftType],)
216 print >> f, "planetype=%s" % (self.aircraftTypeName,)
217 print >> f, "tail_nr=%s" % (self.tailNumber,)
218 print >> f, "passenger=%d" % (self.numPassengers,)
219 print >> f, "crew=%d" % (self.numCrew,)
220 print >> f, "bag=%d" % (self.bagWeight,)
221 print >> f, "cargo=%d" % (self.cargoWeight,)
222 print >> f, "mail=%d" % (self.mailWeight,)
223 print >> f, "flight_route=%s" % (self.route,)
224 departureTime = self.departureTime
225 print >> f, "departure_time=%02d\\:%02d\\:%02d" % \
226 (departureTime.hour, departureTime.minute, departureTime.second)
227 arrivalTime = self.arrivalTime
228 print >> f, "arrival_time=%02d\\:%02d\\:%02d" % \
229 (arrivalTime.hour, arrivalTime.minute, arrivalTime.second)
230 print >> f, "foglalas_id=%s" % ("0" if self.id is None else self.id,)
231
232 def _readAircraftType(self, f):
233 """Read the aircraft type from the given file."""
234 line = readline(f)
235 typeCode = line[:3]
236 self.aircraftType = self._decodeAircraftType(typeCode)
237 self.aircraftTypeName = line[3:]
238
239 def _decodeAircraftType(self, typeCode):
240 """Decode the aircraft type from the given typeCode."""
241 if typeCode in self.TYPECODE2TYPE:
242 return self.TYPECODE2TYPE[typeCode]
243 else:
244 raise Exception("Invalid aircraft type code: '" + typeCode + "'")
245
246 def __repr__(self):
247 """Get a representation of the flight."""
248 s = "<Flight: %s-%s, %s, %s-%s," % (self.departureICAO,
249 self.arrivalICAO,
250 self.route,
251 self.departureTime, self.arrivalTime)
252 s += " %d %s," % (self.aircraftType, self.tailNumber)
253 s += " pax=%d, crew=%d, bag=%d, cargo=%d, mail=%d" % \
254 (self.numPassengers, self.numCrew,
255 self.bagWeight, self.cargoWeight, self.mailWeight)
256 s += ">"
257 return s
258
259#------------------------------------------------------------------------------
260
261class Plane(rpccommon.Plane):
262 """Information about an airplane in the fleet."""
263 def __init__(self, s):
264 """Build a plane info based on the given string.
265
266 The string consists of three, space-separated fields.
267 The first field is the tail number, the second field is the gate
268 number, the third field is the plane's status as a character."""
269 super(Plane, self).__init__()
270
271 try:
272 words = s.split(" ")
273 tailNumber = words[0]
274 self.tailNumber = tailNumber
275
276 status = words[2] if len(words)>2 else None
277 self._setStatus(status)
278
279 gateNumber = words[1] if len(words)>1 else ""
280 self.gateNumber = gateNumber if gateNumber else None
281
282 except:
283 print >> sys.stderr, "Plane string is invalid: '" + s + "'"
284 self.tailNumber = None
285
286#------------------------------------------------------------------------------
287
288class Fleet(rpccommon.Fleet):
289 """Information about the whole fleet."""
290 def __init__(self, f):
291 """Construct the fleet information by reading the given file object."""
292 super(Fleet, self).__init__()
293
294 while True:
295 line = readline(f)
296 if not line or line == "#END": break
297
298 plane = Plane(line)
299 self._addPlane(plane)
300
301#------------------------------------------------------------------------------
302
303class NOTAM(object):
304 """A NOTAM for an airport."""
305 def __init__(self, ident, basic,
306 begin, notice, end = None, permanent = False,
307 repeatCycle = None):
308 """Construct the NOTAM."""
309 self.ident = ident
310 self.basic = basic
311 self.begin = begin
312 self.notice = notice
313 self.end = end
314 self.permanent = permanent
315 self.repeatCycle = repeatCycle
316
317 def __repr__(self):
318 """Get the representation of the NOTAM."""
319 s = "<NOTAM " + str(self.begin)
320 if self.end:
321 s += " - " + str(self.end)
322 elif self.permanent:
323 s += " - PERMANENT"
324 if self.repeatCycle:
325 s += " (" + self.repeatCycle + ")"
326 s += ": " + self.notice
327 s += ">"
328 return s
329
330 def __str__(self):
331 """Get the string representation of the NOTAM."""
332 s = ""
333 s += str(self.ident) + " " + str(self.basic) + "\n"
334 s += str(self.begin)
335 if self.end is not None:
336 s += " - " + str(self.end)
337 elif self.permanent:
338 s += " - PERMANENT"
339 s += "\n"
340 if self.repeatCycle:
341 s += "Repeat cycle: " + self.repeatCycle + "\n"
342 s += self.notice + "\n"
343 return s
344
345#------------------------------------------------------------------------------
346
347class NOTAMHandler(xml.sax.handler.ContentHandler):
348 """A handler for the NOTAM database."""
349 def __init__(self, airportICAOs):
350 """Construct the handler for the airports with the given ICAO code."""
351 self._notams = {}
352 for icao in airportICAOs:
353 self._notams[icao] = []
354
355 def startElement(self, name, attrs):
356 """Start an element."""
357 if name!="notam" or \
358 "ident" not in attrs or not attrs["ident"] or \
359 "Q" not in attrs or not attrs["Q"] or \
360 "A" not in attrs or not attrs["A"] or \
361 "B" not in attrs or not attrs["B"] or \
362 "E" not in attrs or not attrs["E"]:
363 return
364
365 icao = attrs["A"]
366 if icao not in self._notams:
367 return
368
369 begin = datetime.datetime.strptime(attrs["B"], "%Y-%m-%d %H:%M:%S")
370
371 c = attrs["C"] if "C" in attrs else None
372 end = datetime.datetime.strptime(c, "%Y-%m-%d %H:%M:%S") if c else None
373
374 permanent = attrs["C_flag"]=="PERM" if "C_flag" in attrs else False
375
376 repeatCycle = attrs["D"] if "D" in attrs else None
377
378 self._notams[icao].append(NOTAM(attrs["ident"], attrs["Q"],
379 begin, attrs["E"], end = end,
380 permanent = permanent,
381 repeatCycle = repeatCycle))
382
383 def get(self, icao):
384 """Get the NOTAMs for the given ICAO code."""
385 return self._notams[icao] if icao in self._notams else []
386
387#------------------------------------------------------------------------------
388
389class PilotsWebNOTAMsParser(HTMLParser.HTMLParser):
390 """XML handler for the NOTAM query results on the PilotsWeb website."""
391 def __init__(self):
392 """Construct the handler."""
393 HTMLParser.HTMLParser.__init__(self)
394
395 self._notams = []
396 self._currentNOTAM = ""
397 self._stage = 0
398
399 def handle_starttag(self, name, attrs):
400 """Start an element."""
401 if (self._stage==0 and name=="div" and ("id", "notamRight") in attrs) or \
402 (self._stage==1 and name=="span") or \
403 (self._stage==2 and name=="pre"):
404 self._stage += 1
405 if self._stage==1:
406 self._currentNOTAM = ""
407
408 def handle_data(self, content):
409 """Handle characters"""
410 if self._stage==3:
411 self._currentNOTAM += content
412
413 def handle_endtag(self, name):
414 """End an element."""
415 if (self._stage==3 and name=="pre") or \
416 (self._stage==2 and name=="span") or \
417 (self._stage==1 and name=="div"):
418 self._stage -= 1
419 if self._stage==0:
420 self._processCurrentNOTAM()
421
422 def getNOTAMs(self):
423 """Get the NOTAMs collected"""
424 return self._notams
425
426 def _processCurrentNOTAM(self):
427 """Parse the current NOTAM and append its contents to the list of
428 NOTAMS."""
429 notam = None
430 try:
431 notam = self._parseCurrentNOTAM2()
432 except Exception, e:
433 print "Error parsing current NOTAM: " + str(e)
434
435 if notam is None:
436 print "Could not parse NOTAM: " + self._currentNOTAM
437 if self._currentNOTAM:
438 self._notams.append(self._currentNOTAM + "\n")
439 else:
440 self._notams.append(notam)
441
442 def _parseCurrentNOTAM(self):
443 """Parse the current NOTAM, if possible, and return a NOTAM object."""
444 lines = self._currentNOTAM.splitlines()
445 lines = map(lambda line: line.strip(), lines)
446
447 if len(lines)<4:
448 return None
449
450 if not lines[1].startswith("Q)") or \
451 not lines[2].startswith("A)") or \
452 not (lines[3].startswith("E)") or
453 (lines[3].startswith("D)") and lines[4].startswith("E)"))):
454 return None
455
456 ident = lines[0].split()[0]
457 basic = lines[1][2:].strip()
458
459 words = lines[2].split()
460 if len(words)<4 or words[0]!="A)" or words[2]!="B)":
461 return None
462
463 begin = datetime.datetime.strptime(words[3], "%y%m%d%H%M")
464 end = None
465 permanent = False
466 if words[4]=="C)" and len(words)>=6:
467 if words[5] in ["PERM", "UFN"]:
468 permanent = True
469 else:
470 end = datetime.datetime.strptime(words[5], "%y%m%d%H%M")
471 else:
472 permanent = True
473
474 repeatCycle = None
475 noticeStartIndex = 3
476 if lines[3].startswith("D)"):
477 repeatCycle = lines[3][2:].strip()
478 noticeStartIndex = 4
479
480 notice = ""
481 for index in range(noticeStartIndex, len(lines)):
482 line = lines[index][2:] if index==noticeStartIndex else lines[index]
483 line = line.strip()
484
485 if line.lower().startswith("created:") or \
486 line.lower().startswith("source:"):
487 break
488
489 if notice: notice += " "
490 notice += line
491
492 return NOTAM(ident, basic, begin, notice, end = end,
493 permanent = permanent, repeatCycle = repeatCycle)
494
495 def _parseCurrentNOTAM2(self):
496 """Parse the current NOTAM with a second, more flexible method."""
497 lines = self._currentNOTAM.splitlines()
498 lines = map(lambda line: line.strip(), lines)
499
500 if not lines:
501 return None
502
503 ident = lines[0].split()[0]
504
505 lines = lines[1:]
506 for i in range(0, 2):
507 l = lines[-1].lower()
508 if l.startswith("created:") or l.startswith("source:"):
509 lines = lines[:-1]
510
511 lines = map(lambda line: line.strip(), lines)
512 contents = " ".join(lines).split()
513
514 items = {}
515 for i in ["Q)", "A)", "B)", "C)", "D)", "E)"]:
516 items[i] = ""
517
518 currentItem = None
519 for word in contents:
520 if word in items:
521 currentItem = word
522 elif currentItem in items:
523 s = items[currentItem]
524 if s: s+= " "
525 s += word
526 items[currentItem] = s
527
528 if not items["Q)"] or not items["A)"] or not items["B)"] or \
529 not items["E)"]:
530 return None
531
532 basic = items["Q)"]
533 begin = datetime.datetime.strptime(items["B)"], "%y%m%d%H%M")
534
535 end = None
536 permanent = False
537 if items["C)"]:
538 endItem = items["C)"]
539 if endItem in ["PERM", "UFN"]:
540 permanent = True
541 else:
542 end = datetime.datetime.strptime(items["C)"], "%y%m%d%H%M")
543 else:
544 permanent = True
545
546 repeatCycle = None
547 if items["D)"]:
548 repeatCycle = items["D)"]
549
550 notice = items["E)"]
551
552 return NOTAM(ident, basic, begin, notice, end = end,
553 permanent = permanent, repeatCycle = repeatCycle)
554
555#------------------------------------------------------------------------------
556
557class Result(object):
558 """A result object.
559
560 An instance of this filled with the appropriate data is passed to the
561 callback function on each request."""
562
563 def __repr__(self):
564 """Get a representation of the result."""
565 s = "<Result:"
566 for (key, value) in self.__dict__.iteritems():
567 s += " " + key + "=" + unicode(value)
568 s += ">"
569 return s
570
571#------------------------------------------------------------------------------
572
573class Request(object):
574 """Base class for requests.
575
576 It handles any exceptions and the calling of the callback.
577
578 If an exception occurs during processing, the callback is called with
579 the two parameters: a boolean value of False, and the exception object.
580
581 If no exception occurs, the callback is called with True and the return
582 value of the run() function.
583
584 If the callback function throws an exception, that is caught and logged
585 to the debug log."""
586 def __init__(self, callback):
587 """Construct the request."""
588 self._callback = callback
589
590 def perform(self):
591 """Perform the request.
592
593 The object's run() function is called. If it throws an exception,
594 the callback is called with False, and the exception. Otherwise the
595 callback is called with True and the return value of the run()
596 function. Any exceptions thrown by the callback are caught and
597 reported."""
598 try:
599 result = self.run()
600 returned = True
601 except Exception, e:
602 traceback.print_exc()
603 result = e
604 returned = False
605
606 try:
607 self._callback(returned, result)
608 except Exception, e:
609 print >> sys.stderr, "web.Handler.Request.perform: callback throwed an exception: " + util.utf2unicode(str(e))
610 #traceback.print_exc()
611
612#------------------------------------------------------------------------------
613
614class RPCRequest(Request):
615 """Common base class for RPC requests.
616
617 It stores the RPC client received from the handler."""
618 def __init__(self, client, callback):
619 """Construct the request."""
620 super(RPCRequest, self).__init__(callback)
621 self._client = client
622
623#------------------------------------------------------------------------------
624
625class Login(Request):
626 """A login request."""
627 iso88592decoder = codecs.getdecoder("iso-8859-2")
628
629 def __init__(self, callback, pilotID, password, entranceExam):
630 """Construct the login request with the given pilot ID and
631 password."""
632 super(Login, self).__init__(callback)
633
634 self._pilotID = pilotID
635 self._password = password
636 self._entranceExam = entranceExam
637
638 def run(self):
639 """Perform the login request."""
640 md5 = hashlib.md5()
641 md5.update(self._pilotID)
642 pilotID = md5.hexdigest()
643
644 md5 = hashlib.md5()
645 md5.update(self._password)
646 password = md5.hexdigest()
647
648 if self._entranceExam:
649 url = MAVA_BASE_URL + "/ellenorzo/getflightplan.php?pid=%s" % \
650 (pilotID,)
651 else:
652 url = MAVA_BASE_URL + "/leker2.php?pid=%s&psw=%s" % \
653 (pilotID, password)
654
655 result = Result()
656 result.entranceExam = self._entranceExam
657
658 f = urllib2.urlopen(url, timeout = 10.0)
659
660 status = readline(f)
661 if self._entranceExam:
662 result.loggedIn = status != "#NOEXAM"
663 else:
664 result.loggedIn = status == ".OK."
665
666 if result.loggedIn:
667 result.pilotID = self._pilotID
668 result.password = self._password
669 result.flights = []
670 # FIXME: this may not be the correct behaviour
671 # for an entrance exam, but the website returns
672 # an error
673 if self._entranceExam:
674 result.pilotName = result.pilotID
675 result.exams = ""
676 else:
677 result.pilotName = self.iso88592decoder(readline(f))[0]
678 result.exams = readline(f)
679
680 while True:
681 line = readline(f)
682 if not line or line == "#ENDPIREP": break
683
684 flight = BookedFlight(line)
685 flight.readFromWeb(f)
686 result.flights.append(flight)
687
688 result.flights.sort(cmp = lambda flight1, flight2:
689 cmp(flight1.departureTime,
690 flight2.departureTime))
691
692 f.close()
693
694 return result
695
696#------------------------------------------------------------------------------
697
698class LoginRPC(RPCRequest):
699 """An RPC-based login request."""
700 def __init__(self, client, callback, pilotID, password, entranceExam):
701 """Construct the login request with the given pilot ID and
702 password."""
703 super(LoginRPC, self).__init__(client, callback)
704
705 self._pilotID = pilotID
706 self._password = password
707 self._entranceExam = entranceExam
708
709 def run(self):
710 """Perform the login request."""
711 result = Result()
712 # FIXME: handle the entrance exam case
713 result.entranceExam = self._entranceExam
714
715 self._client.setCredentials(self._pilotID, self._password)
716 pilotName = self._client.login()
717 result.loggedIn = pilotName is not None
718 if result.loggedIn:
719 result.pilotID = self._pilotID
720 result.pilotName = pilotName
721 result.password = self._password
722 result.flights = self._client.getFlights()
723
724 return result
725
726#------------------------------------------------------------------------------
727
728class GetFleet(Request):
729 """Request to get the fleet from the website."""
730
731 def __init__(self, callback):
732 """Construct the fleet request."""
733 super(GetFleet, self).__init__(callback)
734
735 def run(self):
736 """Perform the login request."""
737 url = MAVA_BASE_URL + "/onlinegates_get.php"
738
739 f = urllib2.urlopen(url, timeout = 10.0)
740 result = Result()
741 result.fleet = Fleet(f)
742 f.close()
743
744 return result
745
746#------------------------------------------------------------------------------
747
748class GetFleetRPC(RPCRequest):
749 """Request to get the fleet from the website using RPC."""
750 def __init__(self, client, callback):
751 """Construct the request with the given client and callback function."""
752 super(GetFleetRPC, self).__init__(client, callback)
753
754 def run(self):
755 """Perform the login request."""
756 result = Result()
757
758 result.fleet = self._client.getFleet()
759
760 return result
761
762#------------------------------------------------------------------------------
763
764class UpdatePlane(Request):
765 """Update the status of one of the planes in the fleet."""
766 def __init__(self, callback, tailNumber, status, gateNumber = None):
767 """Construct the request."""
768 super(UpdatePlane, self).__init__(callback)
769 self._tailNumber = tailNumber
770 self._status = status
771 self._gateNumber = gateNumber
772
773 def run(self):
774 """Perform the plane update."""
775 url = MAVA_BASE_URL + "/onlinegates_set.php"
776
777 status = Plane.status2str(self._status)
778
779 gateNumber = self._gateNumber if self._gateNumber else ""
780
781 data = urllib.urlencode([("lajstrom", self._tailNumber),
782 ("status", status),
783 ("kapu", gateNumber)])
784
785 f = urllib2.urlopen(url, data, timeout = 10.0)
786 line = readline(f)
787
788 result = Result()
789 result.success = line == "OK"
790
791 return result
792
793#------------------------------------------------------------------------------
794
795class UpdatePlaneRPC(RPCRequest):
796 """RPC request to update the status and the position of a plane in the
797 fleet."""
798 def __init__(self, client, callback, tailNumber, status, gateNumber = None):
799 """Construct the request."""
800 super(UpdatePlaneRPC, self).__init__(client, callback)
801 self._tailNumber = tailNumber
802 self._status = status
803 self._gateNumber = gateNumber
804
805 def run(self):
806 """Perform the plane update."""
807 self._client.updatePlane(self._tailNumber, self._status, self._gateNumber)
808
809 # Otherwise an exception is thrown
810 result = Result()
811 result.success = True
812
813 return result
814
815#------------------------------------------------------------------------------
816
817class GetNOTAMs(Request):
818 """Get the NOTAMs from EURoutePro and select the ones we are interested
819 in."""
820 def __init__(self, callback, departureICAO, arrivalICAO):
821 """Construct the request for the given airports."""
822 super(GetNOTAMs, self).__init__(callback)
823 self._departureICAO = departureICAO
824 self._arrivalICAO = arrivalICAO
825
826 def run(self):
827 """Perform the retrieval of the NOTAMs."""
828 departureNOTAMs = self.getPilotsWebNOTAMs(self._departureICAO)
829 arrivalNOTAMs = self.getPilotsWebNOTAMs(self._arrivalICAO)
830
831 icaos = []
832 if not departureNOTAMs: icaos.append(self._departureICAO)
833 if not arrivalNOTAMs: icaos.append(self._arrivalICAO)
834
835 if icaos:
836 xmlParser = xml.sax.make_parser()
837 notamHandler = NOTAMHandler(icaos)
838 xmlParser.setContentHandler(notamHandler)
839
840 url = "http://notams.euroutepro.com/notams.xml"
841
842 f = urllib2.urlopen(url, timeout = 10.0)
843 try:
844 xmlParser.parse(f)
845 finally:
846 f.close()
847
848 for icao in icaos:
849 if icao==self._departureICAO:
850 departureNOTAMs = notamHandler.get(icao)
851 else:
852 arrivalNOTAMs = notamHandler.get(icao)
853
854 result = Result()
855 result.departureNOTAMs = departureNOTAMs
856 result.arrivalNOTAMs = arrivalNOTAMs
857
858 return result
859
860 def getPilotsWebNOTAMs(self, icao):
861 """Try to get the NOTAMs from FAA's PilotsWeb site for the given ICAO
862 code.
863
864 Returns a list of PilotsWEBNOTAM objects, or None in case of an error."""
865 try:
866 parser = PilotsWebNOTAMsParser()
867
868 url = "https://pilotweb.nas.faa.gov/PilotWeb/notamRetrievalByICAOAction.do?method=displayByICAOs&formatType=ICAO&retrieveLocId=%s&reportType=RAW&actionType=notamRetrievalByICAOs" % \
869 (icao.upper(),)
870
871 f = urllib2.urlopen(url, timeout = 10.0)
872 try:
873 data = f.read(16384)
874 while data:
875 parser.feed(data)
876 data = f.read(16384)
877 finally:
878 f.close()
879
880 return parser.getNOTAMs()
881
882 except Exception, e:
883 traceback.print_exc()
884 print "mlx.web.GetNOTAMs.getPilotsWebNOTAMs: failed to get NOTAMs for '%s': %s" % \
885 (icao, str(e))
886 return None
887
888#------------------------------------------------------------------------------
889
890class GetMETARs(Request):
891 """Get the METARs from the NOAA website for certain airport ICAOs."""
892
893 def __init__(self, callback, airports):
894 """Construct the request for the given airports."""
895 super(GetMETARs, self).__init__(callback)
896 self._airports = airports
897
898 def run(self):
899 """Perform the retrieval opf the METARs."""
900 url = "http://www.aviationweather.gov/adds/dataserver_current/httpparam?"
901 data = urllib.urlencode([ ("dataSource" , "metars"),
902 ("requestType", "retrieve"),
903 ("format", "csv"),
904 ("stationString", " ".join(self._airports)),
905 ("hoursBeforeNow", "24"),
906 ("mostRecentForEachStation", "constraint")])
907 url += data
908 f = urllib2.urlopen(url, timeout = 10.0)
909 try:
910 result = Result()
911 result.metars = {}
912 for line in iter(f.readline, ""):
913 if len(line)>5 and line[4]==' ':
914 icao = line[0:4]
915 if icao in self._airports:
916 result.metars[icao] = line.strip().split(",")[0]
917 finally:
918 f.close()
919
920 return result
921
922#------------------------------------------------------------------------------
923
924class SendPIREP(Request):
925 """A request to send a PIREP to the MAVA website."""
926 _latin2Encoder = codecs.getencoder("iso-8859-2")
927
928 def __init__(self, callback, pirep):
929 """Construct the sending of the PIREP."""
930 super(SendPIREP, self).__init__(callback)
931 self._pirep = pirep
932
933 def run(self):
934 """Perform the sending of the PIREP."""
935 url = MAVA_BASE_URL + "/malevacars.php"
936
937 pirep = self._pirep
938
939 data = {}
940 data["acarsdata"] = SendPIREP._latin2Encoder(pirep.getACARSText())[0]
941
942 bookedFlight = pirep.bookedFlight
943 data["foglalas_id"] = bookedFlight.id
944 data["repdate"] = bookedFlight.departureTime.date().strftime("%Y-%m-%d")
945 data["fltnum"] = bookedFlight.callsign
946 data["depap"] = bookedFlight.departureICAO
947 data["arrap"] = bookedFlight.arrivalICAO
948 data["pass"] = str(pirep.numPassengers)
949 data["crew"] = str(pirep.numCrew)
950 data["cargo"] = str(pirep.cargoWeight)
951 data["bag"] = str(pirep.bagWeight)
952 data["mail"] = str(pirep.mailWeight)
953
954 data["flttype"] = pirep.flightTypeText
955 data["onoff"] = "1" if pirep.online else "0"
956 data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
957 data["bt_arr"] = util.getTimestampString(pirep.blockTimeEnd)
958 data["bt_dur"] = util.getTimeIntervalString(pirep.blockTimeEnd -
959 pirep.blockTimeStart)
960 data["ft_dep"] = util.getTimestampString(pirep.flightTimeStart)
961 data["ft_arr"] = util.getTimestampString(pirep.flightTimeEnd)
962 data["ft_dur"] = util.getTimeIntervalString(pirep.flightTimeEnd -
963 pirep.flightTimeStart)
964 data["timecomm"] = pirep.getTimeComment()
965 data["fuel"] = "%.2f" % (pirep.fuelUsed,)
966 data["dep_rwy"] = pirep.departureRunway
967 data["arr_rwy"] = pirep.arrivalRunway
968 data["wea_dep"] = pirep.departureMETAR
969 data["wea_arr"] = pirep.arrivalMETAR
970 data["alt"] = "FL%.0f" % (pirep.filedCruiseAltitude/100.0,)
971 if pirep.filedCruiseAltitude!=pirep.cruiseAltitude:
972 data["mod_alt"] = "FL%.0f" % (pirep.cruiseAltitude/100.0,)
973 else:
974 data["mod_alt"] = ""
975 data["sid"] = pirep.sid
976 data["navroute"] = pirep.route
977 data["star"] = pirep.getSTAR()
978 data["aprtype"] = pirep.approachType
979 data["diff"] = "2"
980 data["comment"] = SendPIREP._latin2Encoder(pirep.comments)[0]
981 data["flightdefect"] = SendPIREP._latin2Encoder(pirep.flightDefects)[0]
982 data["kritika"] = pirep.getRatingText()
983 data["flightrating"] = "%.1f" % (max(0.0, pirep.rating),)
984 data["distance"] = "%.3f" % (pirep.flownDistance,)
985 data["insdate"] = datetime.date.today().strftime("%Y-%m-%d")
986
987 postData = urllib.urlencode(data)
988 f = urllib2.urlopen(url, postData, timeout = 10.0)
989 try:
990 result = Result()
991 line = f.readline().strip()
992 print "PIREP result from website:", line
993 result.success = line=="OK"
994 result.alreadyFlown = line=="MARVOLT"
995 result.notAvailable = line=="NOMORE"
996 finally:
997 f.close()
998
999 return result
1000#------------------------------------------------------------------------------
1001
1002class SendPIREPRPC(RPCRequest):
1003 """A request to send a PIREP to the MAVA website via the RPC interface."""
1004
1005 def __init__(self, client, callback, pirep):
1006 """Construct the sending of the PIREP."""
1007 super(SendPIREPRPC, self).__init__(client, callback)
1008 self._pirep = pirep
1009
1010 def run(self):
1011 """Perform the sending of the PIREP."""
1012 pirep = self._pirep
1013 resultCode = self._client.addPIREP(pirep.bookedFlight.id, pirep)
1014
1015 result = Result()
1016 result.success = resultCode==rpc.Client.RESULT_OK
1017 result.alreadyFlown = resultCode==rpc.Client.RESULT_FLIGHT_ALREADY_REPORTED
1018 result.notAvailable = resultCode==rpc.Client.RESULT_FLIGHT_NOT_EXISTS
1019
1020 return result
1021
1022#------------------------------------------------------------------------------
1023
1024class SendACARS(Request):
1025 """A request to send an ACARS to the MAVA website."""
1026 _latin2Encoder = codecs.getencoder("iso-8859-2")
1027
1028 def __init__(self, callback, acars):
1029 """Construct the request for the given PIREP."""
1030 super(SendACARS, self).__init__(callback)
1031 self._acars = acars
1032
1033 def run(self):
1034 """Perform the sending of the ACARS."""
1035 print "Sending the online ACARS"
1036
1037 url = MAVA_BASE_URL + "/acars2/acarsonline.php"
1038
1039 acars = self._acars
1040 bookedFlight = acars.bookedFlight
1041
1042 data = {}
1043 data["pid"] = acars.pid
1044 data["pilot"] = SendACARS._latin2Encoder(acars.pilotName)[0]
1045
1046 data["pass"] = str(bookedFlight.numPassengers)
1047 data["callsign"] = bookedFlight.callsign
1048 data["airplane"] = bookedFlight.aircraftTypeName
1049 data["from"] = bookedFlight.departureICAO
1050 data["to"] = bookedFlight.arrivalICAO
1051 data["lajstrom"] = bookedFlight.tailNumber
1052
1053 data["block_time"] = acars.getBlockTimeText()
1054 data["longitude"] = str(acars.state.longitude)
1055 data["latitude"] = str(acars.state.latitude)
1056 data["altitude"] = str(acars.state.altitude)
1057 data["speed"] = str(acars.state.groundSpeed)
1058
1059 data["event"] = acars.getEventText()
1060
1061 f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
1062 try:
1063 result = Result()
1064 finally:
1065 f.close()
1066
1067 return result
1068
1069#------------------------------------------------------------------------------
1070
1071class SendACARSRPC(RPCRequest):
1072 """A request to send an ACARS to the MAVA website via JSON-RPC."""
1073 def __init__(self, client, callback, acars):
1074 """Construct the request for the given PIREP."""
1075 super(SendACARSRPC, self).__init__(client, callback)
1076 self._acars = acars
1077
1078 def run(self):
1079 """Perform the sending of the ACARS."""
1080 print "Sending the online ACARS via JSON-RPC"
1081
1082 self._client.updateOnlineACARS(self._acars)
1083 return Result()
1084
1085#------------------------------------------------------------------------------
1086
1087class SendBugReport(Request):
1088 """A request to send a bug report to the project homepage."""
1089 _latin2Encoder = codecs.getencoder("iso-8859-2")
1090
1091 def __init__(self, callback, summary, description, email):
1092 """Construct the request for the given bug report."""
1093 super(SendBugReport, self).__init__(callback)
1094 self._summary = summary
1095 self._description = description
1096 self._email = email
1097
1098 def run(self):
1099 """Perform the sending of the bug report."""
1100 serverProxy = xmlrpclib.ServerProxy("http://mlx.varadiistvan.hu/rpc")
1101
1102 result = Result()
1103 result.success = False
1104
1105 attributes = {}
1106 if self._email:
1107 attributes["reporter"] = self._email
1108
1109 result.ticketID = serverProxy.ticket.create(self._summary, self._description,
1110 attributes, True)
1111 print "Created ticket with ID:", result.ticketID
1112 result.success = True
1113
1114 return result
1115
1116#------------------------------------------------------------------------------
1117
1118class Handler(threading.Thread):
1119 """The handler for the web services.
1120
1121 It can process one request at a time. The results are passed to a callback
1122 function."""
1123 def __init__(self, config, getCredentialsFn):
1124 """Construct the handler."""
1125 super(Handler, self).__init__()
1126
1127 self._requests = []
1128 self._requestCondition = threading.Condition()
1129
1130 self.daemon = True
1131 self._config = config
1132 self._rpcClient = rpc.Client(getCredentialsFn)
1133 if config.rememberPassword:
1134 self._rpcClient.setCredentials(config.pilotID, config.password)
1135
1136 def login(self, callback, pilotID, password, entranceExam = False):
1137 """Enqueue a login request."""
1138 request = \
1139 LoginRPC(self._rpcClient, callback, pilotID, password, entranceExam) \
1140 if self._config.useRPC and not entranceExam \
1141 else Login(callback, pilotID, password, entranceExam)
1142
1143 self._addRequest(request)
1144
1145 def getFleet(self, callback):
1146 """Enqueue a fleet retrieval request."""
1147 request = \
1148 GetFleetRPC(self._rpcClient, callback,) if self._config.useRPC \
1149 else GetFleet(callback)
1150 self._addRequest(request)
1151
1152 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
1153 """Update the status of the given plane."""
1154 request = \
1155 UpdatePlaneRPC(self._rpcClient, callback,
1156 tailNumber, status, gateNumber) \
1157 if self._config.useRPC \
1158 else UpdatePlane(callback, tailNumber, status, gateNumber)
1159 self._addRequest(request)
1160
1161 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
1162 """Get the NOTAMs for the given two airports."""
1163 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
1164
1165 def getMETARs(self, callback, airports):
1166 """Get the METARs for the given airports."""
1167 self._addRequest(GetMETARs(callback, airports))
1168
1169 def sendPIREP(self, callback, pirep):
1170 """Send the given PIREP."""
1171 request = \
1172 SendPIREPRPC(self._rpcClient, callback, pirep) if self._config.useRPC \
1173 else SendPIREP(callback, pirep)
1174 self._addRequest(request)
1175
1176 def sendACARS(self, callback, acars):
1177 """Send the given ACARS"""
1178 request = \
1179 SendACARSRPC(self._rpcClient, callback, acars) if self._config.useRPC \
1180 else SendACARS(callback, acars)
1181 self._addRequest(request)
1182
1183 def sendBugReport(self, callback, summary, description, email):
1184 """Send a bug report with the given data."""
1185 self._addRequest(SendBugReport(callback, summary, description, email))
1186
1187 def run(self):
1188 """Process the requests."""
1189 while True:
1190 with self._requestCondition:
1191 while not self._requests:
1192 self._requestCondition.wait()
1193 request = self._requests[0]
1194 del self._requests[0]
1195
1196 request.perform()
1197
1198 def _addRequest(self, request):
1199 """Add the given request to the queue."""
1200 with self._requestCondition:
1201 self._requests.append(request)
1202 self._requestCondition.notify()
1203
1204#------------------------------------------------------------------------------
1205
1206if __name__ == "__main__":
1207 import time
1208
1209 def callback(returned, result):
1210 print returned, unicode(result)
1211
1212 handler = Handler()
1213 handler.start()
1214
1215 #handler.login(callback, "P096", "V5fwj")
1216 #handler.getFleet(callback)
1217 # Plane: HA-LEG home (gate 67)
1218 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
1219 #time.sleep(3)
1220 #handler.getFleet(callback)
1221 #time.sleep(3)
1222
1223 #handler.getNOTAMs(callback, "LHBP", "EPWA")
1224 #handler.getMETARs(callback, ["LHBP", "EPWA"])
1225 #time.sleep(5)
1226
1227 handler.updatePlane(callback, "HA-LON", const.PLANE_AWAY, "")
1228 time.sleep(3)
1229
1230#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.