source: src/mlx/web.py@ 764:8fd245311db5

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

The entry exam is called in the browser when requested and the status is displayed in text as well (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.entryExamLink = reply[1]
742 result.checkFlightStatus = reply[2]
743
744 return result
745
746#------------------------------------------------------------------------------
747
748class GetEntryExamStatus(RPCRequest):
749 """A request to get the entry exam status."""
750 def __init__(self, client, callback):
751 """Construct the request."""
752 super(GetEntryExamStatus, self).__init__(client, callback)
753
754 def run(self):
755 """Perform the query."""
756 result = Result()
757
758 reply = self._client.getEntryExamStatus()
759
760 result.entryExamPassed = reply[0]
761 result.entryExamLink = reply[1]
762 result.checkFlightStatus = reply[2]
763
764 return result
765
766#------------------------------------------------------------------------------
767
768class GetFleet(Request):
769 """Request to get the fleet from the website."""
770
771 def __init__(self, callback):
772 """Construct the fleet request."""
773 super(GetFleet, self).__init__(callback)
774
775 def run(self):
776 """Perform the login request."""
777 url = MAVA_BASE_URL + "/onlinegates_get.php"
778
779 f = urllib2.urlopen(url, timeout = 10.0)
780 result = Result()
781 result.fleet = Fleet(f)
782 f.close()
783
784 return result
785
786#------------------------------------------------------------------------------
787
788class GetFleetRPC(RPCRequest):
789 """Request to get the fleet from the website using RPC."""
790 def __init__(self, client, callback):
791 """Construct the request with the given client and callback function."""
792 super(GetFleetRPC, self).__init__(client, callback)
793
794 def run(self):
795 """Perform the login request."""
796 result = Result()
797
798 result.fleet = self._client.getFleet()
799
800 return result
801
802#------------------------------------------------------------------------------
803
804class UpdatePlane(Request):
805 """Update the status of one of the planes in the fleet."""
806 def __init__(self, callback, tailNumber, status, gateNumber = None):
807 """Construct the request."""
808 super(UpdatePlane, self).__init__(callback)
809 self._tailNumber = tailNumber
810 self._status = status
811 self._gateNumber = gateNumber
812
813 def run(self):
814 """Perform the plane update."""
815 url = MAVA_BASE_URL + "/onlinegates_set.php"
816
817 status = Plane.status2str(self._status)
818
819 gateNumber = self._gateNumber if self._gateNumber else ""
820
821 data = urllib.urlencode([("lajstrom", self._tailNumber),
822 ("status", status),
823 ("kapu", gateNumber)])
824
825 f = urllib2.urlopen(url, data, timeout = 10.0)
826 line = readline(f)
827
828 result = Result()
829 result.success = line == "OK"
830
831 return result
832
833#------------------------------------------------------------------------------
834
835class UpdatePlaneRPC(RPCRequest):
836 """RPC request to update the status and the position of a plane in the
837 fleet."""
838 def __init__(self, client, callback, tailNumber, status, gateNumber = None):
839 """Construct the request."""
840 super(UpdatePlaneRPC, self).__init__(client, callback)
841 self._tailNumber = tailNumber
842 self._status = status
843 self._gateNumber = gateNumber
844
845 def run(self):
846 """Perform the plane update."""
847 self._client.updatePlane(self._tailNumber, self._status, self._gateNumber)
848
849 # Otherwise an exception is thrown
850 result = Result()
851 result.success = True
852
853 return result
854
855#------------------------------------------------------------------------------
856
857class GetNOTAMs(Request):
858 """Get the NOTAMs from EURoutePro and select the ones we are interested
859 in."""
860 def __init__(self, callback, departureICAO, arrivalICAO):
861 """Construct the request for the given airports."""
862 super(GetNOTAMs, self).__init__(callback)
863 self._departureICAO = departureICAO
864 self._arrivalICAO = arrivalICAO
865
866 def run(self):
867 """Perform the retrieval of the NOTAMs."""
868 departureNOTAMs = self.getPilotsWebNOTAMs(self._departureICAO)
869 arrivalNOTAMs = self.getPilotsWebNOTAMs(self._arrivalICAO)
870
871 icaos = []
872 if not departureNOTAMs: icaos.append(self._departureICAO)
873 if not arrivalNOTAMs: icaos.append(self._arrivalICAO)
874
875 if icaos:
876 xmlParser = xml.sax.make_parser()
877 notamHandler = NOTAMHandler(icaos)
878 xmlParser.setContentHandler(notamHandler)
879
880 url = "http://notams.euroutepro.com/notams.xml"
881
882 f = urllib2.urlopen(url, timeout = 10.0)
883 try:
884 xmlParser.parse(f)
885 finally:
886 f.close()
887
888 for icao in icaos:
889 if icao==self._departureICAO:
890 departureNOTAMs = notamHandler.get(icao)
891 else:
892 arrivalNOTAMs = notamHandler.get(icao)
893
894 result = Result()
895 result.departureNOTAMs = departureNOTAMs
896 result.arrivalNOTAMs = arrivalNOTAMs
897
898 return result
899
900 def getPilotsWebNOTAMs(self, icao):
901 """Try to get the NOTAMs from FAA's PilotsWeb site for the given ICAO
902 code.
903
904 Returns a list of PilotsWEBNOTAM objects, or None in case of an error."""
905 try:
906 parser = PilotsWebNOTAMsParser()
907
908 url = "https://pilotweb.nas.faa.gov/PilotWeb/notamRetrievalByICAOAction.do?method=displayByICAOs&formatType=ICAO&retrieveLocId=%s&reportType=RAW&actionType=notamRetrievalByICAOs" % \
909 (icao.upper(),)
910
911 f = urllib2.urlopen(url, timeout = 10.0)
912 try:
913 data = f.read(16384)
914 while data:
915 parser.feed(data)
916 data = f.read(16384)
917 finally:
918 f.close()
919
920 return parser.getNOTAMs()
921
922 except Exception, e:
923 traceback.print_exc()
924 print "mlx.web.GetNOTAMs.getPilotsWebNOTAMs: failed to get NOTAMs for '%s': %s" % \
925 (icao, str(e))
926 return None
927
928#------------------------------------------------------------------------------
929
930class GetMETARs(Request):
931 """Get the METARs from the NOAA website for certain airport ICAOs."""
932
933 def __init__(self, callback, airports):
934 """Construct the request for the given airports."""
935 super(GetMETARs, self).__init__(callback)
936 self._airports = airports
937
938 def run(self):
939 """Perform the retrieval opf the METARs."""
940 url = "http://www.aviationweather.gov/adds/dataserver_current/httpparam?"
941 data = urllib.urlencode([ ("dataSource" , "metars"),
942 ("requestType", "retrieve"),
943 ("format", "csv"),
944 ("stationString", " ".join(self._airports)),
945 ("hoursBeforeNow", "24"),
946 ("mostRecentForEachStation", "constraint")])
947 url += data
948 f = urllib2.urlopen(url, timeout = 10.0)
949 try:
950 result = Result()
951 result.metars = {}
952 for line in iter(f.readline, ""):
953 if len(line)>5 and line[4]==' ':
954 icao = line[0:4]
955 if icao in self._airports:
956 result.metars[icao] = line.strip().split(",")[0]
957 finally:
958 f.close()
959
960 return result
961
962#------------------------------------------------------------------------------
963
964class SendPIREP(Request):
965 """A request to send a PIREP to the MAVA website."""
966 _latin2Encoder = codecs.getencoder("iso-8859-2")
967
968 def __init__(self, callback, pirep):
969 """Construct the sending of the PIREP."""
970 super(SendPIREP, self).__init__(callback)
971 self._pirep = pirep
972
973 def run(self):
974 """Perform the sending of the PIREP."""
975 url = MAVA_BASE_URL + "/malevacars.php"
976
977 pirep = self._pirep
978
979 data = {}
980 data["acarsdata"] = SendPIREP._latin2Encoder(pirep.getACARSText())[0]
981
982 bookedFlight = pirep.bookedFlight
983 data["foglalas_id"] = bookedFlight.id
984 data["repdate"] = bookedFlight.departureTime.date().strftime("%Y-%m-%d")
985 data["fltnum"] = bookedFlight.callsign
986 data["depap"] = bookedFlight.departureICAO
987 data["arrap"] = bookedFlight.arrivalICAO
988 data["pass"] = str(pirep.numPassengers)
989 data["crew"] = str(pirep.numCrew)
990 data["cargo"] = str(pirep.cargoWeight)
991 data["bag"] = str(pirep.bagWeight)
992 data["mail"] = str(pirep.mailWeight)
993
994 data["flttype"] = pirep.flightTypeText
995 data["onoff"] = "1" if pirep.online else "0"
996 data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
997 data["bt_arr"] = util.getTimestampString(pirep.blockTimeEnd)
998 data["bt_dur"] = util.getTimeIntervalString(pirep.blockTimeEnd -
999 pirep.blockTimeStart)
1000 data["ft_dep"] = util.getTimestampString(pirep.flightTimeStart)
1001 data["ft_arr"] = util.getTimestampString(pirep.flightTimeEnd)
1002 data["ft_dur"] = util.getTimeIntervalString(pirep.flightTimeEnd -
1003 pirep.flightTimeStart)
1004 data["timecomm"] = pirep.getTimeComment()
1005 data["fuel"] = "%.2f" % (pirep.fuelUsed,)
1006 data["dep_rwy"] = pirep.departureRunway
1007 data["arr_rwy"] = pirep.arrivalRunway
1008 data["wea_dep"] = pirep.departureMETAR
1009 data["wea_arr"] = pirep.arrivalMETAR
1010 data["alt"] = "FL%.0f" % (pirep.filedCruiseAltitude/100.0,)
1011 if pirep.filedCruiseAltitude!=pirep.cruiseAltitude:
1012 data["mod_alt"] = "FL%.0f" % (pirep.cruiseAltitude/100.0,)
1013 else:
1014 data["mod_alt"] = ""
1015 data["sid"] = pirep.sid
1016 data["navroute"] = pirep.route
1017 data["star"] = pirep.getSTAR()
1018 data["aprtype"] = pirep.approachType
1019 data["diff"] = "2"
1020 data["comment"] = SendPIREP._latin2Encoder(pirep.comments)[0]
1021 data["flightdefect"] = SendPIREP._latin2Encoder(pirep.flightDefects)[0]
1022 data["kritika"] = pirep.getRatingText()
1023 data["flightrating"] = "%.1f" % (max(0.0, pirep.rating),)
1024 data["distance"] = "%.3f" % (pirep.flownDistance,)
1025 data["insdate"] = datetime.date.today().strftime("%Y-%m-%d")
1026
1027 postData = urllib.urlencode(data)
1028 f = urllib2.urlopen(url, postData, timeout = 10.0)
1029 try:
1030 result = Result()
1031 line = f.readline().strip()
1032 print "PIREP result from website:", line
1033 result.success = line=="OK"
1034 result.alreadyFlown = line=="MARVOLT"
1035 result.notAvailable = line=="NOMORE"
1036 finally:
1037 f.close()
1038
1039 return result
1040#------------------------------------------------------------------------------
1041
1042class SendPIREPRPC(RPCRequest):
1043 """A request to send a PIREP to the MAVA website via the RPC interface."""
1044
1045 def __init__(self, client, callback, pirep):
1046 """Construct the sending of the PIREP."""
1047 super(SendPIREPRPC, self).__init__(client, callback)
1048 self._pirep = pirep
1049
1050 def run(self):
1051 """Perform the sending of the PIREP."""
1052 pirep = self._pirep
1053 resultCode = self._client.addPIREP(pirep.bookedFlight.id, pirep)
1054
1055 result = Result()
1056 result.success = resultCode==rpc.Client.RESULT_OK
1057 result.alreadyFlown = resultCode==rpc.Client.RESULT_FLIGHT_ALREADY_REPORTED
1058 result.notAvailable = resultCode==rpc.Client.RESULT_FLIGHT_NOT_EXISTS
1059
1060 return result
1061
1062#------------------------------------------------------------------------------
1063
1064class SendACARS(Request):
1065 """A request to send an ACARS to the MAVA website."""
1066 _latin2Encoder = codecs.getencoder("iso-8859-2")
1067
1068 def __init__(self, callback, acars):
1069 """Construct the request for the given PIREP."""
1070 super(SendACARS, self).__init__(callback)
1071 self._acars = acars
1072
1073 def run(self):
1074 """Perform the sending of the ACARS."""
1075 print "Sending the online ACARS"
1076
1077 url = MAVA_BASE_URL + "/acars2/acarsonline.php"
1078
1079 acars = self._acars
1080 bookedFlight = acars.bookedFlight
1081
1082 data = {}
1083 data["pid"] = acars.pid
1084 data["pilot"] = SendACARS._latin2Encoder(acars.pilotName)[0]
1085
1086 data["pass"] = str(bookedFlight.numPassengers)
1087 data["callsign"] = bookedFlight.callsign
1088 data["airplane"] = bookedFlight.aircraftTypeName
1089 data["from"] = bookedFlight.departureICAO
1090 data["to"] = bookedFlight.arrivalICAO
1091 data["lajstrom"] = bookedFlight.tailNumber
1092
1093 data["block_time"] = acars.getBlockTimeText()
1094 data["longitude"] = str(acars.state.longitude)
1095 data["latitude"] = str(acars.state.latitude)
1096 data["altitude"] = str(acars.state.altitude)
1097 data["speed"] = str(acars.state.groundSpeed)
1098
1099 data["event"] = acars.getEventText()
1100
1101 f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
1102 try:
1103 result = Result()
1104 finally:
1105 f.close()
1106
1107 return result
1108
1109#------------------------------------------------------------------------------
1110
1111class SendACARSRPC(RPCRequest):
1112 """A request to send an ACARS to the MAVA website via JSON-RPC."""
1113 def __init__(self, client, callback, acars):
1114 """Construct the request for the given PIREP."""
1115 super(SendACARSRPC, self).__init__(client, callback)
1116 self._acars = acars
1117
1118 def run(self):
1119 """Perform the sending of the ACARS."""
1120 print "Sending the online ACARS via JSON-RPC"
1121
1122 self._client.updateOnlineACARS(self._acars)
1123 return Result()
1124
1125#------------------------------------------------------------------------------
1126
1127class SendBugReport(Request):
1128 """A request to send a bug report to the project homepage."""
1129 _latin2Encoder = codecs.getencoder("iso-8859-2")
1130
1131 def __init__(self, callback, summary, description, email):
1132 """Construct the request for the given bug report."""
1133 super(SendBugReport, self).__init__(callback)
1134 self._summary = summary
1135 self._description = description
1136 self._email = email
1137
1138 def run(self):
1139 """Perform the sending of the bug report."""
1140 serverProxy = xmlrpclib.ServerProxy("http://mlx.varadiistvan.hu/rpc")
1141
1142 result = Result()
1143 result.success = False
1144
1145 attributes = {}
1146 if self._email:
1147 attributes["reporter"] = self._email
1148
1149 result.ticketID = serverProxy.ticket.create(self._summary, self._description,
1150 attributes, True)
1151 print "Created ticket with ID:", result.ticketID
1152 result.success = True
1153
1154 return result
1155
1156#------------------------------------------------------------------------------
1157
1158class Handler(threading.Thread):
1159 """The handler for the web services.
1160
1161 It can process one request at a time. The results are passed to a callback
1162 function."""
1163 def __init__(self, config, getCredentialsFn):
1164 """Construct the handler."""
1165 super(Handler, self).__init__()
1166
1167 self._requests = []
1168 self._requestCondition = threading.Condition()
1169
1170 self.daemon = True
1171 self._config = config
1172 self._rpcClient = rpc.Client(getCredentialsFn)
1173 if config.rememberPassword:
1174 self._rpcClient.setCredentials(config.pilotID, config.password)
1175
1176 def register(self, callback, registrationData):
1177 """Enqueue a registration request."""
1178 self._addRequest(Register(self._rpcClient, callback, registrationData))
1179
1180 def login(self, callback, pilotID, password):
1181 """Enqueue a login request."""
1182 request = \
1183 LoginRPC(self._rpcClient, callback, pilotID, password) \
1184 if self._config.useRPC else Login(callback, pilotID, password)
1185
1186 self._addRequest(request)
1187
1188 def getEntryExamStatus(self, callback):
1189 """Get the entry exam status."""
1190 self._addRequest(GetEntryExamStatus(self._rpcClient, callback))
1191
1192 def getFleet(self, callback):
1193 """Enqueue a fleet retrieval request."""
1194 request = \
1195 GetFleetRPC(self._rpcClient, callback,) if self._config.useRPC \
1196 else GetFleet(callback)
1197 self._addRequest(request)
1198
1199 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
1200 """Update the status of the given plane."""
1201 request = \
1202 UpdatePlaneRPC(self._rpcClient, callback,
1203 tailNumber, status, gateNumber) \
1204 if self._config.useRPC \
1205 else UpdatePlane(callback, tailNumber, status, gateNumber)
1206 self._addRequest(request)
1207
1208 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
1209 """Get the NOTAMs for the given two airports."""
1210 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
1211
1212 def getMETARs(self, callback, airports):
1213 """Get the METARs for the given airports."""
1214 self._addRequest(GetMETARs(callback, airports))
1215
1216 def sendPIREP(self, callback, pirep):
1217 """Send the given PIREP."""
1218 request = \
1219 SendPIREPRPC(self._rpcClient, callback, pirep) if self._config.useRPC \
1220 else SendPIREP(callback, pirep)
1221 self._addRequest(request)
1222
1223 def sendACARS(self, callback, acars):
1224 """Send the given ACARS"""
1225 request = \
1226 SendACARSRPC(self._rpcClient, callback, acars) if self._config.useRPC \
1227 else SendACARS(callback, acars)
1228 self._addRequest(request)
1229
1230 def sendBugReport(self, callback, summary, description, email):
1231 """Send a bug report with the given data."""
1232 self._addRequest(SendBugReport(callback, summary, description, email))
1233
1234 def run(self):
1235 """Process the requests."""
1236 while True:
1237 with self._requestCondition:
1238 while not self._requests:
1239 self._requestCondition.wait()
1240 request = self._requests[0]
1241 del self._requests[0]
1242
1243 request.perform()
1244
1245 def _addRequest(self, request):
1246 """Add the given request to the queue."""
1247 with self._requestCondition:
1248 self._requests.append(request)
1249 self._requestCondition.notify()
1250
1251#------------------------------------------------------------------------------
1252
1253if __name__ == "__main__":
1254 import time
1255
1256 def callback(returned, result):
1257 print returned, unicode(result)
1258
1259 handler = Handler()
1260 handler.start()
1261
1262 #handler.login(callback, "P096", "V5fwj")
1263 #handler.getFleet(callback)
1264 # Plane: HA-LEG home (gate 67)
1265 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
1266 #time.sleep(3)
1267 #handler.getFleet(callback)
1268 #time.sleep(3)
1269
1270 #handler.getNOTAMs(callback, "LHBP", "EPWA")
1271 #handler.getMETARs(callback, ["LHBP", "EPWA"])
1272 #time.sleep(5)
1273
1274 handler.updatePlane(callback, "HA-LON", const.PLANE_AWAY, "")
1275 time.sleep(3)
1276
1277#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.