source: src/mlx/web.py@ 816:4f970cbc02b9

Last change on this file since 816:4f970cbc02b9 was 809:4da4f80a2ab4, checked in by István Váradi <ivaradi@…>, 8 years ago

The past, pending flights are retrieved (re #307)

File size: 47.7 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 STATUS_BOOKED = 1
99
100 STATUS_REPORTED = 2
101
102 STATUS_ACCEPTED = 3
103
104 STATUS_REJECTED = 4
105
106 @staticmethod
107 def getDateTime(date, time):
108 """Get a datetime object from the given textual date and time."""
109 return datetime.datetime.strptime(date + " " + time,
110 "%Y-%m-%d %H:%M:%S")
111
112 @staticmethod
113 def forCheckFlight(aircraftType):
114 """Create a booked flight for a check flight with the given aircraft
115 type."""
116 flight = BookedFlight()
117
118 flight.departureICAO = "LHBP"
119 flight.arrivalICAO = "LHBP"
120
121 flight.aircraftType = aircraftType
122 flight.aircraftTypeName = BookedFlight.TYPE2TYPECODE[aircraftType]
123
124 # FIXME: perhaps find one for the type
125 flight.tailNumber = "HA-CHK"
126 flight.callsign = "HA-CHK"
127
128 flight.numPassengers = 0
129 flight.numCrew = 2
130 flight.bagWeight = 0
131 flight.cargoWeight = 0
132 flight.mailWeight = 0
133 flight.route = "DCT"
134
135 t = datetime.datetime.now() + datetime.timedelta(minutes = 20)
136 flight.departureTime = datetime.datetime(t.year, t.month, t.day,
137 t.hour, t.minute)
138 t = flight.departureTime + datetime.timedelta(minutes = 30)
139 flight.arrivalTime = datetime.datetime(t.year, t.month, t.day,
140 t.hour, t.minute)
141
142 return flight
143
144 def __init__(self, id = None):
145 """Construct a booked flight with the given ID."""
146 self.id = id
147
148 @property
149 def status(self):
150 """Get the status of the flight.
151
152 For web-based flights this is always STATUS_BOOKED."""
153 return BookedFlight.STATUS_BOOKED
154
155 def readFromWeb(self, f):
156 """Read the data of the flight from the web via the given file
157 object."""
158 self.callsign = readline(f)
159
160 date = readline(f)
161 print "web.BookedFlight.readFromWeb: date:", date
162 if date=="0000-00-00": date = "0001-01-01"
163
164 self.departureICAO = readline(f)
165 self.arrivalICAO = readline(f)
166
167 self._readAircraftType(f)
168 self.tailNumber = readline(f)
169 self.numPassengers = int(readline(f))
170 self.numCrew = int(readline(f))
171 self.bagWeight = int(readline(f))
172 self.cargoWeight = int(readline(f))
173 self.mailWeight = int(readline(f))
174 self.route = readline(f)
175
176 departureTime = readline(f)
177 self.departureTime = BookedFlight.getDateTime(date, departureTime)
178
179 arrivalTime = readline(f)
180 self.arrivalTime = BookedFlight.getDateTime(date, arrivalTime)
181 if self.arrivalTime<self.departureTime:
182 self.arrivalTime += datetime.timedelta(days = 1)
183
184 if not readline(f)==".NEXT.":
185 raise Exception("Invalid line in flight data")
186
187 def readFromFile(self, f):
188 """Read the data of the flight from a file via the given file
189 object."""
190 date = None
191 departureTime = None
192 arrivalTime = None
193
194 line = f.readline()
195 lineNumber = 0
196 while line:
197 lineNumber += 1
198 line = line.strip()
199
200 hashIndex = line.find("#")
201 if hashIndex>=0: line = line[:hashIndex]
202 if line:
203 equalIndex = line.find("=")
204 lineOK = equalIndex>0
205
206 if lineOK:
207 key = line[:equalIndex].strip()
208 value = line[equalIndex+1:].strip().replace("\:", ":")
209
210 lineOK = key and value
211
212 if lineOK:
213 if key=="callsign": self.callsign = value
214 elif key=="date": date = value
215 elif key=="dep_airport": self.departureICAO = value
216 elif key=="dest_airport": self.arrivalICAO = value
217 elif key=="planecode": self.aircraftType = \
218 self._decodeAircraftType(value)
219 elif key=="planetype": self.aircraftTypeName = value
220 elif key=="tail_nr": self.tailNumber = value
221 elif key=="passenger": self.numPassengers = int(value)
222 elif key=="crew": self.numCrew = int(value)
223 elif key=="bag": self.bagWeight = int(value)
224 elif key=="cargo": self.cargoWeight = int(value)
225 elif key=="mail": self.mailWeight = int(value)
226 elif key=="flight_route": self.route = value
227 elif key=="departure_time": departureTime = value
228 elif key=="arrival_time": arrivalTime = value
229 elif key=="foglalas_id":
230 self.id = None if value=="0" else value
231 else: lineOK = False
232
233 if not lineOK:
234 print "web.BookedFlight.readFromFile: line %d is invalid" % \
235 (lineNumber,)
236
237 line = f.readline()
238
239 if date is not None:
240 if departureTime is not None:
241 self.departureTime = BookedFlight.getDateTime(date,
242 departureTime)
243 if arrivalTime is not None:
244 self.arrivalTime = BookedFlight.getDateTime(date,
245 arrivalTime)
246
247 d = dir(self)
248 for attribute in ["callsign", "departureICAO", "arrivalICAO",
249 "aircraftType", "tailNumber",
250 "numPassengers", "numCrew",
251 "bagWeight", "cargoWeight", "mailWeight",
252 "route", "departureTime", "arrivalTime"]:
253 if attribute not in d:
254 raise Exception("Attribute %s could not be read" % (attribute,))
255
256 if "aircraftTypeName" not in d:
257 self.aircraftTypeName = \
258 BookedFlight.TYPE2TYPECODE[self.aircraftType]
259
260 def writeIntoFile(self, f):
261 """Write the flight into a file."""
262 print >> f, "callsign=%s" % (self.callsign,)
263 date = self.departureTime.date()
264 print >> f, "date=%04d-%02d-%0d" % (date.year, date.month, date.day)
265 print >> f, "dep_airport=%s" % (self.departureICAO,)
266 print >> f, "dest_airport=%s" % (self.arrivalICAO,)
267 print >> f, "planecode=%s" % \
268 (BookedFlight.TYPE2TYPECODE[self.aircraftType],)
269 print >> f, "planetype=%s" % (self.aircraftTypeName,)
270 print >> f, "tail_nr=%s" % (self.tailNumber,)
271 print >> f, "passenger=%d" % (self.numPassengers,)
272 print >> f, "crew=%d" % (self.numCrew,)
273 print >> f, "bag=%d" % (self.bagWeight,)
274 print >> f, "cargo=%d" % (self.cargoWeight,)
275 print >> f, "mail=%d" % (self.mailWeight,)
276 print >> f, "flight_route=%s" % (self.route,)
277 departureTime = self.departureTime
278 print >> f, "departure_time=%02d\\:%02d\\:%02d" % \
279 (departureTime.hour, departureTime.minute, departureTime.second)
280 arrivalTime = self.arrivalTime
281 print >> f, "arrival_time=%02d\\:%02d\\:%02d" % \
282 (arrivalTime.hour, arrivalTime.minute, arrivalTime.second)
283 print >> f, "foglalas_id=%s" % ("0" if self.id is None else self.id,)
284
285 def _readAircraftType(self, f):
286 """Read the aircraft type from the given file."""
287 line = readline(f)
288 typeCode = line[:3]
289 self.aircraftType = self._decodeAircraftType(typeCode)
290 self.aircraftTypeName = line[3:]
291
292 def _decodeAircraftType(self, typeCode):
293 """Decode the aircraft type from the given typeCode."""
294 if typeCode in self.TYPECODE2TYPE:
295 return self.TYPECODE2TYPE[typeCode]
296 else:
297 raise Exception("Invalid aircraft type code: '" + typeCode + "'")
298
299 def __repr__(self):
300 """Get a representation of the flight."""
301 s = "<Flight: %s-%s, %s, %s-%s," % (self.departureICAO,
302 self.arrivalICAO,
303 self.route,
304 self.departureTime, self.arrivalTime)
305 s += " %d %s," % (self.aircraftType, self.tailNumber)
306 s += " pax=%d, crew=%d, bag=%d, cargo=%d, mail=%d" % \
307 (self.numPassengers, self.numCrew,
308 self.bagWeight, self.cargoWeight, self.mailWeight)
309 s += ">"
310 return s
311
312#------------------------------------------------------------------------------
313
314class Plane(rpccommon.Plane):
315 """Information about an airplane in the fleet."""
316 def __init__(self, s):
317 """Build a plane info based on the given string.
318
319 The string consists of three, space-separated fields.
320 The first field is the tail number, the second field is the gate
321 number, the third field is the plane's status as a character."""
322 super(Plane, self).__init__()
323
324 try:
325 words = s.split(" ")
326 tailNumber = words[0]
327 self.tailNumber = tailNumber
328
329 status = words[2] if len(words)>2 else None
330 self._setStatus(status)
331
332 gateNumber = words[1] if len(words)>1 else ""
333 self.gateNumber = gateNumber if gateNumber else None
334
335 except:
336 print >> sys.stderr, "Plane string is invalid: '" + s + "'"
337 self.tailNumber = None
338
339#------------------------------------------------------------------------------
340
341class Fleet(rpccommon.Fleet):
342 """Information about the whole fleet."""
343 def __init__(self, f):
344 """Construct the fleet information by reading the given file object."""
345 super(Fleet, self).__init__()
346
347 while True:
348 line = readline(f)
349 if not line or line == "#END": break
350
351 plane = Plane(line)
352 self._addPlane(plane)
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 def parseTime(item):
586 try:
587 return datetime.datetime.strptime(item, "%y%m%d%H%M")
588 except ValueError:
589 return datetime.datetime.strptime(item, "%Y%m%d%H%M")
590
591 basic = items["Q)"]
592 begin = parseTime(items["B)"])
593
594 end = None
595 permanent = False
596 if items["C)"]:
597 endItem = items["C)"]
598 if endItem in ["PERM", "UFN"]:
599 permanent = True
600 else:
601 end = parseTime(items["C)"])
602 else:
603 permanent = True
604
605 repeatCycle = None
606 if items["D)"]:
607 repeatCycle = items["D)"]
608
609 notice = items["E)"]
610
611 return NOTAM(ident, basic, begin, notice, end = end,
612 permanent = permanent, repeatCycle = repeatCycle)
613
614#------------------------------------------------------------------------------
615
616class Result(object):
617 """A result object.
618
619 An instance of this filled with the appropriate data is passed to the
620 callback function on each request."""
621
622 def __repr__(self):
623 """Get a representation of the result."""
624 s = "<Result:"
625 for (key, value) in self.__dict__.iteritems():
626 s += " " + key + "=" + unicode(value)
627 s += ">"
628 return s
629
630#------------------------------------------------------------------------------
631
632class Request(object):
633 """Base class for requests.
634
635 It handles any exceptions and the calling of the callback.
636
637 If an exception occurs during processing, the callback is called with
638 the two parameters: a boolean value of False, and the exception object.
639
640 If no exception occurs, the callback is called with True and the return
641 value of the run() function.
642
643 If the callback function throws an exception, that is caught and logged
644 to the debug log."""
645 def __init__(self, callback):
646 """Construct the request."""
647 self._callback = callback
648
649 def perform(self):
650 """Perform the request.
651
652 The object's run() function is called. If it throws an exception,
653 the callback is called with False, and the exception. Otherwise the
654 callback is called with True and the return value of the run()
655 function. Any exceptions thrown by the callback are caught and
656 reported."""
657 try:
658 result = self.run()
659 returned = True
660 except Exception, e:
661 traceback.print_exc()
662 result = e
663 returned = False
664
665 try:
666 self._callback(returned, result)
667 except Exception, e:
668 print >> sys.stderr, "web.Handler.Request.perform: callback throwed an exception: " + util.utf2unicode(str(e))
669 #traceback.print_exc()
670
671#------------------------------------------------------------------------------
672
673class RPCRequest(Request):
674 """Common base class for RPC requests.
675
676 It stores the RPC client received from the handler."""
677 def __init__(self, client, callback):
678 """Construct the request."""
679 super(RPCRequest, self).__init__(callback)
680 self._client = client
681
682#------------------------------------------------------------------------------
683
684class Register(RPCRequest):
685 """A registration request."""
686 def __init__(self, client, callback, registrationData):
687 """Construct the request."""
688 super(Register, self).__init__(client, callback)
689 self._registrationData = registrationData
690
691 def run(self):
692 """Perform the registration."""
693
694 registrationData = self._registrationData
695
696 (resultCode, pilotID) = self._client.register(registrationData)
697 result = Result()
698 result.registered = resultCode==rpc.Client.RESULT_OK
699 if result.registered:
700 result.pilotID = pilotID
701
702 self._client.setCredentials(pilotID, registrationData.password)
703 LoginRPC.setupLoginResult(result, self._client, pilotID,
704 registrationData.password)
705
706 result.invalidData = \
707 resultCode==rpc.Client.RESULT_INVALID_DATA
708 result.emailAlreadyRegistered = \
709 resultCode==rpc.Client.RESULT_EMAIL_ALREADY_REGISTERED
710
711 return result
712
713#------------------------------------------------------------------------------
714
715class Login(Request):
716 """A login request."""
717 iso88592decoder = codecs.getdecoder("iso-8859-2")
718
719 def __init__(self, callback, pilotID, password):
720 """Construct the login request with the given pilot ID and
721 password."""
722 super(Login, self).__init__(callback)
723
724 self._pilotID = pilotID
725 self._password = password
726
727 def run(self):
728 """Perform the login request."""
729 md5 = hashlib.md5()
730 md5.update(self._pilotID)
731 pilotID = md5.hexdigest()
732
733 md5 = hashlib.md5()
734 md5.update(self._password)
735 password = md5.hexdigest()
736
737 url = MAVA_BASE_URL + "/leker2.php?pid=%s&psw=%s" % (pilotID, password)
738
739 result = Result()
740
741 f = urllib2.urlopen(url, timeout = 10.0)
742
743 status = readline(f)
744 result.loggedIn = status == ".OK."
745
746 if result.loggedIn:
747 result.pilotID = self._pilotID
748 result.password = self._password
749 result.rank = "FO"
750 result.flights = []
751
752 result.pilotName = self.iso88592decoder(readline(f))[0]
753 result.exams = readline(f)
754
755 while True:
756 line = readline(f)
757 if not line or line == "#ENDPIREP": break
758
759 flight = BookedFlight(line)
760 flight.readFromWeb(f)
761 result.flights.append(flight)
762
763 result.flights.sort(cmp = lambda flight1, flight2:
764 cmp(flight1.departureTime,
765 flight2.departureTime))
766
767 f.close()
768
769 return result
770
771#------------------------------------------------------------------------------
772
773class LoginRPC(RPCRequest):
774 """An RPC-based login request."""
775 @staticmethod
776 def setupLoginResult(result, client, pilotID, password):
777 """Setup the login result with the given client, pilot ID and
778 password."""
779 loginResult = client.login()
780 result.loggedIn = loginResult is not None
781 if result.loggedIn:
782 result.pilotID = pilotID
783 result.pilotName = loginResult[0]
784 result.rank = loginResult[1]
785 result.password = password
786 flights = client.getFlights()
787 result.flights = flights[0]
788 result.reportedFlights = flights[1]
789 result.rejectedFlights = flights[2]
790 if result.rank=="STU":
791 reply = client.getEntryExamStatus()
792 result.entryExamPassed = reply[0]
793 result.entryExamLink = reply[1]
794 result.checkFlightStatus = reply[2]
795 if reply[3]:
796 result.rank = "FO"
797
798 def __init__(self, client, callback, pilotID, password):
799 """Construct the login request with the given pilot ID and
800 password."""
801 super(LoginRPC, self).__init__(client, callback)
802
803 self._pilotID = pilotID
804 self._password = password
805
806 def run(self):
807 """Perform the login request."""
808 result = Result()
809
810 self._client.setCredentials(self._pilotID, self._password)
811 LoginRPC.setupLoginResult(result, self._client,
812 self._pilotID, self._password)
813
814 return result
815
816#------------------------------------------------------------------------------
817
818class GetEntryExamStatus(RPCRequest):
819 """A request to get the entry exam status."""
820 def __init__(self, client, callback):
821 """Construct the request."""
822 super(GetEntryExamStatus, self).__init__(client, callback)
823
824 def run(self):
825 """Perform the query."""
826 result = Result()
827
828 reply = self._client.getEntryExamStatus()
829
830 result.entryExamPassed = reply[0]
831 result.entryExamLink = reply[1]
832 result.checkFlightStatus = reply[2]
833 result.madeFO = reply[3]
834
835 return result
836
837#------------------------------------------------------------------------------
838
839class GetFleet(Request):
840 """Request to get the fleet from the website."""
841
842 def __init__(self, callback):
843 """Construct the fleet request."""
844 super(GetFleet, self).__init__(callback)
845
846 def run(self):
847 """Perform the login request."""
848 url = MAVA_BASE_URL + "/onlinegates_get.php"
849
850 f = urllib2.urlopen(url, timeout = 10.0)
851 result = Result()
852 result.fleet = Fleet(f)
853 f.close()
854
855 return result
856
857#------------------------------------------------------------------------------
858
859class GetFleetRPC(RPCRequest):
860 """Request to get the fleet from the website using RPC."""
861 def __init__(self, client, callback):
862 """Construct the request with the given client and callback function."""
863 super(GetFleetRPC, self).__init__(client, callback)
864
865 def run(self):
866 """Perform the login request."""
867 result = Result()
868
869 result.fleet = self._client.getFleet()
870
871 return result
872
873#------------------------------------------------------------------------------
874
875class UpdatePlane(Request):
876 """Update the status of one of the planes in the fleet."""
877 def __init__(self, callback, tailNumber, status, gateNumber = None):
878 """Construct the request."""
879 super(UpdatePlane, self).__init__(callback)
880 self._tailNumber = tailNumber
881 self._status = status
882 self._gateNumber = gateNumber
883
884 def run(self):
885 """Perform the plane update."""
886 url = MAVA_BASE_URL + "/onlinegates_set.php"
887
888 status = Plane.status2str(self._status)
889
890 gateNumber = self._gateNumber if self._gateNumber else ""
891
892 data = urllib.urlencode([("lajstrom", self._tailNumber),
893 ("status", status),
894 ("kapu", gateNumber)])
895
896 f = urllib2.urlopen(url, data, timeout = 10.0)
897 line = readline(f)
898
899 result = Result()
900 result.success = line == "OK"
901
902 return result
903
904#------------------------------------------------------------------------------
905
906class UpdatePlaneRPC(RPCRequest):
907 """RPC request to update the status and the position of a plane in the
908 fleet."""
909 def __init__(self, client, callback, tailNumber, status, gateNumber = None):
910 """Construct the request."""
911 super(UpdatePlaneRPC, self).__init__(client, callback)
912 self._tailNumber = tailNumber
913 self._status = status
914 self._gateNumber = gateNumber
915
916 def run(self):
917 """Perform the plane update."""
918 self._client.updatePlane(self._tailNumber, self._status, self._gateNumber)
919
920 # Otherwise an exception is thrown
921 result = Result()
922 result.success = True
923
924 return result
925
926#------------------------------------------------------------------------------
927
928class GetNOTAMs(Request):
929 """Get the NOTAMs from EURoutePro and select the ones we are interested
930 in."""
931 def __init__(self, callback, departureICAO, arrivalICAO):
932 """Construct the request for the given airports."""
933 super(GetNOTAMs, self).__init__(callback)
934 self._departureICAO = departureICAO
935 self._arrivalICAO = arrivalICAO
936
937 def run(self):
938 """Perform the retrieval of the NOTAMs."""
939 departureNOTAMs = self.getPilotsWebNOTAMs(self._departureICAO)
940 arrivalNOTAMs = self.getPilotsWebNOTAMs(self._arrivalICAO)
941
942 icaos = []
943 if not departureNOTAMs: icaos.append(self._departureICAO)
944 if not arrivalNOTAMs: icaos.append(self._arrivalICAO)
945
946 if icaos:
947 xmlParser = xml.sax.make_parser()
948 notamHandler = NOTAMHandler(icaos)
949 xmlParser.setContentHandler(notamHandler)
950
951 url = "http://notams.euroutepro.com/notams.xml"
952
953 f = urllib2.urlopen(url, timeout = 10.0)
954 try:
955 xmlParser.parse(f)
956 finally:
957 f.close()
958
959 for icao in icaos:
960 if icao==self._departureICAO:
961 departureNOTAMs = notamHandler.get(icao)
962 else:
963 arrivalNOTAMs = notamHandler.get(icao)
964
965 result = Result()
966 result.departureNOTAMs = departureNOTAMs
967 result.arrivalNOTAMs = arrivalNOTAMs
968
969 return result
970
971 def getPilotsWebNOTAMs(self, icao):
972 """Try to get the NOTAMs from FAA's PilotsWeb site for the given ICAO
973 code.
974
975 Returns a list of PilotsWEBNOTAM objects, or None in case of an error."""
976 try:
977 parser = PilotsWebNOTAMsParser()
978
979 url = "https://pilotweb.nas.faa.gov/PilotWeb/notamRetrievalByICAOAction.do?method=displayByICAOs&formatType=ICAO&retrieveLocId=%s&reportType=RAW&actionType=notamRetrievalByICAOs" % \
980 (icao.upper(),)
981
982 f = urllib2.urlopen(url, timeout = 10.0)
983 try:
984 data = f.read(16384)
985 while data:
986 parser.feed(data)
987 data = f.read(16384)
988 finally:
989 f.close()
990
991 return parser.getNOTAMs()
992
993 except Exception, e:
994 traceback.print_exc()
995 print "mlx.web.GetNOTAMs.getPilotsWebNOTAMs: failed to get NOTAMs for '%s': %s" % \
996 (icao, str(e))
997 return None
998
999#------------------------------------------------------------------------------
1000
1001class GetMETARs(Request):
1002 """Get the METARs from the NOAA website for certain airport ICAOs."""
1003
1004 def __init__(self, callback, airports):
1005 """Construct the request for the given airports."""
1006 super(GetMETARs, self).__init__(callback)
1007 self._airports = airports
1008
1009 def run(self):
1010 """Perform the retrieval opf the METARs."""
1011 url = "http://www.aviationweather.gov/adds/dataserver_current/httpparam?"
1012 data = urllib.urlencode([ ("dataSource" , "metars"),
1013 ("requestType", "retrieve"),
1014 ("format", "csv"),
1015 ("stationString", " ".join(self._airports)),
1016 ("hoursBeforeNow", "24"),
1017 ("mostRecentForEachStation", "constraint")])
1018 url += data
1019 f = urllib2.urlopen(url, timeout = 10.0)
1020 try:
1021 result = Result()
1022 result.metars = {}
1023 for line in iter(f.readline, ""):
1024 if len(line)>5 and line[4]==' ':
1025 icao = line[0:4]
1026 if icao in self._airports:
1027 result.metars[icao] = line.strip().split(",")[0]
1028 finally:
1029 f.close()
1030
1031 return result
1032
1033#------------------------------------------------------------------------------
1034
1035class SendPIREP(Request):
1036 """A request to send a PIREP to the MAVA website."""
1037 _latin2Encoder = codecs.getencoder("iso-8859-2")
1038
1039 def __init__(self, callback, pirep):
1040 """Construct the sending of the PIREP."""
1041 super(SendPIREP, self).__init__(callback)
1042 self._pirep = pirep
1043
1044 def run(self):
1045 """Perform the sending of the PIREP."""
1046 url = MAVA_BASE_URL + "/malevacars.php"
1047
1048 pirep = self._pirep
1049
1050 data = {}
1051 data["acarsdata"] = SendPIREP._latin2Encoder(pirep.getACARSText())[0]
1052
1053 bookedFlight = pirep.bookedFlight
1054 data["foglalas_id"] = bookedFlight.id
1055 data["repdate"] = bookedFlight.departureTime.date().strftime("%Y-%m-%d")
1056 data["fltnum"] = bookedFlight.callsign
1057 data["depap"] = bookedFlight.departureICAO
1058 data["arrap"] = bookedFlight.arrivalICAO
1059 data["pass"] = str(pirep.numPassengers)
1060 data["crew"] = str(pirep.numCrew)
1061 data["cargo"] = str(pirep.cargoWeight)
1062 data["bag"] = str(pirep.bagWeight)
1063 data["mail"] = str(pirep.mailWeight)
1064
1065 data["flttype"] = pirep.flightTypeText
1066 data["onoff"] = "1" if pirep.online else "0"
1067 data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
1068 data["bt_arr"] = util.getTimestampString(pirep.blockTimeEnd)
1069 data["bt_dur"] = util.getTimeIntervalString(pirep.blockTimeEnd -
1070 pirep.blockTimeStart)
1071 data["ft_dep"] = util.getTimestampString(pirep.flightTimeStart)
1072 data["ft_arr"] = util.getTimestampString(pirep.flightTimeEnd)
1073 data["ft_dur"] = util.getTimeIntervalString(pirep.flightTimeEnd -
1074 pirep.flightTimeStart)
1075 data["timecomm"] = pirep.getTimeComment()
1076 data["fuel"] = "%.2f" % (pirep.fuelUsed,)
1077 data["dep_rwy"] = pirep.departureRunway
1078 data["arr_rwy"] = pirep.arrivalRunway
1079 data["wea_dep"] = pirep.departureMETAR
1080 data["wea_arr"] = pirep.arrivalMETAR
1081 data["alt"] = "FL%.0f" % (pirep.filedCruiseAltitude/100.0,)
1082 if pirep.filedCruiseAltitude!=pirep.cruiseAltitude:
1083 data["mod_alt"] = "FL%.0f" % (pirep.cruiseAltitude/100.0,)
1084 else:
1085 data["mod_alt"] = ""
1086 data["sid"] = pirep.sid
1087 data["navroute"] = pirep.route
1088 data["star"] = pirep.getSTAR()
1089 data["aprtype"] = pirep.approachType
1090 data["diff"] = "2"
1091 data["comment"] = SendPIREP._latin2Encoder(pirep.comments)[0]
1092 data["flightdefect"] = SendPIREP._latin2Encoder(pirep.flightDefects)[0]
1093 data["kritika"] = pirep.getRatingText()
1094 data["flightrating"] = "%.1f" % (max(0.0, pirep.rating),)
1095 data["distance"] = "%.3f" % (pirep.flownDistance,)
1096 data["insdate"] = datetime.date.today().strftime("%Y-%m-%d")
1097
1098 postData = urllib.urlencode(data)
1099 f = urllib2.urlopen(url, postData, timeout = 10.0)
1100 try:
1101 result = Result()
1102 line = f.readline().strip()
1103 print "PIREP result from website:", line
1104 result.success = line=="OK"
1105 result.alreadyFlown = line=="MARVOLT"
1106 result.notAvailable = line=="NOMORE"
1107 finally:
1108 f.close()
1109
1110 return result
1111#------------------------------------------------------------------------------
1112
1113class SendPIREPRPC(RPCRequest):
1114 """A request to send a PIREP to the MAVA website via the RPC interface."""
1115
1116 def __init__(self, client, callback, pirep):
1117 """Construct the sending of the PIREP."""
1118 super(SendPIREPRPC, self).__init__(client, callback)
1119 self._pirep = pirep
1120
1121 def run(self):
1122 """Perform the sending of the PIREP."""
1123 pirep = self._pirep
1124 resultCode = self._client.addPIREP(pirep.bookedFlight.id, pirep)
1125
1126 result = Result()
1127 result.success = resultCode==rpc.Client.RESULT_OK
1128 result.alreadyFlown = resultCode==rpc.Client.RESULT_FLIGHT_ALREADY_REPORTED
1129 result.notAvailable = resultCode==rpc.Client.RESULT_FLIGHT_NOT_EXISTS
1130
1131 return result
1132
1133#------------------------------------------------------------------------------
1134
1135class SendACARS(Request):
1136 """A request to send an ACARS to the MAVA website."""
1137 _latin2Encoder = codecs.getencoder("iso-8859-2")
1138
1139 def __init__(self, callback, acars):
1140 """Construct the request for the given PIREP."""
1141 super(SendACARS, self).__init__(callback)
1142 self._acars = acars
1143
1144 def run(self):
1145 """Perform the sending of the ACARS."""
1146 print "Sending the online ACARS"
1147
1148 url = MAVA_BASE_URL + "/acars2/acarsonline.php"
1149
1150 acars = self._acars
1151 bookedFlight = acars.bookedFlight
1152
1153 data = {}
1154 data["pid"] = acars.pid
1155 data["pilot"] = SendACARS._latin2Encoder(acars.pilotName)[0]
1156
1157 data["pass"] = str(bookedFlight.numPassengers)
1158 data["callsign"] = bookedFlight.callsign
1159 data["airplane"] = bookedFlight.aircraftTypeName
1160 data["from"] = bookedFlight.departureICAO
1161 data["to"] = bookedFlight.arrivalICAO
1162 data["lajstrom"] = bookedFlight.tailNumber
1163
1164 data["block_time"] = acars.getBlockTimeText()
1165 data["longitude"] = str(acars.state.longitude)
1166 data["latitude"] = str(acars.state.latitude)
1167 data["altitude"] = str(acars.state.altitude)
1168 data["speed"] = str(acars.state.groundSpeed)
1169
1170 data["event"] = acars.getEventText()
1171
1172 f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
1173 try:
1174 result = Result()
1175 finally:
1176 f.close()
1177
1178 return result
1179
1180#------------------------------------------------------------------------------
1181
1182class SendACARSRPC(RPCRequest):
1183 """A request to send an ACARS to the MAVA website via JSON-RPC."""
1184 def __init__(self, client, callback, acars):
1185 """Construct the request for the given PIREP."""
1186 super(SendACARSRPC, self).__init__(client, callback)
1187 self._acars = acars
1188
1189 def run(self):
1190 """Perform the sending of the ACARS."""
1191 print "Sending the online ACARS via JSON-RPC"
1192
1193 self._client.updateOnlineACARS(self._acars)
1194 return Result()
1195
1196#------------------------------------------------------------------------------
1197
1198class SendBugReport(Request):
1199 """A request to send a bug report to the project homepage."""
1200 _latin2Encoder = codecs.getencoder("iso-8859-2")
1201
1202 def __init__(self, callback, summary, description, email):
1203 """Construct the request for the given bug report."""
1204 super(SendBugReport, self).__init__(callback)
1205 self._summary = summary
1206 self._description = description
1207 self._email = email
1208
1209 def run(self):
1210 """Perform the sending of the bug report."""
1211 serverProxy = xmlrpclib.ServerProxy("http://mlx.varadiistvan.hu/rpc")
1212
1213 result = Result()
1214 result.success = False
1215
1216 attributes = {}
1217 if self._email:
1218 attributes["reporter"] = self._email
1219
1220 result.ticketID = serverProxy.ticket.create(self._summary, self._description,
1221 attributes, True)
1222 print "Created ticket with ID:", result.ticketID
1223 result.success = True
1224
1225 return result
1226
1227#------------------------------------------------------------------------------
1228
1229class SetCheckFlightPassed(RPCRequest):
1230 """A request to mark the user as one having passed the check flight."""
1231 def __init__(self, client, callback, aircraftType):
1232 """Construct the request for the given type."""
1233 super(SetCheckFlightPassed, self).__init__(client, callback)
1234 self._aircraftType = aircraftType
1235
1236 def run(self):
1237 """Perform the update."""
1238 aircraftType = BookedFlight.TYPE2TYPECODE[self._aircraftType]
1239 self._client.setCheckFlightPassed(aircraftType)
1240 return Result()
1241
1242#------------------------------------------------------------------------------
1243
1244class Handler(threading.Thread):
1245 """The handler for the web services.
1246
1247 It can process one request at a time. The results are passed to a callback
1248 function."""
1249 def __init__(self, config, getCredentialsFn):
1250 """Construct the handler."""
1251 super(Handler, self).__init__()
1252
1253 self._requests = []
1254 self._requestCondition = threading.Condition()
1255
1256 self.daemon = True
1257 self._config = config
1258 self._rpcClient = rpc.Client(getCredentialsFn)
1259 if config.rememberPassword:
1260 self._rpcClient.setCredentials(config.pilotID, config.password)
1261
1262 def register(self, callback, registrationData):
1263 """Enqueue a registration request."""
1264 self._addRequest(Register(self._rpcClient, callback, registrationData))
1265
1266 def login(self, callback, pilotID, password):
1267 """Enqueue a login request."""
1268 request = \
1269 LoginRPC(self._rpcClient, callback, pilotID, password) \
1270 if self._config.useRPC else Login(callback, pilotID, password)
1271
1272 self._addRequest(request)
1273
1274 def getEntryExamStatus(self, callback):
1275 """Get the entry exam status."""
1276 self._addRequest(GetEntryExamStatus(self._rpcClient, callback))
1277
1278 def getFleet(self, callback):
1279 """Enqueue a fleet retrieval request."""
1280 request = \
1281 GetFleetRPC(self._rpcClient, callback,) if self._config.useRPC \
1282 else GetFleet(callback)
1283 self._addRequest(request)
1284
1285 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
1286 """Update the status of the given plane."""
1287 request = \
1288 UpdatePlaneRPC(self._rpcClient, callback,
1289 tailNumber, status, gateNumber) \
1290 if self._config.useRPC \
1291 else UpdatePlane(callback, tailNumber, status, gateNumber)
1292 self._addRequest(request)
1293
1294 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
1295 """Get the NOTAMs for the given two airports."""
1296 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
1297
1298 def getMETARs(self, callback, airports):
1299 """Get the METARs for the given airports."""
1300 self._addRequest(GetMETARs(callback, airports))
1301
1302 def sendPIREP(self, callback, pirep):
1303 """Send the given PIREP."""
1304 request = \
1305 SendPIREPRPC(self._rpcClient, callback, pirep) if self._config.useRPC \
1306 else SendPIREP(callback, pirep)
1307 self._addRequest(request)
1308
1309 def sendACARS(self, callback, acars):
1310 """Send the given ACARS"""
1311 request = \
1312 SendACARSRPC(self._rpcClient, callback, acars) if self._config.useRPC \
1313 else SendACARS(callback, acars)
1314 self._addRequest(request)
1315
1316 def sendBugReport(self, callback, summary, description, email):
1317 """Send a bug report with the given data."""
1318 self._addRequest(SendBugReport(callback, summary, description, email))
1319
1320 def setCheckFlightPassed(self, callback, aircraftType):
1321 """Mark the check flight as passed."""
1322 self._addRequest(SetCheckFlightPassed(self._rpcClient,
1323 callback, aircraftType))
1324
1325 def run(self):
1326 """Process the requests."""
1327 while True:
1328 with self._requestCondition:
1329 while not self._requests:
1330 self._requestCondition.wait()
1331 request = self._requests[0]
1332 del self._requests[0]
1333
1334 request.perform()
1335
1336 def _addRequest(self, request):
1337 """Add the given request to the queue."""
1338 with self._requestCondition:
1339 self._requests.append(request)
1340 self._requestCondition.notify()
1341
1342#------------------------------------------------------------------------------
1343
1344if __name__ == "__main__":
1345 import time
1346
1347 def callback(returned, result):
1348 print returned, unicode(result)
1349
1350 handler = Handler()
1351 handler.start()
1352
1353 #handler.login(callback, "P096", "V5fwj")
1354 #handler.getFleet(callback)
1355 # Plane: HA-LEG home (gate 67)
1356 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
1357 #time.sleep(3)
1358 #handler.getFleet(callback)
1359 #time.sleep(3)
1360
1361 #handler.getNOTAMs(callback, "LHBP", "EPWA")
1362 #handler.getMETARs(callback, ["LHBP", "EPWA"])
1363 #time.sleep(5)
1364
1365 handler.updatePlane(callback, "HA-LON", const.PLANE_AWAY, "")
1366 time.sleep(3)
1367
1368#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.