source: src/mlx/web.py@ 769:d10b450fff75

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

The check flight can also be performed and the registration logic is hopefully complete (re #285)

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