source: src/mlx/web.py@ 759:cf8bdfef70dd

Last change on this file since 759:cf8bdfef70dd was 756:673a9809df08, checked in by István Váradi <ivaradi@…>, 9 years ago

Implemented the basic GUI logic of the registration (re #285)

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