source: src/mlx/web.py@ 1179:7cbf103d3785

python3
Last change on this file since 1179:7cbf103d3785 was 1164:92e6925b39b8, checked in by István Váradi <ivaradi@…>, 5 months ago

Long debug logs are attached as a file to the bug tickets

File size: 35.6 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
25import base64
26
27#---------------------------------------------------------------------------------------
28
29## @package mlx.web
30#
31# Web interface.
32#
33# This module implements a thread that can perform (HTTP) requests
34# asynchronously. When the request is performed, a callback is called. The main
35# interface is the \ref Handler class. Each of its functions creates a \ref
36# Request subclass instance and puts it to the request queue. The handler
37# thread then takes the requests one by one, and executes them.
38#
39# This module also defines some data classes the contents of which are
40# retrieved or sent via HTTP. \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.sessionID = loginResult[3]
477 result.password = password
478 result.fleet = client.getFleet()
479 result.gates = client.getGates()
480 flights = client.getFlights()
481 result.flights = flights[0]
482 result.reportedFlights = flights[1]
483 result.rejectedFlights = flights[2]
484 if result.rank=="STU":
485 reply = client.getEntryExamStatus()
486 result.entryExamPassed = reply[0]
487 result.entryExamLink = reply[1]
488 result.checkFlightStatus = reply[2]
489 if reply[3]:
490 result.rank = "FO"
491
492
493 def __init__(self, client, callback, pilotID, password):
494 """Construct the login request with the given pilot ID and
495 password."""
496 super(LoginRPC, self).__init__(client, callback)
497
498 self._pilotID = pilotID
499 self._password = password
500
501 def run(self):
502 """Perform the login request."""
503 result = Result()
504
505 self._client.setCredentials(self._pilotID, self._password)
506 LoginRPC.setupLoginResult(result, self._client,
507 self._pilotID, self._password)
508
509 return result
510
511#------------------------------------------------------------------------------
512
513class GetEntryExamStatus(RPCRequest):
514 """A request to get the entry exam status."""
515 def __init__(self, client, callback):
516 """Construct the request."""
517 super(GetEntryExamStatus, self).__init__(client, callback)
518
519 def run(self):
520 """Perform the query."""
521 result = Result()
522
523 reply = self._client.getEntryExamStatus()
524
525 result.entryExamPassed = reply[0]
526 result.entryExamLink = reply[1]
527 result.checkFlightStatus = reply[2]
528 result.madeFO = reply[3]
529
530 return result
531
532#------------------------------------------------------------------------------
533
534class GetFleetRPC(RPCRequest):
535 """Request to get the fleet from the website using RPC."""
536 def __init__(self, client, callback):
537 """Construct the request with the given client and callback function."""
538 super(GetFleetRPC, self).__init__(client, callback)
539
540 def run(self):
541 """Perform the login request."""
542 result = Result()
543
544 result.fleet = self._client.getFleet()
545
546 return result
547
548#------------------------------------------------------------------------------
549
550class UpdatePlaneRPC(RPCRequest):
551 """RPC request to update the status and the position of a plane in the
552 fleet."""
553 def __init__(self, client, callback, tailNumber, status, gateNumber = None):
554 """Construct the request."""
555 super(UpdatePlaneRPC, self).__init__(client, callback)
556 self._tailNumber = tailNumber
557 self._status = status
558 self._gateNumber = gateNumber
559
560 def run(self):
561 """Perform the plane update."""
562 self._client.updatePlane(self._tailNumber, self._status, self._gateNumber)
563
564 # Otherwise an exception is thrown
565 result = Result()
566 result.success = True
567
568 return result
569
570#------------------------------------------------------------------------------
571
572class GetNOTAMs(Request):
573 """Get the NOTAMs from EURoutePro and select the ones we are interested
574 in."""
575 def __init__(self, callback, departureICAO, arrivalICAO):
576 """Construct the request for the given airports."""
577 super(GetNOTAMs, self).__init__(callback)
578 self._departureICAO = departureICAO
579 self._arrivalICAO = arrivalICAO
580
581 def run(self):
582 """Perform the retrieval of the NOTAMs."""
583 departureNOTAMs = self.getPilotsWebNOTAMs(self._departureICAO)
584 arrivalNOTAMs = self.getPilotsWebNOTAMs(self._arrivalICAO)
585
586 icaos = []
587 if not departureNOTAMs: icaos.append(self._departureICAO)
588 if not arrivalNOTAMs: icaos.append(self._arrivalICAO)
589
590 if icaos:
591 xmlParser = xml.sax.make_parser()
592 notamHandler = NOTAMHandler(icaos)
593 xmlParser.setContentHandler(notamHandler)
594
595 url = "http://notams.euroutepro.com/notams.xml"
596
597 f = urllib.request.urlopen(url, timeout = 10.0)
598 try:
599 xmlParser.parse(f)
600 finally:
601 f.close()
602
603 for icao in icaos:
604 if icao==self._departureICAO:
605 departureNOTAMs = notamHandler.get(icao)
606 else:
607 arrivalNOTAMs = notamHandler.get(icao)
608
609 result = Result()
610 result.departureNOTAMs = departureNOTAMs
611 result.arrivalNOTAMs = arrivalNOTAMs
612
613 return result
614
615 def getPilotsWebNOTAMs(self, icao):
616 """Try to get the NOTAMs from FAA's PilotsWeb site for the given ICAO
617 code.
618
619 Returns a list of PilotsWEBNOTAM objects, or None in case of an error."""
620 try:
621 parser = PilotsWebNOTAMsParser()
622
623 url = "https://pilotweb.nas.faa.gov/PilotWeb/notamRetrievalByICAOAction.do?method=displayByICAOs&formatType=ICAO&retrieveLocId=%s&reportType=RAW&actionType=notamRetrievalByICAOs" % \
624 (icao.upper(),)
625
626 request = urllib.request.Request(url, headers = {
627 "cookie": "akamai_pilotweb_access=true;"
628 });
629 f = urllib.request.urlopen(request, timeout = 10.0, cafile=certifi.where())
630 try:
631 data = f.read(16384)
632 while data:
633 parser.feed(str(data))
634 data = f.read(16384)
635 finally:
636 f.close()
637
638 return parser.getNOTAMs()
639
640 except Exception as e:
641 traceback.print_exc()
642 print("mlx.web.GetNOTAMs.getPilotsWebNOTAMs: failed to get NOTAMs for '%s': %s" % \
643 (icao, str(e)))
644 return None
645
646#------------------------------------------------------------------------------
647
648class GetMETARs(Request):
649 """Get the METARs from the NOAA website for certain airport ICAOs."""
650
651 def __init__(self, callback, airports):
652 """Construct the request for the given airports."""
653 super(GetMETARs, self).__init__(callback)
654 self._airports = airports
655
656 def run(self):
657 """Perform the retrieval opf the METARs."""
658 url = "https://aviationweather.gov/cgi-bin/data/metar.php?"
659 data = urllib.parse.urlencode([ ("ids", ",".join(self._airports)),
660 ("hours", "0"),
661 ("format", "raw") ])
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, debugLog):
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 self._debugLog = debugLog
736
737 def run(self):
738 """Perform the sending of the bug report."""
739 serverProxy = xmlrpc.client.ServerProxy("http://mlx.varadiistvan.hu/rpc")
740
741 result = Result()
742 result.success = False
743
744 attributes = {}
745 if self._email:
746 attributes["reporter"] = self._email
747
748 result.ticketID = serverProxy.ticket.create(self._summary, self._description,
749 attributes, True)
750 print("Created ticket with ID:", result.ticketID)
751 if self._debugLog:
752 serverProxy.ticket.putAttachment(result.ticketID, "debug.log",
753 "Debug log",
754 bytes(self._debugLog, "utf-8"))
755
756
757 result.success = True
758
759 return result
760
761#------------------------------------------------------------------------------
762
763class SetCheckFlightPassed(RPCRequest):
764 """A request to mark the user as one having passed the check flight."""
765 def __init__(self, client, callback, aircraftType):
766 """Construct the request for the given type."""
767 super(SetCheckFlightPassed, self).__init__(client, callback)
768 self._aircraftType = aircraftType
769
770 def run(self):
771 """Perform the update."""
772 aircraftType = BookedFlight.TYPE2TYPECODE[self._aircraftType]
773 self._client.setCheckFlightPassed(aircraftType)
774 return Result()
775
776#------------------------------------------------------------------------------
777
778class GetPIREP(RPCRequest):
779 """A request to retrieve the PIREP of a certain flight."""
780 def __init__(self, client, callback, flightID):
781 """Construct the request."""
782 super(GetPIREP, self).__init__(client, callback)
783 self._flightID = flightID
784
785 def run(self):
786 """Perform the update."""
787 result = Result()
788
789 pirepData = self._client.getPIREP(self._flightID)
790 print("pirepData:", pirepData)
791
792 bookedFlight = BookedFlight(id = self._flightID)
793 bookedFlight.setupFromPIREPData(pirepData)
794
795 result.pirep = PIREP(None)
796 result.pirep.setupFromPIREPData(pirepData, bookedFlight)
797
798 return result
799
800#------------------------------------------------------------------------------
801
802class ReflyFlights(RPCRequest):
803 """A request to mark certain flights for reflying."""
804 def __init__(self, client, callback, flightIDs):
805 """Construct the request."""
806 super(ReflyFlights, self).__init__(client, callback)
807 self._flightIDs = flightIDs
808
809 def run(self):
810 """Perform the update."""
811 self._client.reflyFlights(self._flightIDs)
812 return Result()
813
814#------------------------------------------------------------------------------
815
816class DeleteFlights(RPCRequest):
817 """A request to delete certain flights."""
818 def __init__(self, client, callback, flightIDs):
819 """Construct the request."""
820 super(DeleteFlights, self).__init__(client, callback)
821 self._flightIDs = flightIDs
822
823 def run(self):
824 """Perform the update."""
825 self._client.deleteFlights(self._flightIDs)
826 return Result()
827
828#------------------------------------------------------------------------------
829
830class GetAcceptedFlights(RPCRequest):
831 """Request to get the accepted flights."""
832 def __init__(self, client, callback):
833 """Construct the request with the given client and callback function."""
834 super(GetAcceptedFlights, self).__init__(client, callback)
835
836 def run(self):
837 """Perform the login request."""
838 result = Result()
839
840 result.flights = self._client.getAcceptedFlights()
841
842 return result
843
844#------------------------------------------------------------------------------
845
846class GetTimetable(RPCRequest):
847 """Request to get the timetable."""
848 def __init__(self, client, callback, date, types):
849 """Construct the request with the given client and callback function."""
850 super(GetTimetable, self).__init__(client, callback)
851 self._date = date
852 self._types = types
853
854 def run(self):
855 """Perform the login request."""
856 result = Result()
857
858 result.flightPairs = self._client.getTimetable(self._date, self._types)
859
860 return result
861
862#------------------------------------------------------------------------------
863
864class BookFlights(RPCRequest):
865 """Request to book flights."""
866 def __init__(self, client, callback, flightIDs, date, tailNumber):
867 """Construct the request with the given client and callback function."""
868 super(BookFlights, self).__init__(client, callback)
869 self._flightIDs = flightIDs
870 self._date = date
871 self._tailNumber = tailNumber
872
873 def run(self):
874 """Perform the login request."""
875 result = Result()
876
877 result.bookedFlights = self._client.bookFlights(self._flightIDs,
878 self._date,
879 self._tailNumber)
880
881 return result
882
883#------------------------------------------------------------------------------
884
885class GetSimBriefResult(RPCRequest):
886 """Request the SimBrief result."""
887 def __init__(self, client, callback, timestamp):
888 """Construct the request with the given client and callback function."""
889 super(GetSimBriefResult, self).__init__(client, callback)
890 self._timestamp = timestamp
891
892 def run(self):
893 """Perform the request."""
894 result = Result()
895
896 result.result = self._client.getSimBriefResult(self._timestamp)
897
898 return result
899
900#------------------------------------------------------------------------------
901
902class Handler(threading.Thread):
903 """The handler for the web services.
904
905 It can process one request at a time. The results are passed to a callback
906 function."""
907 def __init__(self, config, getCredentialsFn):
908 """Construct the handler."""
909 super(Handler, self).__init__()
910
911 self._requests = []
912 self._requestCondition = threading.Condition()
913
914 self.daemon = True
915 self._config = config
916 self._rpcClient = rpc.Client(getCredentialsFn)
917 if config.rememberPassword:
918 self._rpcClient.setCredentials(config.pilotID, config.password)
919
920 def register(self, callback, registrationData):
921 """Enqueue a registration request."""
922 self._addRequest(Register(self._rpcClient, callback, registrationData))
923
924 def login(self, callback, pilotID, password):
925 """Enqueue a login request."""
926 request = LoginRPC(self._rpcClient, callback, pilotID, password)
927
928 self._addRequest(request)
929
930 def getEntryExamStatus(self, callback):
931 """Get the entry exam status."""
932 self._addRequest(GetEntryExamStatus(self._rpcClient, callback))
933
934 def getFleet(self, callback):
935 """Enqueue a fleet retrieval request."""
936 request = GetFleetRPC(self._rpcClient, callback,)
937 self._addRequest(request)
938
939 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
940 """Update the status of the given plane."""
941 request = UpdatePlaneRPC(self._rpcClient, callback,
942 tailNumber, status, gateNumber)
943 self._addRequest(request)
944
945 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
946 """Get the NOTAMs for the given two airports."""
947 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
948
949 def getMETARs(self, callback, airports):
950 """Get the METARs for the given airports."""
951 self._addRequest(GetMETARs(callback, airports))
952
953 def sendPIREP(self, callback, pirep, update = False):
954 """Send the given PIREP."""
955 request = SendPIREPRPC(self._rpcClient, callback, pirep, update)
956 self._addRequest(request)
957
958 def sendACARS(self, callback, acars):
959 """Send the given ACARS"""
960 request = SendACARSRPC(self._rpcClient, callback, acars)
961 self._addRequest(request)
962
963 def sendBugReport(self, callback, summary, description, email, debugLog = None):
964 """Send a bug report with the given data."""
965 self._addRequest(SendBugReport(callback, summary, description, email,
966 debugLog = debugLog))
967
968 def setCheckFlightPassed(self, callback, aircraftType):
969 """Mark the check flight as passed."""
970 self._addRequest(SetCheckFlightPassed(self._rpcClient,
971 callback, aircraftType))
972
973 def getPIREP(self, callback, flightID):
974 """Query the PIREP for the given flight."""
975 self._addRequest(GetPIREP(self._rpcClient, callback, flightID))
976
977 def reflyFlights(self, callback, flightIDs):
978 """Mark the flights with the given IDs for reflying."""
979 self._addRequest(ReflyFlights(self._rpcClient, callback, flightIDs))
980
981 def deleteFlights(self, callback, flightIDs):
982 """Delete the flights with the given IDs."""
983 self._addRequest(DeleteFlights(self._rpcClient, callback, flightIDs))
984
985 def getAcceptedFlights(self, callback):
986 """Enqueue a request to get the accepted flights."""
987 self._addRequest(GetAcceptedFlights(self._rpcClient, callback))
988
989 def getTimetable(self, callback, date, types):
990 """Enqueue a request to get the timetable."""
991 self._addRequest(GetTimetable(self._rpcClient, callback, date, types))
992
993 def bookFlights(self, callback, flightIDs, date, tailNumber):
994 """Enqueue a request to book some flights."""
995 self._addRequest(BookFlights(self._rpcClient, callback,
996 flightIDs, date, tailNumber))
997
998 def getSimBriefResult(self, callback, timestamp):
999 """Enqueue a request to get the SimBrief result."""
1000 self._addRequest(GetSimBriefResult(self._rpcClient, callback,
1001 timestamp))
1002
1003 def run(self):
1004 """Process the requests."""
1005 while True:
1006 with self._requestCondition:
1007 while not self._requests:
1008 self._requestCondition.wait()
1009 request = self._requests[0]
1010 del self._requests[0]
1011
1012 request.perform()
1013
1014 def _addRequest(self, request):
1015 """Add the given request to the queue."""
1016 with self._requestCondition:
1017 self._requests.append(request)
1018 self._requestCondition.notify()
1019
1020#------------------------------------------------------------------------------
1021
1022if __name__ == "__main__":
1023 import time
1024
1025 def callback(returned, result):
1026 print(returned, str(result))
1027
1028 handler = Handler()
1029 handler.start()
1030
1031 #handler.login(callback, "P096", "V5fwj")
1032 #handler.getFleet(callback)
1033 # Plane: HA-LEG home (gate 67)
1034 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
1035 #time.sleep(3)
1036 #handler.getFleet(callback)
1037 #time.sleep(3)
1038
1039 #handler.getNOTAMs(callback, "LHBP", "EPWA")
1040 #handler.getMETARs(callback, ["LHBP", "EPWA"])
1041 #time.sleep(5)
1042
1043 handler.updatePlane(callback, "HA-LON", const.PLANE_AWAY, "")
1044 time.sleep(3)
1045
1046#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.