source: src/mlx/web.py@ 1059:07ffbc5c13f8

python3
Last change on this file since 1059:07ffbc5c13f8 was 1057:c0b03a92ef29, checked in by István Váradi <ivaradi@…>, 22 months ago

Eliminated some unresolved references in the documentation

File size: 34.5 KB
Line 
1
2from . import const
3from . import util
4from .rpc import Registration, BookedFlight
5from . import rpc
6from . import rpccommon
7
8from .common import MAVA_BASE_URL
9from .pirep import PIREP
10
11import threading
12import sys
13import urllib.request, urllib.parse, urllib.error
14import urllib.request, urllib.error, urllib.parse
15import hashlib
16import time
17import re
18import datetime
19import codecs
20import traceback
21import xml.sax
22import xmlrpc.client
23import html.parser
24import certifi
25
26#---------------------------------------------------------------------------------------
27
28## @package mlx.web
29#
30# Web interface.
31#
32# This module implements a thread that can perform (HTTP) requests
33# asynchronously. When the request is performed, a callback is called. The main
34# interface is the \ref Handler class. Each of its functions creates a \ref
35# Request subclass instance and puts it to the request queue. The handler
36# thread then takes the requests one by one, and executes them.
37#
38# This module also defines some data classes the contents of which are
39# retrieved or sent via HTTP. \ref Fleet and \ref Plane represents the MAVA
40# fleet and the gates at Ferihegy and \ref NOTAM is a NOTAM.
41
42#---------------------------------------------------------------------------------------
43
44def readline(f):
45 """Read a line from the given file.
46
47 The line is stripped and empty lines are discarded."""
48 while True:
49 line = f.readline()
50 if not line: return ""
51 line = line.strip()
52 if line:
53 return line
54
55#------------------------------------------------------------------------------
56
57class Plane(rpccommon.Plane):
58 """Information about an airplane in the fleet."""
59 def __init__(self, s):
60 """Build a plane info based on the given string.
61
62 The string consists of three, space-separated fields.
63 The first field is the tail number, the second field is the gate
64 number, the third field is the plane's status as a character."""
65 super(Plane, self).__init__()
66
67 try:
68 words = s.split(" ")
69 tailNumber = words[0]
70 self.tailNumber = tailNumber
71
72 status = words[2] if len(words)>2 else None
73 self._setStatus(status)
74
75 gateNumber = words[1] if len(words)>1 else ""
76 self.gateNumber = gateNumber if gateNumber else None
77
78 except:
79 print("Plane string is invalid: '" + s + "'", file=sys.stderr)
80 self.tailNumber = None
81
82#------------------------------------------------------------------------------
83
84class Fleet(rpccommon.Fleet):
85 """Information about the whole fleet."""
86 def __init__(self, f):
87 """Construct the fleet information by reading the given file object."""
88 super(Fleet, self).__init__()
89
90 while True:
91 line = readline(f)
92 if not line or line == "#END": break
93
94 plane = Plane(line)
95 self._addPlane(plane)
96
97#------------------------------------------------------------------------------
98
99class NOTAM(object):
100 """A NOTAM for an airport."""
101 def __init__(self, ident, basic,
102 begin, notice, end = None, permanent = False,
103 repeatCycle = None):
104 """Construct the NOTAM."""
105 self.ident = ident
106 self.basic = basic
107 self.begin = begin
108 self.notice = notice
109 self.end = end
110 self.permanent = permanent
111 self.repeatCycle = repeatCycle
112
113 def __repr__(self):
114 """Get the representation of the NOTAM."""
115 s = "<NOTAM " + str(self.begin)
116 if self.end:
117 s += " - " + str(self.end)
118 elif self.permanent:
119 s += " - PERMANENT"
120 if self.repeatCycle:
121 s += " (" + self.repeatCycle + ")"
122 s += ": " + self.notice
123 s += ">"
124 return s
125
126 def __str__(self):
127 """Get the string representation of the NOTAM."""
128 s = ""
129 s += str(self.ident) + " " + str(self.basic) + "\n"
130 s += str(self.begin)
131 if self.end is not None:
132 s += " - " + str(self.end)
133 elif self.permanent:
134 s += " - PERMANENT"
135 s += "\n"
136 if self.repeatCycle:
137 s += "Repeat cycle: " + self.repeatCycle + "\n"
138 s += self.notice + "\n"
139 return s
140
141#------------------------------------------------------------------------------
142
143class NOTAMHandler(xml.sax.handler.ContentHandler):
144 """A handler for the NOTAM database."""
145 def __init__(self, airportICAOs):
146 """Construct the handler for the airports with the given ICAO code."""
147 self._notams = {}
148 for icao in airportICAOs:
149 self._notams[icao] = []
150
151 def startElement(self, name, attrs):
152 """Start an element."""
153 if name!="notam" or \
154 "ident" not in attrs or not attrs["ident"] or \
155 "Q" not in attrs or not attrs["Q"] or \
156 "A" not in attrs or not attrs["A"] or \
157 "B" not in attrs or not attrs["B"] or \
158 "E" not in attrs or not attrs["E"]:
159 return
160
161 icao = attrs["A"]
162 if icao not in self._notams:
163 return
164
165 begin = datetime.datetime.strptime(attrs["B"], "%Y-%m-%d %H:%M:%S")
166
167 c = attrs["C"] if "C" in attrs else None
168 end = datetime.datetime.strptime(c, "%Y-%m-%d %H:%M:%S") if c else None
169
170 permanent = attrs["C_flag"]=="PERM" if "C_flag" in attrs else False
171
172 repeatCycle = attrs["D"] if "D" in attrs else None
173
174 self._notams[icao].append(NOTAM(attrs["ident"], attrs["Q"],
175 begin, attrs["E"], end = end,
176 permanent = permanent,
177 repeatCycle = repeatCycle))
178
179 def get(self, icao):
180 """Get the NOTAMs for the given ICAO code."""
181 return self._notams[icao] if icao in self._notams else []
182
183#------------------------------------------------------------------------------
184
185class PilotsWebNOTAMsParser(html.parser.HTMLParser):
186 """XML handler for the NOTAM query results on the PilotsWeb website."""
187 def __init__(self):
188 """Construct the handler."""
189 html.parser.HTMLParser.__init__(self)
190
191 self._notams = []
192 self._currentNOTAM = ""
193 self._stage = 0
194
195 def handle_starttag(self, name, attrs):
196 """Start an element."""
197 if (self._stage==0 and name=="div" and ("id", "notamRight") in attrs) or \
198 (self._stage==1 and name=="span") or \
199 (self._stage==2 and name=="pre"):
200 self._stage += 1
201 if self._stage==1:
202 self._currentNOTAM = ""
203
204 def handle_data(self, content):
205 """Handle characters"""
206 if self._stage==3:
207 self._currentNOTAM += content
208
209 def handle_endtag(self, name):
210 """End an element."""
211 if (self._stage==3 and name=="pre") or \
212 (self._stage==2 and name=="span") or \
213 (self._stage==1 and name=="div"):
214 self._stage -= 1
215 if self._stage==0:
216 self._processCurrentNOTAM()
217
218 def getNOTAMs(self):
219 """Get the NOTAMs collected"""
220 return self._notams
221
222 def _processCurrentNOTAM(self):
223 """Parse the current NOTAM and append its contents to the list of
224 NOTAMS."""
225 notam = None
226 try:
227 notam = self._parseCurrentNOTAM2()
228 except Exception as e:
229 print("Error parsing current NOTAM: " + str(e))
230
231 if notam is None:
232 print("Could not parse NOTAM: " + self._currentNOTAM)
233 if self._currentNOTAM:
234 self._notams.append(self._currentNOTAM + "\n")
235 else:
236 self._notams.append(notam)
237
238 def _parseCurrentNOTAM(self):
239 """Parse the current NOTAM, if possible, and return a NOTAM object."""
240 lines = self._currentNOTAM.splitlines()
241 lines = [line.strip() for line in lines]
242
243 if len(lines)<4:
244 return None
245
246 if not lines[1].startswith("Q)") or \
247 not lines[2].startswith("A)") or \
248 not (lines[3].startswith("E)") or
249 (lines[3].startswith("D)") and lines[4].startswith("E)"))):
250 return None
251
252 ident = lines[0].split()[0]
253 basic = lines[1][2:].strip()
254
255 words = lines[2].split()
256 if len(words)<4 or words[0]!="A)" or words[2]!="B)":
257 return None
258
259 begin = datetime.datetime.strptime(words[3], "%y%m%d%H%M")
260 end = None
261 permanent = False
262 if words[4]=="C)" and len(words)>=6:
263 if words[5] in ["PERM", "UFN"]:
264 permanent = True
265 else:
266 end = datetime.datetime.strptime(words[5], "%y%m%d%H%M")
267 else:
268 permanent = True
269
270 repeatCycle = None
271 noticeStartIndex = 3
272 if lines[3].startswith("D)"):
273 repeatCycle = lines[3][2:].strip()
274 noticeStartIndex = 4
275
276 notice = ""
277 for index in range(noticeStartIndex, len(lines)):
278 line = lines[index][2:] if index==noticeStartIndex else lines[index]
279 line = line.strip()
280
281 if line.lower().startswith("created:") or \
282 line.lower().startswith("source:"):
283 break
284
285 if notice: notice += " "
286 notice += line
287
288 return NOTAM(ident, basic, begin, notice, end = end,
289 permanent = permanent, repeatCycle = repeatCycle)
290
291 def _parseCurrentNOTAM2(self):
292 """Parse the current NOTAM with a second, more flexible method."""
293 self._currentNOTAM = self._currentNOTAM.replace("\\n", "\n")
294 lines = self._currentNOTAM.splitlines()
295 if len(lines)==1:
296 lines = lines[0].splitlines()
297 lines = [line.strip() for line in lines]
298
299 if not lines:
300 return None
301
302 ident = lines[0].split()[0]
303
304 lines = lines[1:]
305 for i in range(0, 2):
306 l = lines[-1].lower()
307 if l.startswith("created:") or l.startswith("source:"):
308 lines = lines[:-1]
309
310 lines = [line.strip() for line in lines]
311 contents = " ".join(lines).split()
312
313 items = {}
314 for i in ["Q)", "A)", "B)", "C)", "D)", "E)"]:
315 items[i] = ""
316
317 currentItem = None
318 for word in contents:
319 if word in items:
320 currentItem = word
321 elif currentItem in items:
322 s = items[currentItem]
323 if s: s+= " "
324 s += word
325 items[currentItem] = s
326
327 if not items["Q)"] or not items["A)"] or not items["B)"] or \
328 not items["E)"]:
329 return None
330
331 def parseTime(item):
332 item = re.sub("([0-9]+).*", "\\1", item)
333 try:
334 return datetime.datetime.strptime(item, "%y%m%d%H%M")
335 except ValueError:
336 return datetime.datetime.strptime(item, "%Y%m%d%H%M")
337
338 basic = items["Q)"]
339 begin = parseTime(items["B)"])
340
341 end = None
342 permanent = False
343 if items["C)"]:
344 endItem = items["C)"]
345 if endItem in ["PERM", "UFN"]:
346 permanent = True
347 else:
348 end = parseTime(items["C)"])
349 else:
350 permanent = True
351
352 repeatCycle = None
353 if items["D)"]:
354 repeatCycle = items["D)"]
355
356 notice = items["E)"]
357
358 return NOTAM(ident, basic, begin, notice, end = end,
359 permanent = permanent, repeatCycle = repeatCycle)
360
361#------------------------------------------------------------------------------
362
363class Result(object):
364 """A result object.
365
366 An instance of this filled with the appropriate data is passed to the
367 callback function on each request."""
368
369 def __repr__(self):
370 """Get a representation of the result."""
371 s = "<Result:"
372 for (key, value) in self.__dict__.items():
373 s += " " + key + "=" + str(value)
374 s += ">"
375 return s
376
377#------------------------------------------------------------------------------
378
379class Request(object):
380 """Base class for requests.
381
382 It handles any exceptions and the calling of the callback.
383
384 If an exception occurs during processing, the callback is called with
385 the two parameters: a boolean value of False, and the exception object.
386
387 If no exception occurs, the callback is called with True and the return
388 value of the run() function.
389
390 If the callback function throws an exception, that is caught and logged
391 to the debug log."""
392 def __init__(self, callback):
393 """Construct the request."""
394 self._callback = callback
395
396 def perform(self):
397 """Perform the request.
398
399 The object's run() function is called. If it throws an exception,
400 the callback is called with False, and the exception. Otherwise the
401 callback is called with True and the return value of the run()
402 function. Any exceptions thrown by the callback are caught and
403 reported."""
404 try:
405 result = self.run()
406 returned = True
407 except Exception as e:
408 traceback.print_exc()
409 result = e
410 returned = False
411
412 try:
413 self._callback(returned, result)
414 except Exception as e:
415 print("web.Handler.Request.perform: callback throwed an exception: " + util.utf2unicode(str(e)), file=sys.stderr)
416 #traceback.print_exc()
417
418#------------------------------------------------------------------------------
419
420class RPCRequest(Request):
421 """Common base class for RPC requests.
422
423 It stores the RPC client received from the handler."""
424 def __init__(self, client, callback):
425 """Construct the request."""
426 super(RPCRequest, self).__init__(callback)
427 self._client = client
428
429#------------------------------------------------------------------------------
430
431class Register(RPCRequest):
432 """A registration request."""
433 def __init__(self, client, callback, registrationData):
434 """Construct the request."""
435 super(Register, self).__init__(client, callback)
436 self._registrationData = registrationData
437
438 def run(self):
439 """Perform the registration."""
440
441 registrationData = self._registrationData
442
443 (resultCode, pilotID) = self._client.register(registrationData)
444 result = Result()
445 result.registered = resultCode==rpc.Client.RESULT_OK
446 if result.registered:
447 result.pilotID = pilotID
448
449 self._client.setCredentials(pilotID, registrationData.password)
450 LoginRPC.setupLoginResult(result, self._client, pilotID,
451 registrationData.password)
452
453 result.invalidData = \
454 resultCode==rpc.Client.RESULT_INVALID_DATA
455 result.emailAlreadyRegistered = \
456 resultCode==rpc.Client.RESULT_EMAIL_ALREADY_REGISTERED
457
458 return result
459
460#------------------------------------------------------------------------------
461
462class LoginRPC(RPCRequest):
463 """An RPC-based login request."""
464 @staticmethod
465 def setupLoginResult(result, client, pilotID, password):
466 """Setup the login result with the given client, pilot ID and
467 password."""
468 loginResult = client.login()
469 result.loggedIn = loginResult is not None
470 if result.loggedIn:
471 result.pilotID = pilotID
472 result.pilotName = loginResult[0]
473 result.rank = loginResult[1]
474 result.types = loginResult[2]
475 result.password = password
476 result.fleet = client.getFleet()
477 flights = client.getFlights()
478 result.flights = flights[0]
479 result.reportedFlights = flights[1]
480 result.rejectedFlights = flights[2]
481 if result.rank=="STU":
482 reply = client.getEntryExamStatus()
483 result.entryExamPassed = reply[0]
484 result.entryExamLink = reply[1]
485 result.checkFlightStatus = reply[2]
486 if reply[3]:
487 result.rank = "FO"
488
489
490 def __init__(self, client, callback, pilotID, password):
491 """Construct the login request with the given pilot ID and
492 password."""
493 super(LoginRPC, self).__init__(client, callback)
494
495 self._pilotID = pilotID
496 self._password = password
497
498 def run(self):
499 """Perform the login request."""
500 result = Result()
501
502 self._client.setCredentials(self._pilotID, self._password)
503 LoginRPC.setupLoginResult(result, self._client,
504 self._pilotID, self._password)
505
506 return result
507
508#------------------------------------------------------------------------------
509
510class GetEntryExamStatus(RPCRequest):
511 """A request to get the entry exam status."""
512 def __init__(self, client, callback):
513 """Construct the request."""
514 super(GetEntryExamStatus, self).__init__(client, callback)
515
516 def run(self):
517 """Perform the query."""
518 result = Result()
519
520 reply = self._client.getEntryExamStatus()
521
522 result.entryExamPassed = reply[0]
523 result.entryExamLink = reply[1]
524 result.checkFlightStatus = reply[2]
525 result.madeFO = reply[3]
526
527 return result
528
529#------------------------------------------------------------------------------
530
531class GetFleetRPC(RPCRequest):
532 """Request to get the fleet from the website using RPC."""
533 def __init__(self, client, callback):
534 """Construct the request with the given client and callback function."""
535 super(GetFleetRPC, self).__init__(client, callback)
536
537 def run(self):
538 """Perform the login request."""
539 result = Result()
540
541 result.fleet = self._client.getFleet()
542
543 return result
544
545#------------------------------------------------------------------------------
546
547class UpdatePlaneRPC(RPCRequest):
548 """RPC request to update the status and the position of a plane in the
549 fleet."""
550 def __init__(self, client, callback, tailNumber, status, gateNumber = None):
551 """Construct the request."""
552 super(UpdatePlaneRPC, self).__init__(client, callback)
553 self._tailNumber = tailNumber
554 self._status = status
555 self._gateNumber = gateNumber
556
557 def run(self):
558 """Perform the plane update."""
559 self._client.updatePlane(self._tailNumber, self._status, self._gateNumber)
560
561 # Otherwise an exception is thrown
562 result = Result()
563 result.success = True
564
565 return result
566
567#------------------------------------------------------------------------------
568
569class GetNOTAMs(Request):
570 """Get the NOTAMs from EURoutePro and select the ones we are interested
571 in."""
572 def __init__(self, callback, departureICAO, arrivalICAO):
573 """Construct the request for the given airports."""
574 super(GetNOTAMs, self).__init__(callback)
575 self._departureICAO = departureICAO
576 self._arrivalICAO = arrivalICAO
577
578 def run(self):
579 """Perform the retrieval of the NOTAMs."""
580 departureNOTAMs = self.getPilotsWebNOTAMs(self._departureICAO)
581 arrivalNOTAMs = self.getPilotsWebNOTAMs(self._arrivalICAO)
582
583 icaos = []
584 if not departureNOTAMs: icaos.append(self._departureICAO)
585 if not arrivalNOTAMs: icaos.append(self._arrivalICAO)
586
587 if icaos:
588 xmlParser = xml.sax.make_parser()
589 notamHandler = NOTAMHandler(icaos)
590 xmlParser.setContentHandler(notamHandler)
591
592 url = "http://notams.euroutepro.com/notams.xml"
593
594 f = urllib.request.urlopen(url, timeout = 10.0)
595 try:
596 xmlParser.parse(f)
597 finally:
598 f.close()
599
600 for icao in icaos:
601 if icao==self._departureICAO:
602 departureNOTAMs = notamHandler.get(icao)
603 else:
604 arrivalNOTAMs = notamHandler.get(icao)
605
606 result = Result()
607 result.departureNOTAMs = departureNOTAMs
608 result.arrivalNOTAMs = arrivalNOTAMs
609
610 return result
611
612 def getPilotsWebNOTAMs(self, icao):
613 """Try to get the NOTAMs from FAA's PilotsWeb site for the given ICAO
614 code.
615
616 Returns a list of PilotsWEBNOTAM objects, or None in case of an error."""
617 try:
618 parser = PilotsWebNOTAMsParser()
619
620 url = "https://pilotweb.nas.faa.gov/PilotWeb/notamRetrievalByICAOAction.do?method=displayByICAOs&formatType=ICAO&retrieveLocId=%s&reportType=RAW&actionType=notamRetrievalByICAOs" % \
621 (icao.upper(),)
622
623 request = urllib.request.Request(url, headers = {
624 "cookie": "akamai_pilotweb_access=true;"
625 });
626 f = urllib.request.urlopen(request, timeout = 10.0, cafile=certifi.where())
627 try:
628 data = f.read(16384)
629 while data:
630 parser.feed(str(data))
631 data = f.read(16384)
632 finally:
633 f.close()
634
635 return parser.getNOTAMs()
636
637 except Exception as e:
638 traceback.print_exc()
639 print("mlx.web.GetNOTAMs.getPilotsWebNOTAMs: failed to get NOTAMs for '%s': %s" % \
640 (icao, str(e)))
641 return None
642
643#------------------------------------------------------------------------------
644
645class GetMETARs(Request):
646 """Get the METARs from the NOAA website for certain airport ICAOs."""
647
648 def __init__(self, callback, airports):
649 """Construct the request for the given airports."""
650 super(GetMETARs, self).__init__(callback)
651 self._airports = airports
652
653 def run(self):
654 """Perform the retrieval opf the METARs."""
655 url = "http://www.aviationweather.gov/adds/dataserver_current/httpparam?"
656 data = urllib.parse.urlencode([ ("dataSource" , "metars"),
657 ("requestType", "retrieve"),
658 ("format", "csv"),
659 ("stationString", " ".join(self._airports)),
660 ("hoursBeforeNow", "24"),
661 ("mostRecentForEachStation", "constraint")])
662 result = Result()
663 result.metars = {}
664 try:
665 url += data
666 f = urllib.request.urlopen(url, timeout = 10.0, cafile = certifi.where())
667 try:
668 for line in f.readlines():
669 line = str(line, "iso-8859-1")
670 if len(line)>5 and line[4]==' ':
671 icao = line[0:4]
672 if icao in self._airports:
673 result.metars[icao] = line.strip().split(",")[0]
674 finally:
675 f.close()
676 except Exception as e:
677 traceback.print_exc()
678 print("mlx.web.GetMETARs.run: failed to get METARs for %s: %s" % \
679 (self._airports, str(e)))
680
681 return result
682
683#------------------------------------------------------------------------------
684
685class SendPIREPRPC(RPCRequest):
686 """A request to send a PIREP to the MAVA website via the RPC interface."""
687
688 def __init__(self, client, callback, pirep, update):
689 """Construct the sending of the PIREP."""
690 super(SendPIREPRPC, self).__init__(client, callback)
691 self._pirep = pirep
692 self._update = update
693
694 def run(self):
695 """Perform the sending of the PIREP."""
696 pirep = self._pirep
697 resultCode = self._client.addPIREP(pirep.bookedFlight.id, pirep,
698 self._update)
699
700 result = Result()
701 result.success = resultCode==rpc.Client.RESULT_OK
702 result.alreadyFlown = resultCode==rpc.Client.RESULT_FLIGHT_ALREADY_REPORTED
703 result.notAvailable = resultCode==rpc.Client.RESULT_FLIGHT_NOT_EXISTS
704
705 return result
706
707#------------------------------------------------------------------------------
708
709class SendACARSRPC(RPCRequest):
710 """A request to send an ACARS to the MAVA website via JSON-RPC."""
711 def __init__(self, client, callback, acars):
712 """Construct the request for the given PIREP."""
713 super(SendACARSRPC, self).__init__(client, callback)
714 self._acars = acars
715
716 def run(self):
717 """Perform the sending of the ACARS."""
718 print("Sending the online ACARS via JSON-RPC")
719
720 self._client.updateOnlineACARS(self._acars)
721 return Result()
722
723#------------------------------------------------------------------------------
724
725class SendBugReport(Request):
726 """A request to send a bug report to the project homepage."""
727 _latin2Encoder = codecs.getencoder("iso-8859-2")
728
729 def __init__(self, callback, summary, description, email):
730 """Construct the request for the given bug report."""
731 super(SendBugReport, self).__init__(callback)
732 self._summary = summary
733 self._description = description
734 self._email = email
735
736 def run(self):
737 """Perform the sending of the bug report."""
738 serverProxy = xmlrpc.client.ServerProxy("http://mlx.varadiistvan.hu/rpc")
739
740 result = Result()
741 result.success = False
742
743 attributes = {}
744 if self._email:
745 attributes["reporter"] = self._email
746
747 result.ticketID = serverProxy.ticket.create(self._summary, self._description,
748 attributes, True)
749 print("Created ticket with ID:", result.ticketID)
750 result.success = True
751
752 return result
753
754#------------------------------------------------------------------------------
755
756class SetCheckFlightPassed(RPCRequest):
757 """A request to mark the user as one having passed the check flight."""
758 def __init__(self, client, callback, aircraftType):
759 """Construct the request for the given type."""
760 super(SetCheckFlightPassed, self).__init__(client, callback)
761 self._aircraftType = aircraftType
762
763 def run(self):
764 """Perform the update."""
765 aircraftType = BookedFlight.TYPE2TYPECODE[self._aircraftType]
766 self._client.setCheckFlightPassed(aircraftType)
767 return Result()
768
769#------------------------------------------------------------------------------
770
771class GetPIREP(RPCRequest):
772 """A request to retrieve the PIREP of a certain flight."""
773 def __init__(self, client, callback, flightID):
774 """Construct the request."""
775 super(GetPIREP, self).__init__(client, callback)
776 self._flightID = flightID
777
778 def run(self):
779 """Perform the update."""
780 result = Result()
781
782 pirepData = self._client.getPIREP(self._flightID)
783 print("pirepData:", pirepData)
784
785 bookedFlight = BookedFlight(id = self._flightID)
786 bookedFlight.setupFromPIREPData(pirepData)
787
788 result.pirep = PIREP(None)
789 result.pirep.setupFromPIREPData(pirepData, bookedFlight)
790
791 return result
792
793#------------------------------------------------------------------------------
794
795class ReflyFlights(RPCRequest):
796 """A request to mark certain flights for reflying."""
797 def __init__(self, client, callback, flightIDs):
798 """Construct the request."""
799 super(ReflyFlights, self).__init__(client, callback)
800 self._flightIDs = flightIDs
801
802 def run(self):
803 """Perform the update."""
804 self._client.reflyFlights(self._flightIDs)
805 return Result()
806
807#------------------------------------------------------------------------------
808
809class DeleteFlights(RPCRequest):
810 """A request to delete certain flights."""
811 def __init__(self, client, callback, flightIDs):
812 """Construct the request."""
813 super(DeleteFlights, self).__init__(client, callback)
814 self._flightIDs = flightIDs
815
816 def run(self):
817 """Perform the update."""
818 self._client.deleteFlights(self._flightIDs)
819 return Result()
820
821#------------------------------------------------------------------------------
822
823class GetAcceptedFlights(RPCRequest):
824 """Request to get the accepted flights."""
825 def __init__(self, client, callback):
826 """Construct the request with the given client and callback function."""
827 super(GetAcceptedFlights, self).__init__(client, callback)
828
829 def run(self):
830 """Perform the login request."""
831 result = Result()
832
833 result.flights = self._client.getAcceptedFlights()
834
835 return result
836
837#------------------------------------------------------------------------------
838
839class GetTimetable(RPCRequest):
840 """Request to get the timetable."""
841 def __init__(self, client, callback, date, types):
842 """Construct the request with the given client and callback function."""
843 super(GetTimetable, self).__init__(client, callback)
844 self._date = date
845 self._types = types
846
847 def run(self):
848 """Perform the login request."""
849 result = Result()
850
851 result.flightPairs = self._client.getTimetable(self._date, self._types)
852
853 return result
854
855#------------------------------------------------------------------------------
856
857class BookFlights(RPCRequest):
858 """Request to book flights."""
859 def __init__(self, client, callback, flightIDs, date, tailNumber):
860 """Construct the request with the given client and callback function."""
861 super(BookFlights, self).__init__(client, callback)
862 self._flightIDs = flightIDs
863 self._date = date
864 self._tailNumber = tailNumber
865
866 def run(self):
867 """Perform the login request."""
868 result = Result()
869
870 result.bookedFlights = self._client.bookFlights(self._flightIDs,
871 self._date,
872 self._tailNumber)
873
874 return result
875
876#------------------------------------------------------------------------------
877
878class Handler(threading.Thread):
879 """The handler for the web services.
880
881 It can process one request at a time. The results are passed to a callback
882 function."""
883 def __init__(self, config, getCredentialsFn):
884 """Construct the handler."""
885 super(Handler, self).__init__()
886
887 self._requests = []
888 self._requestCondition = threading.Condition()
889
890 self.daemon = True
891 self._config = config
892 self._rpcClient = rpc.Client(getCredentialsFn)
893 if config.rememberPassword:
894 self._rpcClient.setCredentials(config.pilotID, config.password)
895
896 def register(self, callback, registrationData):
897 """Enqueue a registration request."""
898 self._addRequest(Register(self._rpcClient, callback, registrationData))
899
900 def login(self, callback, pilotID, password):
901 """Enqueue a login request."""
902 request = LoginRPC(self._rpcClient, callback, pilotID, password)
903
904 self._addRequest(request)
905
906 def getEntryExamStatus(self, callback):
907 """Get the entry exam status."""
908 self._addRequest(GetEntryExamStatus(self._rpcClient, callback))
909
910 def getFleet(self, callback):
911 """Enqueue a fleet retrieval request."""
912 request = GetFleetRPC(self._rpcClient, callback,)
913 self._addRequest(request)
914
915 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
916 """Update the status of the given plane."""
917 request = UpdatePlaneRPC(self._rpcClient, callback,
918 tailNumber, status, gateNumber)
919 self._addRequest(request)
920
921 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
922 """Get the NOTAMs for the given two airports."""
923 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
924
925 def getMETARs(self, callback, airports):
926 """Get the METARs for the given airports."""
927 self._addRequest(GetMETARs(callback, airports))
928
929 def sendPIREP(self, callback, pirep, update = False):
930 """Send the given PIREP."""
931 request = SendPIREPRPC(self._rpcClient, callback, pirep, update)
932 self._addRequest(request)
933
934 def sendACARS(self, callback, acars):
935 """Send the given ACARS"""
936 request = SendACARSRPC(self._rpcClient, callback, acars)
937 self._addRequest(request)
938
939 def sendBugReport(self, callback, summary, description, email):
940 """Send a bug report with the given data."""
941 self._addRequest(SendBugReport(callback, summary, description, email))
942
943 def setCheckFlightPassed(self, callback, aircraftType):
944 """Mark the check flight as passed."""
945 self._addRequest(SetCheckFlightPassed(self._rpcClient,
946 callback, aircraftType))
947
948 def getPIREP(self, callback, flightID):
949 """Query the PIREP for the given flight."""
950 self._addRequest(GetPIREP(self._rpcClient, callback, flightID))
951
952 def reflyFlights(self, callback, flightIDs):
953 """Mark the flights with the given IDs for reflying."""
954 self._addRequest(ReflyFlights(self._rpcClient, callback, flightIDs))
955
956 def deleteFlights(self, callback, flightIDs):
957 """Delete the flights with the given IDs."""
958 self._addRequest(DeleteFlights(self._rpcClient, callback, flightIDs))
959
960 def getAcceptedFlights(self, callback):
961 """Enqueue a request to get the accepted flights."""
962 self._addRequest(GetAcceptedFlights(self._rpcClient, callback))
963
964 def getTimetable(self, callback, date, types):
965 """Enqueue a request to get the timetable."""
966 self._addRequest(GetTimetable(self._rpcClient, callback, date, types))
967
968 def bookFlights(self, callback, flightIDs, date, tailNumber):
969 """Enqueue a request to book some flights."""
970 self._addRequest(BookFlights(self._rpcClient, callback,
971 flightIDs, date, tailNumber))
972
973 def run(self):
974 """Process the requests."""
975 while True:
976 with self._requestCondition:
977 while not self._requests:
978 self._requestCondition.wait()
979 request = self._requests[0]
980 del self._requests[0]
981
982 request.perform()
983
984 def _addRequest(self, request):
985 """Add the given request to the queue."""
986 with self._requestCondition:
987 self._requests.append(request)
988 self._requestCondition.notify()
989
990#------------------------------------------------------------------------------
991
992if __name__ == "__main__":
993 import time
994
995 def callback(returned, result):
996 print(returned, str(result))
997
998 handler = Handler()
999 handler.start()
1000
1001 #handler.login(callback, "P096", "V5fwj")
1002 #handler.getFleet(callback)
1003 # Plane: HA-LEG home (gate 67)
1004 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
1005 #time.sleep(3)
1006 #handler.getFleet(callback)
1007 #time.sleep(3)
1008
1009 #handler.getNOTAMs(callback, "LHBP", "EPWA")
1010 #handler.getMETARs(callback, ["LHBP", "EPWA"])
1011 #time.sleep(5)
1012
1013 handler.updatePlane(callback, "HA-LON", const.PLANE_AWAY, "")
1014 time.sleep(3)
1015
1016#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.