source: src/mlx/web.py@ 128:e14fcd9d9215

Last change on this file since 128:e14fcd9d9215 was 118:aaa1bc00131b, checked in by István Váradi <ivaradi@…>, 13 years ago

Implemented the gates tab

File size: 23.0 KB
Line 
1# Interface towards the websites used
2
3#------------------------------------------------------------------------------
4
5import const
6import util
7
8import threading
9import sys
10import urllib
11import urllib2
12import hashlib
13import time
14import datetime
15import codecs
16import traceback
17import xml.sax
18
19#---------------------------------------------------------------------------------------
20
21def readline(f):
22 """Read a line from the given file.
23
24 The line is stripped and empty lines are discarded."""
25 while True:
26 line = f.readline()
27 if not line: return ""
28 line = line.strip()
29 if line:
30 return line
31
32#---------------------------------------------------------------------------------------
33
34class BookedFlight(object):
35 """A flight that was booked."""
36 TYPECODE2TYPE = { "736" : const.AIRCRAFT_B736,
37 "73G" : const.AIRCRAFT_B737,
38 "738" : const.AIRCRAFT_B738,
39 "733" : const.AIRCRAFT_B733,
40 "734" : const.AIRCRAFT_B734,
41 "735" : const.AIRCRAFT_B735,
42 "DH4" : const.AIRCRAFT_DH8D,
43 "762" : const.AIRCRAFT_B762,
44 "763" : const.AIRCRAFT_B763,
45 "CR2" : const.AIRCRAFT_CRJ2,
46 "F70" : const.AIRCRAFT_F70,
47 "LI2" : const.AIRCRAFT_DC3,
48 "TU3" : const.AIRCRAFT_T134,
49 "TU5" : const.AIRCRAFT_T154,
50 "YK4" : const.AIRCRAFT_YK40 }
51
52 def __init__(self, id, f):
53 """Construct a booked flight with the given ID.
54
55 The rest of the data is read from the given file."""
56
57 self.id = id
58 self.callsign = readline(f)
59
60 date = readline(f)
61
62 self.departureICAO = readline(f)
63 self.arrivalICAO = readline(f)
64
65 self._readAircraftType(f)
66 self.tailNumber = readline(f)
67 self.numPassengers = int(readline(f))
68 self.numCrew = int(readline(f))
69 self.bagWeight = int(readline(f))
70 self.cargoWeight = int(readline(f))
71 self.mailWeight = int(readline(f))
72 self.route = readline(f)
73
74 departureTime = readline(f)
75 self.departureTime = datetime.datetime.strptime(date + " " + departureTime,
76 "%Y-%m-%d %H:%M:%S")
77
78 arrivalTime = readline(f)
79 self.arrivalTime = datetime.datetime.strptime(date + " " + arrivalTime,
80 "%Y-%m-%d %H:%M:%S")
81
82 if not readline(f)==".NEXT.":
83 raise Exception("Invalid line in flight data")
84
85 def _readAircraftType(self, f):
86 """Read the aircraft type from the given file."""
87 line = readline(f)
88 typeCode = line[:3]
89 if typeCode in self.TYPECODE2TYPE:
90 self.aircraftType = self.TYPECODE2TYPE[typeCode]
91 else:
92 raise Exception("Invalid aircraft type code: '" + typeCode + "'")
93
94 def __repr__(self):
95 """Get a representation of the flight."""
96 s = "<Flight: %s-%s, %s, %s-%s," % (self.departureICAO,
97 self.arrivalICAO,
98 self.route,
99 self.departureTime, self.arrivalTime)
100 s += " %d %s," % (self.aircraftType, self.tailNumber)
101 s += " pax=%d, crew=%d, bag=%d, cargo=%d, mail=%d" % \
102 (self.numPassengers, self.numCrew,
103 self.bagWeight, self.cargoWeight, self.mailWeight)
104 s += ">"
105 return s
106
107#------------------------------------------------------------------------------
108
109class Plane(object):
110 """Information about an airplane in the fleet."""
111 def __init__(self, s):
112 """Build a plane info based on the given string.
113
114 The string consists of three, space-separated fields.
115 The first field is the tail number, the second field is the gate
116 number, the third field is the plane's status as a character."""
117 try:
118 words = s.split(" ")
119 tailNumber = words[0]
120 self.tailNumber = tailNumber
121
122 status = words[2] if len(words)>2 else None
123 self.status = const.PLANE_HOME if status=="H" else \
124 const.PLANE_AWAY if status=="A" else \
125 const.PLANE_PARKING if status=="P" else \
126 const.PLANE_UNKNOWN
127
128 gateNumber = words[1] if len(words)>1 else ""
129 self.gateNumber = gateNumber if gateNumber else None
130
131 except:
132 print >> sys.stderr, "Plane string is invalid: '" + s + "'"
133 self.tailNumber = None
134
135 def __repr__(self):
136 """Get the representation of the plane object."""
137 s = "<Plane: %s %s" % (self.tailNumber,
138 "home" if self.status==const.PLANE_HOME else \
139 "away" if self.status==const.PLANE_AWAY else \
140 "parking" if self.status==const.PLANE_PARKING \
141 else "unknown")
142 if self.gateNumber is not None:
143 s += " (gate " + self.gateNumber + ")"
144 s += ">"
145 return s
146
147
148#------------------------------------------------------------------------------
149
150class Fleet(object):
151 """Information about the whole fleet."""
152 def __init__(self, f):
153 """Construct the fleet information by reading the given file object."""
154 self._planes = {}
155 while True:
156 line = readline(f)
157 if not line or line == "#END": break
158
159 plane = Plane(line)
160 if plane.tailNumber is not None:
161 self._planes[plane.tailNumber] = plane
162
163 def isGateConflicting(self, plane):
164 """Check if the gate of the given plane conflicts with another plane's
165 position."""
166 for p in self._planes.itervalues():
167 if p.tailNumber!=plane.tailNumber and \
168 p.status==const.PLANE_HOME and \
169 p.gateNumber==plane.gateNumber:
170 return True
171
172 return False
173
174 def getOccupiedGateNumbers(self):
175 """Get a set containing the numbers of the gates occupied by planes."""
176 gateNumbers = set()
177 for p in self._planes.itervalues():
178 if p.status==const.PLANE_HOME and p.gateNumber:
179 gateNumbers.add(p.gateNumber)
180 return gateNumbers
181
182 def __iter__(self):
183 """Get an iterator over the planes."""
184 for plane in self._planes.itervalues():
185 yield plane
186
187 def __getitem__(self, tailNumber):
188 """Get the plane with the given tail number.
189
190 If the plane is not in the fleet, None is returned."""
191 return self._planes[tailNumber] if tailNumber in self._planes else None
192
193 def __repr__(self):
194 """Get the representation of the fleet object."""
195 return self._planes.__repr__()
196
197#------------------------------------------------------------------------------
198
199class NOTAM(object):
200 """A NOTAM for an airport."""
201 def __init__(self, begin, notice, end = None, permanent = False,
202 repeatCycle = None):
203 """Construct the NOTAM."""
204 self.begin = begin
205 self.notice = notice
206 self.end = end
207 self.permanent = permanent
208 self.repeatCycle = None
209
210 def __repr__(self):
211 """Get the representation of the NOTAM."""
212 s = "<NOTAM " + str(self.begin)
213 if self.end:
214 s += " - " + str(self.end)
215 elif self.permanent:
216 s += " - PERMANENT"
217 if self.repeatCycle:
218 s += " (" + self.repeatCycle + ")"
219 s += ": " + self.notice
220 s += ">"
221 return s
222
223#------------------------------------------------------------------------------
224
225class NOTAMHandler(xml.sax.handler.ContentHandler):
226 """A handler for the NOTAM database."""
227 def __init__(self, airportICAOs):
228 """Construct the handler for the airports with the given ICAO code."""
229 self._notams = {}
230 for icao in airportICAOs:
231 self._notams[icao] = []
232
233 def startElement(self, name, attrs):
234 """Start an element."""
235 if name!="notam" or \
236 "A" not in attrs or not attrs["A"] or \
237 "B" not in attrs or not attrs["B"] or \
238 "E" not in attrs or not attrs["E"]:
239 return
240
241 icao = attrs["A"]
242 if icao not in self._notams:
243 return
244
245 begin = datetime.datetime.strptime(attrs["B"], "%Y-%m-%d %H:%M:%S")
246
247 c = attrs["C"] if "C" in attrs else None
248 end = datetime.datetime.strptime(c, "%Y-%m-%d %H:%M:%S") if c else None
249
250 permanent = attrs["C_flag"]=="PERM" if "C_flag" in attrs else False
251
252 repeatCycle = attrs["D"] if "D" in attrs else None
253
254 self._notams[icao].append(NOTAM(begin, attrs["E"], end = end,
255 permanent = permanent,
256 repeatCycle = repeatCycle))
257
258 def get(self, icao):
259 """Get the NOTAMs for the given ICAO code."""
260 return self._notams[icao] if icao in self._notams else []
261
262#------------------------------------------------------------------------------
263
264class Result(object):
265 """A result object.
266
267 An instance of this filled with the appropriate data is passed to the
268 callback function on each request."""
269
270 def __repr__(self):
271 """Get a representation of the result."""
272 s = "<Result:"
273 for (key, value) in self.__dict__.iteritems():
274 s += " " + key + "=" + unicode(value)
275 s += ">"
276 return s
277
278#------------------------------------------------------------------------------
279
280class Request(object):
281 """Base class for requests.
282
283 It handles any exceptions and the calling of the callback.
284
285 If an exception occurs during processing, the callback is called with
286 the two parameters: a boolean value of False, and the exception object.
287
288 If no exception occurs, the callback is called with True and the return
289 value of the run() function.
290
291 If the callback function throws an exception, that is caught and logged
292 to the debug log."""
293 def __init__(self, callback):
294 """Construct the request."""
295 self._callback = callback
296
297 def perform(self):
298 """Perform the request.
299
300 The object's run() function is called. If it throws an exception,
301 the callback is called with False, and the exception. Otherwise the
302 callback is called with True and the return value of the run()
303 function. Any exceptions thrown by the callback are caught and
304 reported."""
305 try:
306 result = self.run()
307 returned = True
308 except Exception, e:
309 traceback.print_exc()
310 result = e
311 returned = False
312
313 try:
314 self._callback(returned, result)
315 except Exception, e:
316 print >> sys.stderr, "web.Handler.Request.perform: callback throwed an exception: " + str(e)
317 traceback.print_exc()
318
319#------------------------------------------------------------------------------
320
321class Login(Request):
322 """A login request."""
323 iso88592decoder = codecs.getdecoder("iso-8859-2")
324
325 def __init__(self, callback, pilotID, password):
326 """Construct the login request with the given pilot ID and
327 password."""
328 super(Login, self).__init__(callback)
329
330 self._pilotID = pilotID
331 self._password = password
332
333 def run(self):
334 """Perform the login request."""
335 md5 = hashlib.md5()
336 md5.update(self._pilotID)
337 pilotID = md5.hexdigest()
338
339 md5 = hashlib.md5()
340 md5.update(self._password)
341 password = md5.hexdigest()
342
343 url = "http://www.virtualairlines.hu/leker2.php?pid=%s&psw=%s" % \
344 (pilotID, password)
345
346 result = Result()
347
348 f = urllib2.urlopen(url, timeout = 10.0)
349
350 status = readline(f)
351 result.loggedIn = status == ".OK."
352
353 if result.loggedIn:
354 result.pilotName = self.iso88592decoder(readline(f))[0]
355 result.exams = readline(f)
356 result.flights = []
357
358 while True:
359 line = readline(f)
360 if not line or line == "#ENDPIREP": break
361
362 flight = BookedFlight(line, f)
363 result.flights.append(flight)
364
365 result.flights.sort(cmp = lambda flight1, flight2:
366 cmp(flight1.departureTime,
367 flight2.departureTime))
368
369 f.close()
370
371 return result
372
373#------------------------------------------------------------------------------
374
375class GetFleet(Request):
376 """Request to get the fleet from the website."""
377
378 def __init__(self, callback):
379 """Construct the fleet request."""
380 super(GetFleet, self).__init__(callback)
381
382 def run(self):
383 """Perform the login request."""
384 url = "http://www.virtualairlines.hu/onlinegates_get.php"
385
386 f = urllib2.urlopen(url, timeout = 10.0)
387 result = Result()
388 result.fleet = Fleet(f)
389 f.close()
390
391 return result
392
393#------------------------------------------------------------------------------
394
395class UpdatePlane(Request):
396 """Update the status of one of the planes in the fleet."""
397 def __init__(self, callback, tailNumber, status, gateNumber = None):
398 """Construct the request."""
399 super(UpdatePlane, self).__init__(callback)
400 self._tailNumber = tailNumber
401 self._status = status
402 self._gateNumber = gateNumber
403
404 def run(self):
405 """Perform the plane update."""
406 url = "http://www.virtualairlines.hu/onlinegates_set.php"
407
408 status = "H" if self._status==const.PLANE_HOME else \
409 "A" if self._status==const.PLANE_AWAY else \
410 "P" if self._status==const.PLANE_PARKING else ""
411
412 gateNumber = self._gateNumber if self._gateNumber else ""
413
414 data = urllib.urlencode([("lajstrom", self._tailNumber),
415 ("status", status),
416 ("kapu", gateNumber)])
417
418 f = urllib2.urlopen(url, data, timeout = 10.0)
419 line = readline(f)
420
421 result = Result()
422 result.success = line == "OK"
423
424 return result
425
426#------------------------------------------------------------------------------
427
428class GetNOTAMs(Request):
429 """Get the NOTAMs from EURoutePro and select the ones we are interested
430 in."""
431 def __init__(self, callback, departureICAO, arrivalICAO):
432 """Construct the request for the given airports."""
433 super(GetNOTAMs, self).__init__(callback)
434 self._departureICAO = departureICAO
435 self._arrivalICAO = arrivalICAO
436
437 def run(self):
438 """Perform the retrieval of the NOTAMs."""
439 xmlParser = xml.sax.make_parser()
440 notamHandler = NOTAMHandler([self._departureICAO, self._arrivalICAO])
441 xmlParser.setContentHandler(notamHandler)
442
443 url = "http://notams.euroutepro.com/notams.xml"
444
445 f = urllib2.urlopen(url, timeout = 10.0)
446 try:
447 xmlParser.parse(f)
448 finally:
449 f.close()
450
451 result = Result()
452 result.departureNOTAMs = notamHandler.get(self._departureICAO)
453 result.arrivalNOTAMs = notamHandler.get(self._arrivalICAO)
454
455 return result
456
457#------------------------------------------------------------------------------
458
459class GetMETARs(Request):
460 """Get the METARs from the NOAA website for certain airport ICAOs."""
461
462 def __init__(self, callback, airports):
463 """Construct the request for the given airports."""
464 super(GetMETARs, self).__init__(callback)
465 self._airports = airports
466
467 def run(self):
468 """Perform the retrieval opf the METARs."""
469 url = "http://www.aviationweather.gov/adds/dataserver_current/httpparam?"
470 data = urllib.urlencode([ ("dataSource" , "metars"),
471 ("requestType", "retrieve"),
472 ("format", "csv"),
473 ("stationString", " ".join(self._airports)),
474 ("hoursBeforeNow", "24"),
475 ("mostRecentForEachStation", "constraint")])
476 url += data
477 f = urllib2.urlopen(url, timeout = 10.0)
478 try:
479 result = Result()
480 result.metars = {}
481 for line in iter(f.readline, ""):
482 if len(line)>5 and line[4]==' ':
483 icao = line[0:4]
484 if icao in self._airports:
485 result.metars[icao] = line.strip().split(",")[0]
486 finally:
487 f.close()
488
489 return result
490
491#------------------------------------------------------------------------------
492
493class SendPIREP(Request):
494 """A request to send a PIREP to the MAVA website."""
495 _flightTypes = { const.FLIGHTTYPE_SCHEDULED : "SCHEDULED",
496 const.FLIGHTTYPE_OLDTIMER : "OT",
497 const.FLIGHTTYPE_VIP : "VIP",
498 const.FLIGHTTYPE_CHARTER : "CHARTER" }
499
500 _latin2Encoder = codecs.getencoder("iso-8859-2")
501
502 def __init__(self, callback, pirep):
503 """Construct the request for the given PIREP."""
504 super(SendPIREP, self).__init__(callback)
505 self._pirep = pirep
506
507 def run(self):
508 """Perform the retrieval opf the METARs."""
509 url = "http://www.virtualairlines.hu/malevacars.php"
510
511 pirep = self._pirep
512
513 data = {}
514 data["acarsdata"] = pirep.getACARSText()
515
516 bookedFlight = pirep.bookedFlight
517 data["foglalas_id"] = bookedFlight.id
518 data["repdate"] = bookedFlight.departureTime.date().strftime("%Y-%m-%d")
519 data["fltnum"] = bookedFlight.callsign
520 data["depap"] = bookedFlight.departureICAO
521 data["arrap"] = bookedFlight.arrivalICAO
522 data["pass"] = str(bookedFlight.numPassengers)
523 data["crew"] = str(bookedFlight.numCrew)
524 data["cargo"] = str(pirep.cargoWeight)
525 data["bag"] = str(bookedFlight.bagWeight)
526 data["mail"] = str(bookedFlight.mailWeight)
527
528 data["flttype"] = SendPIREP._flightTypes[pirep.flightType]
529 data["onoff"] = "1" if pirep.online else "0"
530 data["bt_dep"] = util.getTimestampString(pirep.blockTimeStart)
531 data["bt_arr"] = util.getTimestampString(pirep.blockTimeEnd)
532 data["bt_dur"] = util.getTimeIntervalString(pirep.blockTimeEnd -
533 pirep.blockTimeStart)
534 data["ft_dep"] = util.getTimestampString(pirep.flightTimeStart)
535 data["ft_arr"] = util.getTimestampString(pirep.flightTimeEnd)
536 data["ft_dur"] = util.getTimeIntervalString(pirep.flightTimeEnd -
537 pirep.flightTimeStart)
538 data["timecomm"] = pirep.getTimeComment()
539 data["fuel"] = "%.0f" % (pirep.fuelUsed,)
540 data["dep_rwy"] = pirep.departureRunway
541 data["arr_rwy"] = pirep.arrivalRunway
542 data["wea_dep"] = pirep.departureMETAR
543 data["wea_arr"] = pirep.arrivalMETAR
544 data["alt"] = "FL%.0f" % (pirep.filedCruiseAltitude/100.0,)
545 if pirep.filedCruiseAltitude!=pirep.cruiseAltitude:
546 data["mod_alt"] = "FL%.0f" % (pirep.cruiseAltitude/100.0,)
547 else:
548 data["mod_alt"] = ""
549 data["sid"] = pirep.sid
550 data["navroute"] = pirep.route
551 data["star"] = pirep.getSTAR()
552 data["aprtype"] = pirep.approachType
553 data["diff"] = "2"
554 data["comment"] = SendPIREP._latin2Encoder(pirep.comments)[0]
555 data["flightdefect"] = SendPIREP._latin2Encoder(pirep.flightDefects)[0]
556 data["kritika"] = pirep.getRatingText()
557 data["flightrating"] = "%.1f" % (max(0.0, pirep.rating),)
558 data["distance"] = "%.3f" % (pirep.flownDistance,)
559 data["insdate"] = datetime.date.today().strftime("%Y-%m-%d")
560
561 f = urllib2.urlopen(url, urllib.urlencode(data), timeout = 10.0)
562 try:
563 result = Result()
564 line = f.readline().strip()
565 print "PIREP result from website:", line
566 result.success = line=="OK"
567 result.alreadyFlown = line=="MARVOLT"
568 result.notAvailable = line=="NOMORE"
569 finally:
570 f.close()
571
572 return result
573
574#------------------------------------------------------------------------------
575
576class Handler(threading.Thread):
577 """The handler for the web services.
578
579 It can process one request at a time. The results are passed to a callback
580 function."""
581 def __init__(self):
582 """Construct the handler."""
583 super(Handler, self).__init__()
584
585 self._requests = []
586 self._requestCondition = threading.Condition()
587
588 self.daemon = True
589
590 def login(self, callback, pilotID, password):
591 """Enqueue a login request."""
592 self._addRequest(Login(callback, pilotID, password))
593
594 def getFleet(self, callback):
595 """Enqueue a fleet retrieval request."""
596 self._addRequest(GetFleet(callback))
597
598 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
599 """Update the status of the given plane."""
600 self._addRequest(UpdatePlane(callback, tailNumber, status, gateNumber))
601
602 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
603 """Get the NOTAMs for the given two airports."""
604 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
605
606 def getMETARs(self, callback, airports):
607 """Get the METARs for the given airports."""
608 self._addRequest(GetMETARs(callback, airports))
609
610 def sendPIREP(self, callback, pirep):
611 """Send the given PIREP."""
612 self._addRequest(SendPIREP(callback, pirep))
613
614 def run(self):
615 """Process the requests."""
616 while True:
617 with self._requestCondition:
618 while not self._requests:
619 self._requestCondition.wait()
620 request = self._requests[0]
621 del self._requests[0]
622
623 request.perform()
624
625 def _addRequest(self, request):
626 """Add the given request to the queue."""
627 with self._requestCondition:
628 self._requests.append(request)
629 self._requestCondition.notify()
630
631#------------------------------------------------------------------------------
632
633if __name__ == "__main__":
634 import time
635
636 def callback(returned, result):
637 print returned, unicode(result)
638
639 handler = Handler()
640 handler.start()
641
642 #handler.login(callback, "P096", "V5fwj")
643 #handler.getFleet(callback)
644 # Plane: HA-LEG home (gate 67)
645 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
646 #time.sleep(3)
647 #handler.getFleet(callback)
648 #time.sleep(3)
649
650 #handler.getNOTAMs(callback, "LHBP", "EPWA")
651 handler.getMETARs(callback, ["LHBP", "EPWA"])
652 time.sleep(5)
653
654
655#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.