source: src/mlx/web.py@ 1052:ab6c60d5cd36

python3
Last change on this file since 1052:ab6c60d5cd36 was 1044:988bfea3e8db, checked in by István Váradi <ivaradi@…>, 2 years ago

RPC is the only communication method (re #357)

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