source: src/mlx/web.py@ 762:c49eee53b7c5

Last change on this file since 762:c49eee53b7c5 was 761:fd39e894ffbe, checked in by István Váradi <ivaradi@…>, 9 years ago

The entry exam status can be and is queried (re #285).

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