source: src/mlx/web.py@ 1185:a48a0883c767

python3
Last change on this file since 1185:a48a0883c767 was 1181:52dda2b6d0eb, checked in by István Váradi <ivaradi@…>, 4 months ago

The Trac server can be accessed with password authentication only

File size: 37.9 KB
RevLine 
[41]1
[919]2from . import const
3from . import util
[1044]4from .rpc import Registration, BookedFlight
[919]5from . import rpc
6from . import rpccommon
[41]7
[919]8from .common import MAVA_BASE_URL
9from .pirep import PIREP
[740]10
[41]11import threading
12import sys
[919]13import urllib.request, urllib.parse, urllib.error
14import urllib.request, urllib.error, urllib.parse
[41]15import hashlib
[66]16import time
[930]17import re
[41]18import datetime
19import codecs
[106]20import traceback
[63]21import xml.sax
[919]22import xmlrpc.client
23import html.parser
[962]24import certifi
[1164]25import base64
[1181]26import os.path
27import ssl
[41]28
29#---------------------------------------------------------------------------------------
30
[298]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
[1057]42# retrieved or sent via HTTP. \ref Fleet and \ref Plane represents the MAVA
[298]43# fleet and the gates at Ferihegy and \ref NOTAM is a NOTAM.
44
45#---------------------------------------------------------------------------------------
46
[41]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
[742]60class Plane(rpccommon.Plane):
[51]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."""
[742]68 super(Plane, self).__init__()
69
[51]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
[742]76 self._setStatus(status)
[51]77
78 gateNumber = words[1] if len(words)>1 else ""
79 self.gateNumber = gateNumber if gateNumber else None
80
81 except:
[919]82 print("Plane string is invalid: '" + s + "'", file=sys.stderr)
[51]83 self.tailNumber = None
84
85#------------------------------------------------------------------------------
86
[742]87class Fleet(rpccommon.Fleet):
[51]88 """Information about the whole fleet."""
89 def __init__(self, f):
90 """Construct the fleet information by reading the given file object."""
[742]91 super(Fleet, self).__init__()
92
[51]93 while True:
94 line = readline(f)
95 if not line or line == "#END": break
96
97 plane = Plane(line)
[742]98 self._addPlane(plane)
[443]99
[51]100#------------------------------------------------------------------------------
101
[63]102class NOTAM(object):
103 """A NOTAM for an airport."""
[570]104 def __init__(self, ident, basic,
105 begin, notice, end = None, permanent = False,
[63]106 repeatCycle = None):
107 """Construct the NOTAM."""
[570]108 self.ident = ident
109 self.basic = basic
[63]110 self.begin = begin
111 self.notice = notice
112 self.end = end
113 self.permanent = permanent
[570]114 self.repeatCycle = repeatCycle
[63]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
[443]128
[570]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
[63]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 \
[570]157 "ident" not in attrs or not attrs["ident"] or \
158 "Q" not in attrs or not attrs["Q"] or \
[63]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
[443]163
[63]164 icao = attrs["A"]
165 if icao not in self._notams:
166 return
[443]167
[63]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
[443]172
[63]173 permanent = attrs["C_flag"]=="PERM" if "C_flag" in attrs else False
[443]174
[63]175 repeatCycle = attrs["D"] if "D" in attrs else None
176
[570]177 self._notams[icao].append(NOTAM(attrs["ident"], attrs["Q"],
178 begin, attrs["E"], end = end,
[63]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
[919]188class PilotsWebNOTAMsParser(html.parser.HTMLParser):
[570]189 """XML handler for the NOTAM query results on the PilotsWeb website."""
190 def __init__(self):
191 """Construct the handler."""
[919]192 html.parser.HTMLParser.__init__(self)
[570]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()
[919]231 except Exception as e:
232 print("Error parsing current NOTAM: " + str(e))
[570]233
234 if notam is None:
[919]235 print("Could not parse NOTAM: " + self._currentNOTAM)
[570]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()
[919]244 lines = [line.strip() for line in lines]
[570]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."""
[930]296 self._currentNOTAM = self._currentNOTAM.replace("\\n", "\n")
[570]297 lines = self._currentNOTAM.splitlines()
[930]298 if len(lines)==1:
299 lines = lines[0].splitlines()
[919]300 lines = [line.strip() for line in lines]
[570]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
[919]313 lines = [line.strip() for line in lines]
[570]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
[806]334 def parseTime(item):
[930]335 item = re.sub("([0-9]+).*", "\\1", item)
[806]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
[570]341 basic = items["Q)"]
[806]342 begin = parseTime(items["B)"])
[570]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:
[806]351 end = parseTime(items["C)"])
[570]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
[41]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:"
[919]375 for (key, value) in self.__dict__.items():
376 s += " " + key + "=" + str(value)
[41]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
[919]410 except Exception as e:
[106]411 traceback.print_exc()
[41]412 result = e
413 returned = False
414
415 try:
416 self._callback(returned, result)
[919]417 except Exception as e:
418 print("web.Handler.Request.perform: callback throwed an exception: " + util.utf2unicode(str(e)), file=sys.stderr)
[570]419 #traceback.print_exc()
[41]420
421#------------------------------------------------------------------------------
422
[746]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
[756]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)
[771]453 LoginRPC.setupLoginResult(result, self._client, pilotID,
454 registrationData.password)
[756]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
[746]465class LoginRPC(RPCRequest):
466 """An RPC-based login request."""
[771]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]
[858]477 result.types = loginResult[2]
[1074]478 result.sessionID = loginResult[3]
[771]479 result.password = password
[1044]480 result.fleet = client.getFleet()
[1154]481 result.gates = client.getGates()
[809]482 flights = client.getFlights()
483 result.flights = flights[0]
484 result.reportedFlights = flights[1]
485 result.rejectedFlights = flights[2]
[771]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
[858]494
[760]495 def __init__(self, client, callback, pilotID, password):
[746]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)
[771]508 LoginRPC.setupLoginResult(result, self._client,
509 self._pilotID, self._password)
[761]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]
[764]528 result.entryExamLink = reply[1]
529 result.checkFlightStatus = reply[2]
[769]530 result.madeFO = reply[3]
[746]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
[63]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):
[66]584 """Perform the retrieval of the NOTAMs."""
[570]585 departureNOTAMs = self.getPilotsWebNOTAMs(self._departureICAO)
586 arrivalNOTAMs = self.getPilotsWebNOTAMs(self._arrivalICAO)
[63]587
[570]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)
[63]596
[570]597 url = "http://notams.euroutepro.com/notams.xml"
598
[919]599 f = urllib.request.urlopen(url, timeout = 10.0)
[570]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)
[63]610
611 result = Result()
[570]612 result.departureNOTAMs = departureNOTAMs
613 result.arrivalNOTAMs = arrivalNOTAMs
[63]614
615 return result
616
[570]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
[1053]628 request = urllib.request.Request(url, headers = {
629 "cookie": "akamai_pilotweb_access=true;"
630 });
631 f = urllib.request.urlopen(request, timeout = 10.0, cafile=certifi.where())
[570]632 try:
633 data = f.read(16384)
634 while data:
[928]635 parser.feed(str(data))
[570]636 data = f.read(16384)
637 finally:
638 f.close()
639
640 return parser.getNOTAMs()
641
[919]642 except Exception as e:
[570]643 traceback.print_exc()
[919]644 print("mlx.web.GetNOTAMs.getPilotsWebNOTAMs: failed to get NOTAMs for '%s': %s" % \
645 (icao, str(e)))
[570]646 return None
647
[63]648#------------------------------------------------------------------------------
649
[66]650class GetMETARs(Request):
[443]651 """Get the METARs from the NOAA website for certain airport ICAOs."""
[66]652
653 def __init__(self, callback, airports):
654 """Construct the request for the given airports."""
655 super(GetMETARs, self).__init__(callback)
656 self._airports = airports
657
658 def run(self):
659 """Perform the retrieval opf the METARs."""
[1114]660 url = "https://aviationweather.gov/cgi-bin/data/metar.php?"
661 data = urllib.parse.urlencode([ ("ids", ",".join(self._airports)),
662 ("hours", "0"),
663 ("format", "raw") ])
[963]664 result = Result()
665 result.metars = {}
[66]666 try:
[963]667 url += data
668 f = urllib.request.urlopen(url, timeout = 10.0, cafile = certifi.where())
669 try:
670 for line in f.readlines():
671 line = str(line, "iso-8859-1")
672 if len(line)>5 and line[4]==' ':
673 icao = line[0:4]
674 if icao in self._airports:
675 result.metars[icao] = line.strip().split(",")[0]
676 finally:
677 f.close()
678 except Exception as e:
679 traceback.print_exc()
680 print("mlx.web.GetMETARs.run: failed to get METARs for %s: %s" % \
681 (self._airports, str(e)))
[66]682
683 return result
684
685#------------------------------------------------------------------------------
686
[746]687class SendPIREPRPC(RPCRequest):
688 """A request to send a PIREP to the MAVA website via the RPC interface."""
689
[853]690 def __init__(self, client, callback, pirep, update):
[746]691 """Construct the sending of the PIREP."""
692 super(SendPIREPRPC, self).__init__(client, callback)
693 self._pirep = pirep
[853]694 self._update = update
[746]695
696 def run(self):
697 """Perform the sending of the PIREP."""
698 pirep = self._pirep
[853]699 resultCode = self._client.addPIREP(pirep.bookedFlight.id, pirep,
700 self._update)
[746]701
702 result = Result()
703 result.success = resultCode==rpc.Client.RESULT_OK
704 result.alreadyFlown = resultCode==rpc.Client.RESULT_FLIGHT_ALREADY_REPORTED
705 result.notAvailable = resultCode==rpc.Client.RESULT_FLIGHT_NOT_EXISTS
706
707 return result
708
709#------------------------------------------------------------------------------
710
711class SendACARSRPC(RPCRequest):
712 """A request to send an ACARS to the MAVA website via JSON-RPC."""
713 def __init__(self, client, callback, acars):
714 """Construct the request for the given PIREP."""
715 super(SendACARSRPC, self).__init__(client, callback)
716 self._acars = acars
717
718 def run(self):
719 """Perform the sending of the ACARS."""
[919]720 print("Sending the online ACARS via JSON-RPC")
[746]721
722 self._client.updateOnlineACARS(self._acars)
723 return Result()
724
725#------------------------------------------------------------------------------
726
[1181]727class BugReportPasswordManager:
728 """Password manager for the Trac XML-RPC server"""
729 def __init__(self, programDirectory):
730 """Construct the password manager by reading the password from
731 the bugreport.txt file"""
732 with open(os.path.join(programDirectory, "bugreport.txt")) as f:
733 self._password = f.read().strip()
734
735 def add_password(self, realm, uri, username, password):
736 pass
737
738 def find_user_password(self, realm, uri):
739 return ("mlxbugreport", self._password)
740
741#------------------------------------------------------------------------------
742
743class BugReportTransport(xmlrpc.client.Transport):
744 """A transport for digest authentication towards the Trac XML-RPC server"""
745 verbose = True
746
747 def __init__(self, programDirectory):
748 """Construct the transport for the given program directory."""
749 super().__init__()
750
751 sslContext = ssl.create_default_context(ssl.Purpose.SERVER_AUTH,
752 cafile = certifi.where())
753 sslContext.set_alpn_protocols(['http/1.1'])
754 httpsHandler = urllib.request.HTTPSHandler(context = sslContext)
755
756 authHandler = urllib.request.HTTPDigestAuthHandler(
757 BugReportPasswordManager(programDirectory))
758
759 self._opener = urllib.request.build_opener(httpsHandler, authHandler)
760
761 def single_request(self, host, handler, request_body, verbose=False):
762 """Perform a single request"""
763 url = "https://" + host + handler
764 request = urllib.request.Request(url, data = request_body)
765 request.add_header("User-Agent", self.user_agent)
766 request.add_header("Content-Type", "text/xml")
767
768 with self._opener.open(request) as f:
769 if f.status==200:
770 return self.parse_response(f)
771 else:
772 raise xmlrpc.client.ProtocolError(
773 host + handler,
774 f.status, f.reason,
775 f.getheaders())
776
777#------------------------------------------------------------------------------
778
[484]779class SendBugReport(Request):
780 """A request to send a bug report to the project homepage."""
781 _latin2Encoder = codecs.getencoder("iso-8859-2")
782
[1181]783 def __init__(self, callback, summary, description, email, debugLog,
784 transport):
[484]785 """Construct the request for the given bug report."""
786 super(SendBugReport, self).__init__(callback)
787 self._summary = summary
788 self._description = description
789 self._email = email
[1164]790 self._debugLog = debugLog
[1181]791 self._transport = transport
[484]792
793 def run(self):
794 """Perform the sending of the bug report."""
[1181]795 serverProxy = xmlrpc.client.ServerProxy("https://mlx.varadiistvan.hu/login/rpc",
796 transport = self._transport)
[484]797 result = Result()
[491]798 result.success = False
[488]799
800 attributes = {}
801 if self._email:
802 attributes["reporter"] = self._email
803
[491]804 result.ticketID = serverProxy.ticket.create(self._summary, self._description,
805 attributes, True)
[919]806 print("Created ticket with ID:", result.ticketID)
[1164]807 if self._debugLog:
808 serverProxy.ticket.putAttachment(result.ticketID, "debug.log",
809 "Debug log",
810 bytes(self._debugLog, "utf-8"))
811
812
[484]813 result.success = True
814
815 return result
816
817#------------------------------------------------------------------------------
818
[769]819class SetCheckFlightPassed(RPCRequest):
820 """A request to mark the user as one having passed the check flight."""
821 def __init__(self, client, callback, aircraftType):
822 """Construct the request for the given type."""
823 super(SetCheckFlightPassed, self).__init__(client, callback)
824 self._aircraftType = aircraftType
825
826 def run(self):
827 """Perform the update."""
828 aircraftType = BookedFlight.TYPE2TYPECODE[self._aircraftType]
829 self._client.setCheckFlightPassed(aircraftType)
830 return Result()
831
832#------------------------------------------------------------------------------
833
[829]834class GetPIREP(RPCRequest):
835 """A request to retrieve the PIREP of a certain flight."""
836 def __init__(self, client, callback, flightID):
837 """Construct the request."""
838 super(GetPIREP, self).__init__(client, callback)
839 self._flightID = flightID
840
841 def run(self):
842 """Perform the update."""
843 result = Result()
844
845 pirepData = self._client.getPIREP(self._flightID)
[919]846 print("pirepData:", pirepData)
[829]847
[1044]848 bookedFlight = BookedFlight(id = self._flightID)
[829]849 bookedFlight.setupFromPIREPData(pirepData)
850
851 result.pirep = PIREP(None)
852 result.pirep.setupFromPIREPData(pirepData, bookedFlight)
853
854 return result
855
856#------------------------------------------------------------------------------
857
[821]858class ReflyFlights(RPCRequest):
859 """A request to mark certain flights for reflying."""
860 def __init__(self, client, callback, flightIDs):
861 """Construct the request."""
862 super(ReflyFlights, self).__init__(client, callback)
863 self._flightIDs = flightIDs
864
865 def run(self):
866 """Perform the update."""
867 self._client.reflyFlights(self._flightIDs)
868 return Result()
869
870#------------------------------------------------------------------------------
871
[824]872class DeleteFlights(RPCRequest):
873 """A request to delete certain flights."""
874 def __init__(self, client, callback, flightIDs):
875 """Construct the request."""
876 super(DeleteFlights, self).__init__(client, callback)
877 self._flightIDs = flightIDs
878
879 def run(self):
880 """Perform the update."""
881 self._client.deleteFlights(self._flightIDs)
882 return Result()
883
884#------------------------------------------------------------------------------
885
[854]886class GetAcceptedFlights(RPCRequest):
887 """Request to get the accepted flights."""
888 def __init__(self, client, callback):
889 """Construct the request with the given client and callback function."""
890 super(GetAcceptedFlights, self).__init__(client, callback)
891
892 def run(self):
893 """Perform the login request."""
894 result = Result()
895
896 result.flights = self._client.getAcceptedFlights()
897
898 return result
899
900#------------------------------------------------------------------------------
901
[858]902class GetTimetable(RPCRequest):
903 """Request to get the timetable."""
904 def __init__(self, client, callback, date, types):
905 """Construct the request with the given client and callback function."""
906 super(GetTimetable, self).__init__(client, callback)
907 self._date = date
908 self._types = types
909
910 def run(self):
911 """Perform the login request."""
912 result = Result()
913
914 result.flightPairs = self._client.getTimetable(self._date, self._types)
915
916 return result
917
918#------------------------------------------------------------------------------
919
[859]920class BookFlights(RPCRequest):
921 """Request to book flights."""
922 def __init__(self, client, callback, flightIDs, date, tailNumber):
923 """Construct the request with the given client and callback function."""
924 super(BookFlights, self).__init__(client, callback)
925 self._flightIDs = flightIDs
926 self._date = date
927 self._tailNumber = tailNumber
928
929 def run(self):
930 """Perform the login request."""
931 result = Result()
932
933 result.bookedFlights = self._client.bookFlights(self._flightIDs,
934 self._date,
935 self._tailNumber)
936
937 return result
938
939#------------------------------------------------------------------------------
940
[1074]941class GetSimBriefResult(RPCRequest):
942 """Request the SimBrief result."""
943 def __init__(self, client, callback, timestamp):
944 """Construct the request with the given client and callback function."""
945 super(GetSimBriefResult, self).__init__(client, callback)
946 self._timestamp = timestamp
947
948 def run(self):
949 """Perform the request."""
950 result = Result()
951
952 result.result = self._client.getSimBriefResult(self._timestamp)
953
954 return result
955
956#------------------------------------------------------------------------------
957
[41]958class Handler(threading.Thread):
959 """The handler for the web services.
960
961 It can process one request at a time. The results are passed to a callback
962 function."""
[1181]963 def __init__(self, config, getCredentialsFn, programDirectory):
[41]964 """Construct the handler."""
965 super(Handler, self).__init__()
966
967 self._requests = []
968 self._requestCondition = threading.Condition()
969
970 self.daemon = True
[746]971 self._config = config
972 self._rpcClient = rpc.Client(getCredentialsFn)
973 if config.rememberPassword:
974 self._rpcClient.setCredentials(config.pilotID, config.password)
[1181]975 self._bugReportTransport = BugReportTransport(programDirectory)
[41]976
[756]977 def register(self, callback, registrationData):
978 """Enqueue a registration request."""
979 self._addRequest(Register(self._rpcClient, callback, registrationData))
980
[760]981 def login(self, callback, pilotID, password):
[41]982 """Enqueue a login request."""
[1044]983 request = LoginRPC(self._rpcClient, callback, pilotID, password)
[746]984
985 self._addRequest(request)
[41]986
[761]987 def getEntryExamStatus(self, callback):
988 """Get the entry exam status."""
989 self._addRequest(GetEntryExamStatus(self._rpcClient, callback))
990
[51]991 def getFleet(self, callback):
992 """Enqueue a fleet retrieval request."""
[1044]993 request = GetFleetRPC(self._rpcClient, callback,)
[746]994 self._addRequest(request)
[443]995
[51]996 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
[443]997 """Update the status of the given plane."""
[1044]998 request = UpdatePlaneRPC(self._rpcClient, callback,
999 tailNumber, status, gateNumber)
[746]1000 self._addRequest(request)
[63]1001
1002 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
1003 """Get the NOTAMs for the given two airports."""
1004 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
[443]1005
[66]1006 def getMETARs(self, callback, airports):
1007 """Get the METARs for the given airports."""
1008 self._addRequest(GetMETARs(callback, airports))
[97]1009
[853]1010 def sendPIREP(self, callback, pirep, update = False):
[97]1011 """Send the given PIREP."""
[1044]1012 request = SendPIREPRPC(self._rpcClient, callback, pirep, update)
[746]1013 self._addRequest(request)
[139]1014
1015 def sendACARS(self, callback, acars):
1016 """Send the given ACARS"""
[1044]1017 request = SendACARSRPC(self._rpcClient, callback, acars)
[746]1018 self._addRequest(request)
[443]1019
[1164]1020 def sendBugReport(self, callback, summary, description, email, debugLog = None):
[484]1021 """Send a bug report with the given data."""
[1164]1022 self._addRequest(SendBugReport(callback, summary, description, email,
[1181]1023 debugLog = debugLog,
1024 transport = self._bugReportTransport))
[484]1025
[769]1026 def setCheckFlightPassed(self, callback, aircraftType):
1027 """Mark the check flight as passed."""
1028 self._addRequest(SetCheckFlightPassed(self._rpcClient,
1029 callback, aircraftType))
1030
[829]1031 def getPIREP(self, callback, flightID):
1032 """Query the PIREP for the given flight."""
1033 self._addRequest(GetPIREP(self._rpcClient, callback, flightID))
1034
[821]1035 def reflyFlights(self, callback, flightIDs):
1036 """Mark the flights with the given IDs for reflying."""
1037 self._addRequest(ReflyFlights(self._rpcClient, callback, flightIDs))
1038
[824]1039 def deleteFlights(self, callback, flightIDs):
1040 """Delete the flights with the given IDs."""
1041 self._addRequest(DeleteFlights(self._rpcClient, callback, flightIDs))
1042
[854]1043 def getAcceptedFlights(self, callback):
1044 """Enqueue a request to get the accepted flights."""
1045 self._addRequest(GetAcceptedFlights(self._rpcClient, callback))
1046
[858]1047 def getTimetable(self, callback, date, types):
1048 """Enqueue a request to get the timetable."""
1049 self._addRequest(GetTimetable(self._rpcClient, callback, date, types))
1050
[859]1051 def bookFlights(self, callback, flightIDs, date, tailNumber):
1052 """Enqueue a request to book some flights."""
1053 self._addRequest(BookFlights(self._rpcClient, callback,
1054 flightIDs, date, tailNumber))
1055
[1074]1056 def getSimBriefResult(self, callback, timestamp):
1057 """Enqueue a request to get the SimBrief result."""
1058 self._addRequest(GetSimBriefResult(self._rpcClient, callback,
1059 timestamp))
1060
[41]1061 def run(self):
1062 """Process the requests."""
1063 while True:
1064 with self._requestCondition:
1065 while not self._requests:
1066 self._requestCondition.wait()
1067 request = self._requests[0]
1068 del self._requests[0]
1069
1070 request.perform()
[443]1071
[41]1072 def _addRequest(self, request):
1073 """Add the given request to the queue."""
1074 with self._requestCondition:
1075 self._requests.append(request)
1076 self._requestCondition.notify()
1077
1078#------------------------------------------------------------------------------
1079
1080if __name__ == "__main__":
1081 import time
[443]1082
[41]1083 def callback(returned, result):
[919]1084 print(returned, str(result))
[443]1085
[41]1086 handler = Handler()
1087 handler.start()
1088
[51]1089 #handler.login(callback, "P096", "V5fwj")
1090 #handler.getFleet(callback)
1091 # Plane: HA-LEG home (gate 67)
[63]1092 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
[443]1093 #time.sleep(3)
[63]1094 #handler.getFleet(callback)
1095 #time.sleep(3)
1096
[66]1097 #handler.getNOTAMs(callback, "LHBP", "EPWA")
[204]1098 #handler.getMETARs(callback, ["LHBP", "EPWA"])
1099 #time.sleep(5)
[443]1100
[204]1101 handler.updatePlane(callback, "HA-LON", const.PLANE_AWAY, "")
[443]1102 time.sleep(3)
[41]1103
1104#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.