source: src/mlx/web.py@ 760:733c148959e9

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

The rank is returned when logging in, and in case of a student, we jump to the student page

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