source: src/mlx/web.py@ 708:2e411a2d77a0

Last change on this file since 708:2e411a2d77a0 was 570:7b92e7021a5b, checked in by István Váradi <ivaradi@…>, 10 years ago

Changed NOTAM acquisition to use PilotWeb as the primary source (re #232)

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