source: src/mlx/web.py@ 806:e456d06f32d6

version_0.38.3
Last change on this file since 806:e456d06f32d6 was 806:e456d06f32d6, checked in by István Váradi <ivaradi@…>, 8 years ago

Fixed the parsing of the beginning and ending times of NOTAMs to handle both 2- and 4-digit years

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