source: src/mlx/web.py@ 1191:0db341c8c3b3

python3 tip
Last change on this file since 1191:0db341c8c3b3 was 1191:0db341c8c3b3, checked in by István Váradi <ivaradi@…>, 4 days ago

context is used instead of cafile in urlopen()

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