source: src/mlx/web.py@ 742:464227881300

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

Extracted some common classes (re #283).

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