source: src/mlx/web.py@ 801:bb5f4fee2370

Last change on this file since 801:bb5f4fee2370 was 790:e69a08004f40, checked in by István Váradi <ivaradi@…>, 8 years ago

Added support for the Boeing 737-200 aircraft type (weight data is still missing, re #302)

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