source: src/mlx/web.py@ 1097:f2ac841cffec

python3
Last change on this file since 1097:f2ac841cffec was 1074:638d4c261307, checked in by István Váradi <ivaradi@…>, 22 months ago

New RPC call to query the SimBrief results (re #362)

File size: 35.4 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.sessionID = loginResult[3]
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 request = urllib.request.Request(url, headers = {
625 "cookie": "akamai_pilotweb_access=true;"
626 });
627 f = urllib.request.urlopen(request, timeout = 10.0, cafile=certifi.where())
628 try:
629 data = f.read(16384)
630 while data:
631 parser.feed(str(data))
632 data = f.read(16384)
633 finally:
634 f.close()
635
636 return parser.getNOTAMs()
637
638 except Exception as e:
639 traceback.print_exc()
640 print("mlx.web.GetNOTAMs.getPilotsWebNOTAMs: failed to get NOTAMs for '%s': %s" % \
641 (icao, str(e)))
642 return None
643
644#------------------------------------------------------------------------------
645
646class GetMETARs(Request):
647 """Get the METARs from the NOAA website for certain airport ICAOs."""
648
649 def __init__(self, callback, airports):
650 """Construct the request for the given airports."""
651 super(GetMETARs, self).__init__(callback)
652 self._airports = airports
653
654 def run(self):
655 """Perform the retrieval opf the METARs."""
656 url = "http://www.aviationweather.gov/adds/dataserver_current/httpparam?"
657 data = urllib.parse.urlencode([ ("dataSource" , "metars"),
658 ("requestType", "retrieve"),
659 ("format", "csv"),
660 ("stationString", " ".join(self._airports)),
661 ("hoursBeforeNow", "24"),
662 ("mostRecentForEachStation", "constraint")])
663 result = Result()
664 result.metars = {}
665 try:
666 url += data
667 f = urllib.request.urlopen(url, timeout = 10.0, cafile = certifi.where())
668 try:
669 for line in f.readlines():
670 line = str(line, "iso-8859-1")
671 if len(line)>5 and line[4]==' ':
672 icao = line[0:4]
673 if icao in self._airports:
674 result.metars[icao] = line.strip().split(",")[0]
675 finally:
676 f.close()
677 except Exception as e:
678 traceback.print_exc()
679 print("mlx.web.GetMETARs.run: failed to get METARs for %s: %s" % \
680 (self._airports, str(e)))
681
682 return result
683
684#------------------------------------------------------------------------------
685
686class SendPIREPRPC(RPCRequest):
687 """A request to send a PIREP to the MAVA website via the RPC interface."""
688
689 def __init__(self, client, callback, pirep, update):
690 """Construct the sending of the PIREP."""
691 super(SendPIREPRPC, self).__init__(client, callback)
692 self._pirep = pirep
693 self._update = update
694
695 def run(self):
696 """Perform the sending of the PIREP."""
697 pirep = self._pirep
698 resultCode = self._client.addPIREP(pirep.bookedFlight.id, pirep,
699 self._update)
700
701 result = Result()
702 result.success = resultCode==rpc.Client.RESULT_OK
703 result.alreadyFlown = resultCode==rpc.Client.RESULT_FLIGHT_ALREADY_REPORTED
704 result.notAvailable = resultCode==rpc.Client.RESULT_FLIGHT_NOT_EXISTS
705
706 return result
707
708#------------------------------------------------------------------------------
709
710class SendACARSRPC(RPCRequest):
711 """A request to send an ACARS to the MAVA website via JSON-RPC."""
712 def __init__(self, client, callback, acars):
713 """Construct the request for the given PIREP."""
714 super(SendACARSRPC, self).__init__(client, callback)
715 self._acars = acars
716
717 def run(self):
718 """Perform the sending of the ACARS."""
719 print("Sending the online ACARS via JSON-RPC")
720
721 self._client.updateOnlineACARS(self._acars)
722 return Result()
723
724#------------------------------------------------------------------------------
725
726class SendBugReport(Request):
727 """A request to send a bug report to the project homepage."""
728 _latin2Encoder = codecs.getencoder("iso-8859-2")
729
730 def __init__(self, callback, summary, description, email):
731 """Construct the request for the given bug report."""
732 super(SendBugReport, self).__init__(callback)
733 self._summary = summary
734 self._description = description
735 self._email = email
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 result.success = True
752
753 return result
754
755#------------------------------------------------------------------------------
756
757class SetCheckFlightPassed(RPCRequest):
758 """A request to mark the user as one having passed the check flight."""
759 def __init__(self, client, callback, aircraftType):
760 """Construct the request for the given type."""
761 super(SetCheckFlightPassed, self).__init__(client, callback)
762 self._aircraftType = aircraftType
763
764 def run(self):
765 """Perform the update."""
766 aircraftType = BookedFlight.TYPE2TYPECODE[self._aircraftType]
767 self._client.setCheckFlightPassed(aircraftType)
768 return Result()
769
770#------------------------------------------------------------------------------
771
772class GetPIREP(RPCRequest):
773 """A request to retrieve the PIREP of a certain flight."""
774 def __init__(self, client, callback, flightID):
775 """Construct the request."""
776 super(GetPIREP, self).__init__(client, callback)
777 self._flightID = flightID
778
779 def run(self):
780 """Perform the update."""
781 result = Result()
782
783 pirepData = self._client.getPIREP(self._flightID)
784 print("pirepData:", pirepData)
785
786 bookedFlight = BookedFlight(id = self._flightID)
787 bookedFlight.setupFromPIREPData(pirepData)
788
789 result.pirep = PIREP(None)
790 result.pirep.setupFromPIREPData(pirepData, bookedFlight)
791
792 return result
793
794#------------------------------------------------------------------------------
795
796class ReflyFlights(RPCRequest):
797 """A request to mark certain flights for reflying."""
798 def __init__(self, client, callback, flightIDs):
799 """Construct the request."""
800 super(ReflyFlights, self).__init__(client, callback)
801 self._flightIDs = flightIDs
802
803 def run(self):
804 """Perform the update."""
805 self._client.reflyFlights(self._flightIDs)
806 return Result()
807
808#------------------------------------------------------------------------------
809
810class DeleteFlights(RPCRequest):
811 """A request to delete certain flights."""
812 def __init__(self, client, callback, flightIDs):
813 """Construct the request."""
814 super(DeleteFlights, self).__init__(client, callback)
815 self._flightIDs = flightIDs
816
817 def run(self):
818 """Perform the update."""
819 self._client.deleteFlights(self._flightIDs)
820 return Result()
821
822#------------------------------------------------------------------------------
823
824class GetAcceptedFlights(RPCRequest):
825 """Request to get the accepted flights."""
826 def __init__(self, client, callback):
827 """Construct the request with the given client and callback function."""
828 super(GetAcceptedFlights, self).__init__(client, callback)
829
830 def run(self):
831 """Perform the login request."""
832 result = Result()
833
834 result.flights = self._client.getAcceptedFlights()
835
836 return result
837
838#------------------------------------------------------------------------------
839
840class GetTimetable(RPCRequest):
841 """Request to get the timetable."""
842 def __init__(self, client, callback, date, types):
843 """Construct the request with the given client and callback function."""
844 super(GetTimetable, self).__init__(client, callback)
845 self._date = date
846 self._types = types
847
848 def run(self):
849 """Perform the login request."""
850 result = Result()
851
852 result.flightPairs = self._client.getTimetable(self._date, self._types)
853
854 return result
855
856#------------------------------------------------------------------------------
857
858class BookFlights(RPCRequest):
859 """Request to book flights."""
860 def __init__(self, client, callback, flightIDs, date, tailNumber):
861 """Construct the request with the given client and callback function."""
862 super(BookFlights, self).__init__(client, callback)
863 self._flightIDs = flightIDs
864 self._date = date
865 self._tailNumber = tailNumber
866
867 def run(self):
868 """Perform the login request."""
869 result = Result()
870
871 result.bookedFlights = self._client.bookFlights(self._flightIDs,
872 self._date,
873 self._tailNumber)
874
875 return result
876
877#------------------------------------------------------------------------------
878
879class GetSimBriefResult(RPCRequest):
880 """Request the SimBrief result."""
881 def __init__(self, client, callback, timestamp):
882 """Construct the request with the given client and callback function."""
883 super(GetSimBriefResult, self).__init__(client, callback)
884 self._timestamp = timestamp
885
886 def run(self):
887 """Perform the request."""
888 result = Result()
889
890 result.result = self._client.getSimBriefResult(self._timestamp)
891
892 return result
893
894#------------------------------------------------------------------------------
895
896class Handler(threading.Thread):
897 """The handler for the web services.
898
899 It can process one request at a time. The results are passed to a callback
900 function."""
901 def __init__(self, config, getCredentialsFn):
902 """Construct the handler."""
903 super(Handler, self).__init__()
904
905 self._requests = []
906 self._requestCondition = threading.Condition()
907
908 self.daemon = True
909 self._config = config
910 self._rpcClient = rpc.Client(getCredentialsFn)
911 if config.rememberPassword:
912 self._rpcClient.setCredentials(config.pilotID, config.password)
913
914 def register(self, callback, registrationData):
915 """Enqueue a registration request."""
916 self._addRequest(Register(self._rpcClient, callback, registrationData))
917
918 def login(self, callback, pilotID, password):
919 """Enqueue a login request."""
920 request = LoginRPC(self._rpcClient, callback, pilotID, password)
921
922 self._addRequest(request)
923
924 def getEntryExamStatus(self, callback):
925 """Get the entry exam status."""
926 self._addRequest(GetEntryExamStatus(self._rpcClient, callback))
927
928 def getFleet(self, callback):
929 """Enqueue a fleet retrieval request."""
930 request = GetFleetRPC(self._rpcClient, callback,)
931 self._addRequest(request)
932
933 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
934 """Update the status of the given plane."""
935 request = UpdatePlaneRPC(self._rpcClient, callback,
936 tailNumber, status, gateNumber)
937 self._addRequest(request)
938
939 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
940 """Get the NOTAMs for the given two airports."""
941 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
942
943 def getMETARs(self, callback, airports):
944 """Get the METARs for the given airports."""
945 self._addRequest(GetMETARs(callback, airports))
946
947 def sendPIREP(self, callback, pirep, update = False):
948 """Send the given PIREP."""
949 request = SendPIREPRPC(self._rpcClient, callback, pirep, update)
950 self._addRequest(request)
951
952 def sendACARS(self, callback, acars):
953 """Send the given ACARS"""
954 request = SendACARSRPC(self._rpcClient, callback, acars)
955 self._addRequest(request)
956
957 def sendBugReport(self, callback, summary, description, email):
958 """Send a bug report with the given data."""
959 self._addRequest(SendBugReport(callback, summary, description, email))
960
961 def setCheckFlightPassed(self, callback, aircraftType):
962 """Mark the check flight as passed."""
963 self._addRequest(SetCheckFlightPassed(self._rpcClient,
964 callback, aircraftType))
965
966 def getPIREP(self, callback, flightID):
967 """Query the PIREP for the given flight."""
968 self._addRequest(GetPIREP(self._rpcClient, callback, flightID))
969
970 def reflyFlights(self, callback, flightIDs):
971 """Mark the flights with the given IDs for reflying."""
972 self._addRequest(ReflyFlights(self._rpcClient, callback, flightIDs))
973
974 def deleteFlights(self, callback, flightIDs):
975 """Delete the flights with the given IDs."""
976 self._addRequest(DeleteFlights(self._rpcClient, callback, flightIDs))
977
978 def getAcceptedFlights(self, callback):
979 """Enqueue a request to get the accepted flights."""
980 self._addRequest(GetAcceptedFlights(self._rpcClient, callback))
981
982 def getTimetable(self, callback, date, types):
983 """Enqueue a request to get the timetable."""
984 self._addRequest(GetTimetable(self._rpcClient, callback, date, types))
985
986 def bookFlights(self, callback, flightIDs, date, tailNumber):
987 """Enqueue a request to book some flights."""
988 self._addRequest(BookFlights(self._rpcClient, callback,
989 flightIDs, date, tailNumber))
990
991 def getSimBriefResult(self, callback, timestamp):
992 """Enqueue a request to get the SimBrief result."""
993 self._addRequest(GetSimBriefResult(self._rpcClient, callback,
994 timestamp))
995
996 def run(self):
997 """Process the requests."""
998 while True:
999 with self._requestCondition:
1000 while not self._requests:
1001 self._requestCondition.wait()
1002 request = self._requests[0]
1003 del self._requests[0]
1004
1005 request.perform()
1006
1007 def _addRequest(self, request):
1008 """Add the given request to the queue."""
1009 with self._requestCondition:
1010 self._requests.append(request)
1011 self._requestCondition.notify()
1012
1013#------------------------------------------------------------------------------
1014
1015if __name__ == "__main__":
1016 import time
1017
1018 def callback(returned, result):
1019 print(returned, str(result))
1020
1021 handler = Handler()
1022 handler.start()
1023
1024 #handler.login(callback, "P096", "V5fwj")
1025 #handler.getFleet(callback)
1026 # Plane: HA-LEG home (gate 67)
1027 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
1028 #time.sleep(3)
1029 #handler.getFleet(callback)
1030 #time.sleep(3)
1031
1032 #handler.getNOTAMs(callback, "LHBP", "EPWA")
1033 #handler.getMETARs(callback, ["LHBP", "EPWA"])
1034 #time.sleep(5)
1035
1036 handler.updatePlane(callback, "HA-LON", const.PLANE_AWAY, "")
1037 time.sleep(3)
1038
1039#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.