source: src/mlx/web.py@ 64:1764c7271057

Last change on this file since 64:1764c7271057 was 63:11d3a9b5f97b, checked in by István Váradi <ivaradi@…>, 13 years ago

Implemented the NOTAM query.

File size: 17.4 KB
Line 
1# Interface towards the websites used
2
3#------------------------------------------------------------------------------
4
5import const
6
7import threading
8import sys
9import urllib
10import urllib2
11import hashlib
12import datetime
13import codecs
14import xml.sax
15
16#---------------------------------------------------------------------------------------
17
18def readline(f):
19 """Read a line from the given file.
20
21 The line is stripped and empty lines are discarded."""
22 while True:
23 line = f.readline()
24 if not line: return ""
25 line = line.strip()
26 if line:
27 return line
28
29#---------------------------------------------------------------------------------------
30
31class BookedFlight(object):
32 """A flight that was booked."""
33 TYPECODE2TYPE = { "736" : const.AIRCRAFT_B736,
34 "73G" : const.AIRCRAFT_B737,
35 "738" : const.AIRCRAFT_B738,
36 "733" : const.AIRCRAFT_B733,
37 "734" : const.AIRCRAFT_B734,
38 "735" : const.AIRCRAFT_B735,
39 "DH4" : const.AIRCRAFT_DH8D,
40 "762" : const.AIRCRAFT_B762,
41 "763" : const.AIRCRAFT_B763,
42 "CR2" : const.AIRCRAFT_CRJ2,
43 "F70" : const.AIRCRAFT_F70,
44 "LI2" : const.AIRCRAFT_DC3,
45 "TU3" : const.AIRCRAFT_T134,
46 "TU5" : const.AIRCRAFT_T154,
47 "YK4" : const.AIRCRAFT_YK40 }
48
49 def __init__(self, id, f):
50 """Construct a booked flight with the given ID.
51
52 The rest of the data is read from the given file."""
53
54 self.id = id
55 self.callsign = readline(f)
56
57 date = readline(f)
58
59 self.departureICAO = readline(f)
60 self.arrivalICAO = readline(f)
61
62 self._readAircraftType(f)
63 self.tailNumber = readline(f)
64 self.numPassengers = int(readline(f))
65 self.numCrew = int(readline(f))
66 self.bagWeight = int(readline(f))
67 self.cargoWeight = int(readline(f))
68 self.mailWeight = int(readline(f))
69 self.route = readline(f)
70
71 departureTime = readline(f)
72 self.departureTime = datetime.datetime.strptime(date + " " + departureTime,
73 "%Y-%m-%d %H:%M:%S")
74
75 arrivalTime = readline(f)
76 self.arrivalTime = datetime.datetime.strptime(date + " " + arrivalTime,
77 "%Y-%m-%d %H:%M:%S")
78
79 if not readline(f)==".NEXT.":
80 raise Exception("Invalid line in flight data")
81
82 def _readAircraftType(self, f):
83 """Read the aircraft type from the given file."""
84 line = readline(f)
85 typeCode = line[:3]
86 if typeCode in self.TYPECODE2TYPE:
87 self.aircraftType = self.TYPECODE2TYPE[typeCode]
88 else:
89 raise Exception("Invalid aircraft type code: '" + typeCode + "'")
90
91 def __repr__(self):
92 """Get a representation of the flight."""
93 s = "<Flight: %s-%s, %s, %s-%s," % (self.departureICAO,
94 self.arrivalICAO,
95 self.route,
96 self.departureTime, self.arrivalTime)
97 s += " %d %s," % (self.aircraftType, self.tailNumber)
98 s += " pax=%d, crew=%d, bag=%d, cargo=%d, mail=%d" % \
99 (self.numPassengers, self.numCrew,
100 self.bagWeight, self.cargoWeight, self.mailWeight)
101 s += ">"
102 return s
103
104#------------------------------------------------------------------------------
105
106class Plane(object):
107 """Information about an airplane in the fleet."""
108 def __init__(self, s):
109 """Build a plane info based on the given string.
110
111 The string consists of three, space-separated fields.
112 The first field is the tail number, the second field is the gate
113 number, the third field is the plane's status as a character."""
114 try:
115 words = s.split(" ")
116 tailNumber = words[0]
117 self.tailNumber = tailNumber
118
119 status = words[2] if len(words)>2 else None
120 self.status = const.PLANE_HOME if status=="H" else \
121 const.PLANE_AWAY if status=="A" else \
122 const.PLANE_PARKING if status=="P" else \
123 const.PLANE_UNKNOWN
124
125 gateNumber = words[1] if len(words)>1 else ""
126 self.gateNumber = gateNumber if gateNumber else None
127
128 except:
129 print >> sys.stderr, "Plane string is invalid: '" + s + "'"
130 self.tailNumber = None
131
132 def __repr__(self):
133 """Get the representation of the plane object."""
134 s = "<Plane: %s %s" % (self.tailNumber,
135 "home" if self.status==const.PLANE_HOME else \
136 "away" if self.status==const.PLANE_AWAY else \
137 "parking" if self.status==const.PLANE_PARKING \
138 else "unknown")
139 if self.gateNumber is not None:
140 s += " (gate " + self.gateNumber + ")"
141 s += ">"
142 return s
143
144
145#------------------------------------------------------------------------------
146
147class Fleet(object):
148 """Information about the whole fleet."""
149 def __init__(self, f):
150 """Construct the fleet information by reading the given file object."""
151 self._planes = {}
152 while True:
153 line = readline(f)
154 if not line or line == "#END": break
155
156 plane = Plane(line)
157 if plane.tailNumber is not None:
158 self._planes[plane.tailNumber] = plane
159
160 def isGateConflicting(self, plane):
161 """Check if the gate of the given plane conflicts with another plane's
162 position."""
163 for p in self._planes.itervalues():
164 if p.tailNumber!=plane.tailNumber and \
165 p.status==const.PLANE_HOME and \
166 p.gateNumber==plane.gateNumber:
167 return True
168
169 return False
170
171 def getOccupiedGateNumbers(self):
172 """Get a set containing the numbers of the gates occupied by planes."""
173 gateNumbers = set()
174 for p in self._planes.itervalues():
175 if p.status==const.PLANE_HOME and p.gateNumber:
176 gateNumbers.add(p.gateNumber)
177 return gateNumbers
178
179 def __getitem__(self, tailNumber):
180 """Get the plane with the given tail number.
181
182 If the plane is not in the fleet, None is returned."""
183 return self._planes[tailNumber] if tailNumber in self._planes else None
184
185 def __repr__(self):
186 """Get the representation of the fleet object."""
187 return self._planes.__repr__()
188
189#------------------------------------------------------------------------------
190
191class NOTAM(object):
192 """A NOTAM for an airport."""
193 def __init__(self, begin, notice, end = None, permanent = False,
194 repeatCycle = None):
195 """Construct the NOTAM."""
196 self.begin = begin
197 self.notice = notice
198 self.end = end
199 self.permanent = permanent
200 self.repeatCycle = None
201
202 def __repr__(self):
203 """Get the representation of the NOTAM."""
204 s = "<NOTAM " + str(self.begin)
205 if self.end:
206 s += " - " + str(self.end)
207 elif self.permanent:
208 s += " - PERMANENT"
209 if self.repeatCycle:
210 s += " (" + self.repeatCycle + ")"
211 s += ": " + self.notice
212 s += ">"
213 return s
214
215#------------------------------------------------------------------------------
216
217class NOTAMHandler(xml.sax.handler.ContentHandler):
218 """A handler for the NOTAM database."""
219 def __init__(self, airportICAOs):
220 """Construct the handler for the airports with the given ICAO code."""
221 self._notams = {}
222 for icao in airportICAOs:
223 self._notams[icao] = []
224
225 def startElement(self, name, attrs):
226 """Start an element."""
227 if name!="notam" or \
228 "A" not in attrs or not attrs["A"] or \
229 "B" not in attrs or not attrs["B"] or \
230 "E" not in attrs or not attrs["E"]:
231 return
232
233 icao = attrs["A"]
234 if icao not in self._notams:
235 return
236
237 begin = datetime.datetime.strptime(attrs["B"], "%Y-%m-%d %H:%M:%S")
238
239 c = attrs["C"] if "C" in attrs else None
240 end = datetime.datetime.strptime(c, "%Y-%m-%d %H:%M:%S") if c else None
241
242 permanent = attrs["C_flag"]=="PERM" if "C_flag" in attrs else False
243
244 repeatCycle = attrs["D"] if "D" in attrs else None
245
246 self._notams[icao].append(NOTAM(begin, attrs["E"], end = end,
247 permanent = permanent,
248 repeatCycle = repeatCycle))
249
250 def get(self, icao):
251 """Get the NOTAMs for the given ICAO code."""
252 return self._notams[icao] if icao in self._notams else []
253
254#------------------------------------------------------------------------------
255
256class Result(object):
257 """A result object.
258
259 An instance of this filled with the appropriate data is passed to the
260 callback function on each request."""
261
262 def __repr__(self):
263 """Get a representation of the result."""
264 s = "<Result:"
265 for (key, value) in self.__dict__.iteritems():
266 s += " " + key + "=" + unicode(value)
267 s += ">"
268 return s
269
270#------------------------------------------------------------------------------
271
272class Request(object):
273 """Base class for requests.
274
275 It handles any exceptions and the calling of the callback.
276
277 If an exception occurs during processing, the callback is called with
278 the two parameters: a boolean value of False, and the exception object.
279
280 If no exception occurs, the callback is called with True and the return
281 value of the run() function.
282
283 If the callback function throws an exception, that is caught and logged
284 to the debug log."""
285 def __init__(self, callback):
286 """Construct the request."""
287 self._callback = callback
288
289 def perform(self):
290 """Perform the request.
291
292 The object's run() function is called. If it throws an exception,
293 the callback is called with False, and the exception. Otherwise the
294 callback is called with True and the return value of the run()
295 function. Any exceptions thrown by the callback are caught and
296 reported."""
297 try:
298 result = self.run()
299 returned = True
300 except Exception, e:
301 result = e
302 returned = False
303
304 try:
305 self._callback(returned, result)
306 except Exception, e:
307 print >> sys.stderr, "web.Handler.Request.perform: callback throwed an exception: " + str(e)
308
309#------------------------------------------------------------------------------
310
311class Login(Request):
312 """A login request."""
313 iso88592decoder = codecs.getdecoder("iso-8859-2")
314
315 def __init__(self, callback, pilotID, password):
316 """Construct the login request with the given pilot ID and
317 password."""
318 super(Login, self).__init__(callback)
319
320 self._pilotID = pilotID
321 self._password = password
322
323 def run(self):
324 """Perform the login request."""
325 md5 = hashlib.md5()
326 md5.update(self._pilotID)
327 pilotID = md5.hexdigest()
328
329 md5 = hashlib.md5()
330 md5.update(self._password)
331 password = md5.hexdigest()
332
333 url = "http://www.virtualairlines.hu/leker2.php?pid=%s&psw=%s" % \
334 (pilotID, password)
335
336 result = Result()
337
338 f = urllib2.urlopen(url)
339
340 status = readline(f)
341 result.loggedIn = status == ".OK."
342
343 if result.loggedIn:
344 result.pilotName = self.iso88592decoder(readline(f))[0]
345 result.exams = readline(f)
346 result.flights = []
347
348 while True:
349 line = readline(f)
350 if not line or line == "#ENDPIREP": break
351
352 flight = BookedFlight(line, f)
353 result.flights.append(flight)
354
355 result.flights.sort(cmp = lambda flight1, flight2:
356 cmp(flight1.departureTime,
357 flight2.departureTime))
358
359 f.close()
360
361 return result
362
363#------------------------------------------------------------------------------
364
365class GetFleet(Request):
366 """Request to get the fleet from the website."""
367
368 def __init__(self, callback):
369 """Construct the fleet request."""
370 super(GetFleet, self).__init__(callback)
371
372 def run(self):
373 """Perform the login request."""
374 url = "http://www.virtualairlines.hu/onlinegates_get.php"
375
376 f = urllib2.urlopen(url)
377 result = Result()
378 result.fleet = Fleet(f)
379 f.close()
380
381 return result
382
383#------------------------------------------------------------------------------
384
385class UpdatePlane(Request):
386 """Update the status of one of the planes in the fleet."""
387 def __init__(self, callback, tailNumber, status, gateNumber = None):
388 """Construct the request."""
389 super(UpdatePlane, self).__init__(callback)
390 self._tailNumber = tailNumber
391 self._status = status
392 self._gateNumber = gateNumber
393
394 def run(self):
395 """Perform the plane update."""
396 url = "http://www.virtualairlines.hu/onlinegates_set.php"
397
398 status = "H" if self._status==const.PLANE_HOME else \
399 "A" if self._status==const.PLANE_AWAY else \
400 "P" if self._status==const.PLANE_PARKING else ""
401
402 gateNumber = self._gateNumber if self._gateNumber else ""
403
404 data = urllib.urlencode([("lajstrom", self._tailNumber),
405 ("status", status),
406 ("kapu", gateNumber)])
407
408 f = urllib2.urlopen(url, data)
409 line = readline(f)
410
411 result = Result()
412 result.success = line == "OK"
413
414 return result
415
416#------------------------------------------------------------------------------
417
418class GetNOTAMs(Request):
419 """Get the NOTAMs from EURoutePro and select the ones we are interested
420 in."""
421 def __init__(self, callback, departureICAO, arrivalICAO):
422 """Construct the request for the given airports."""
423 super(GetNOTAMs, self).__init__(callback)
424 self._departureICAO = departureICAO
425 self._arrivalICAO = arrivalICAO
426
427 def run(self):
428 """Perform the plane update."""
429 xmlParser = xml.sax.make_parser()
430 notamHandler = NOTAMHandler([self._departureICAO, self._arrivalICAO])
431 xmlParser.setContentHandler(notamHandler)
432
433 url = "http://notams.euroutepro.com/notams.xml"
434
435 f = urllib2.urlopen(url)
436 try:
437 xmlParser.parse(f)
438 finally:
439 f.close()
440
441 result = Result()
442 result.departureNOTAMs = notamHandler.get(self._departureICAO)
443 result.arrivalNOTAMs = notamHandler.get(self._arrivalICAO)
444
445 return result
446
447#------------------------------------------------------------------------------
448
449class Handler(threading.Thread):
450 """The handler for the web services.
451
452 It can process one request at a time. The results are passed to a callback
453 function."""
454 def __init__(self):
455 """Construct the handler."""
456 super(Handler, self).__init__()
457
458 self._requests = []
459 self._requestCondition = threading.Condition()
460
461 self.daemon = True
462
463 def login(self, callback, pilotID, password):
464 """Enqueue a login request."""
465 self._addRequest(Login(callback, pilotID, password))
466
467 def getFleet(self, callback):
468 """Enqueue a fleet retrieval request."""
469 self._addRequest(GetFleet(callback))
470
471 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
472 """Update the status of the given plane."""
473 self._addRequest(UpdatePlane(callback, tailNumber, status, gateNumber))
474
475 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
476 """Get the NOTAMs for the given two airports."""
477 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
478
479 def run(self):
480 """Process the requests."""
481 while True:
482 with self._requestCondition:
483 while not self._requests:
484 self._requestCondition.wait()
485 request = self._requests[0]
486 del self._requests[0]
487
488 request.perform()
489
490 def _addRequest(self, request):
491 """Add the given request to the queue."""
492 with self._requestCondition:
493 self._requests.append(request)
494 self._requestCondition.notify()
495
496#------------------------------------------------------------------------------
497
498if __name__ == "__main__":
499 import time
500
501 def callback(returned, result):
502 print returned, unicode(result)
503
504 handler = Handler()
505 handler.start()
506
507 #handler.login(callback, "P096", "V5fwj")
508 #handler.getFleet(callback)
509 # Plane: HA-LEG home (gate 67)
510 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
511 #time.sleep(3)
512 #handler.getFleet(callback)
513 #time.sleep(3)
514
515 handler.getNOTAMs(callback, "LHBP", "EPWA")
516 time.sleep(3)
517
518
519#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.