source: src/mlx/web.py@ 771:76aae72ccaba

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

Fixed the handling of the login information after registering (re #285)

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