source: src/mlx/web.py@ 740:940403ee48fb

Last change on this file since 740:940403ee48fb was 740:940403ee48fb, checked in by István Váradi <ivaradi@…>, 8 years ago

Added support for an environment variable that can be set to use a different base URL for the MAVA site.

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