source: src/mlx/web.py@ 1181:52dda2b6d0eb

python3 tip
Last change on this file since 1181:52dda2b6d0eb was 1181:52dda2b6d0eb, checked in by István Váradi <ivaradi@…>, 6 days ago

The Trac server can be accessed with password authentication only

File size: 37.9 KB
Line 
1
2from . import const
3from . import util
4from .rpc import Registration, BookedFlight
5from . import rpc
6from . import rpccommon
7
8from .common import MAVA_BASE_URL
9from .pirep import PIREP
10
11import threading
12import sys
13import urllib.request, urllib.parse, urllib.error
14import urllib.request, urllib.error, urllib.parse
15import hashlib
16import time
17import re
18import datetime
19import codecs
20import traceback
21import xml.sax
22import xmlrpc.client
23import html.parser
24import certifi
25import base64
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, cafile=certifi.where())
632 try:
633 data = f.read(16384)
634 while data:
635 parser.feed(str(data))
636 data = f.read(16384)
637 finally:
638 f.close()
639
640 return parser.getNOTAMs()
641
642 except Exception as e:
643 traceback.print_exc()
644 print("mlx.web.GetNOTAMs.getPilotsWebNOTAMs: failed to get NOTAMs for '%s': %s" % \
645 (icao, str(e)))
646 return None
647
648#------------------------------------------------------------------------------
649
650class GetMETARs(Request):
651 """Get the METARs from the NOAA website for certain airport ICAOs."""
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."""
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") ])
664 result = Result()
665 result.metars = {}
666 try:
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)))
682
683 return result
684
685#------------------------------------------------------------------------------
686
687class SendPIREPRPC(RPCRequest):
688 """A request to send a PIREP to the MAVA website via the RPC interface."""
689
690 def __init__(self, client, callback, pirep, update):
691 """Construct the sending of the PIREP."""
692 super(SendPIREPRPC, self).__init__(client, callback)
693 self._pirep = pirep
694 self._update = update
695
696 def run(self):
697 """Perform the sending of the PIREP."""
698 pirep = self._pirep
699 resultCode = self._client.addPIREP(pirep.bookedFlight.id, pirep,
700 self._update)
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."""
720 print("Sending the online ACARS via JSON-RPC")
721
722 self._client.updateOnlineACARS(self._acars)
723 return Result()
724
725#------------------------------------------------------------------------------
726
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
779class SendBugReport(Request):
780 """A request to send a bug report to the project homepage."""
781 _latin2Encoder = codecs.getencoder("iso-8859-2")
782
783 def __init__(self, callback, summary, description, email, debugLog,
784 transport):
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
790 self._debugLog = debugLog
791 self._transport = transport
792
793 def run(self):
794 """Perform the sending of the bug report."""
795 serverProxy = xmlrpc.client.ServerProxy("https://mlx.varadiistvan.hu/login/rpc",
796 transport = self._transport)
797 result = Result()
798 result.success = False
799
800 attributes = {}
801 if self._email:
802 attributes["reporter"] = self._email
803
804 result.ticketID = serverProxy.ticket.create(self._summary, self._description,
805 attributes, True)
806 print("Created ticket with ID:", result.ticketID)
807 if self._debugLog:
808 serverProxy.ticket.putAttachment(result.ticketID, "debug.log",
809 "Debug log",
810 bytes(self._debugLog, "utf-8"))
811
812
813 result.success = True
814
815 return result
816
817#------------------------------------------------------------------------------
818
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
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)
846 print("pirepData:", pirepData)
847
848 bookedFlight = BookedFlight(id = self._flightID)
849 bookedFlight.setupFromPIREPData(pirepData)
850
851 result.pirep = PIREP(None)
852 result.pirep.setupFromPIREPData(pirepData, bookedFlight)
853
854 return result
855
856#------------------------------------------------------------------------------
857
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
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
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
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
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
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
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."""
963 def __init__(self, config, getCredentialsFn, programDirectory):
964 """Construct the handler."""
965 super(Handler, self).__init__()
966
967 self._requests = []
968 self._requestCondition = threading.Condition()
969
970 self.daemon = True
971 self._config = config
972 self._rpcClient = rpc.Client(getCredentialsFn)
973 if config.rememberPassword:
974 self._rpcClient.setCredentials(config.pilotID, config.password)
975 self._bugReportTransport = BugReportTransport(programDirectory)
976
977 def register(self, callback, registrationData):
978 """Enqueue a registration request."""
979 self._addRequest(Register(self._rpcClient, callback, registrationData))
980
981 def login(self, callback, pilotID, password):
982 """Enqueue a login request."""
983 request = LoginRPC(self._rpcClient, callback, pilotID, password)
984
985 self._addRequest(request)
986
987 def getEntryExamStatus(self, callback):
988 """Get the entry exam status."""
989 self._addRequest(GetEntryExamStatus(self._rpcClient, callback))
990
991 def getFleet(self, callback):
992 """Enqueue a fleet retrieval request."""
993 request = GetFleetRPC(self._rpcClient, callback,)
994 self._addRequest(request)
995
996 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
997 """Update the status of the given plane."""
998 request = UpdatePlaneRPC(self._rpcClient, callback,
999 tailNumber, status, gateNumber)
1000 self._addRequest(request)
1001
1002 def getNOTAMs(self, callback, departureICAO, arrivalICAO):
1003 """Get the NOTAMs for the given two airports."""
1004 self._addRequest(GetNOTAMs(callback, departureICAO, arrivalICAO))
1005
1006 def getMETARs(self, callback, airports):
1007 """Get the METARs for the given airports."""
1008 self._addRequest(GetMETARs(callback, airports))
1009
1010 def sendPIREP(self, callback, pirep, update = False):
1011 """Send the given PIREP."""
1012 request = SendPIREPRPC(self._rpcClient, callback, pirep, update)
1013 self._addRequest(request)
1014
1015 def sendACARS(self, callback, acars):
1016 """Send the given ACARS"""
1017 request = SendACARSRPC(self._rpcClient, callback, acars)
1018 self._addRequest(request)
1019
1020 def sendBugReport(self, callback, summary, description, email, debugLog = None):
1021 """Send a bug report with the given data."""
1022 self._addRequest(SendBugReport(callback, summary, description, email,
1023 debugLog = debugLog,
1024 transport = self._bugReportTransport))
1025
1026 def setCheckFlightPassed(self, callback, aircraftType):
1027 """Mark the check flight as passed."""
1028 self._addRequest(SetCheckFlightPassed(self._rpcClient,
1029 callback, aircraftType))
1030
1031 def getPIREP(self, callback, flightID):
1032 """Query the PIREP for the given flight."""
1033 self._addRequest(GetPIREP(self._rpcClient, callback, flightID))
1034
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
1039 def deleteFlights(self, callback, flightIDs):
1040 """Delete the flights with the given IDs."""
1041 self._addRequest(DeleteFlights(self._rpcClient, callback, flightIDs))
1042
1043 def getAcceptedFlights(self, callback):
1044 """Enqueue a request to get the accepted flights."""
1045 self._addRequest(GetAcceptedFlights(self._rpcClient, callback))
1046
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
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
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
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()
1071
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
1082
1083 def callback(returned, result):
1084 print(returned, str(result))
1085
1086 handler = Handler()
1087 handler.start()
1088
1089 #handler.login(callback, "P096", "V5fwj")
1090 #handler.getFleet(callback)
1091 # Plane: HA-LEG home (gate 67)
1092 #handler.updatePlane(callback, "HA-LQC", const.PLANE_AWAY, "72")
1093 #time.sleep(3)
1094 #handler.getFleet(callback)
1095 #time.sleep(3)
1096
1097 #handler.getNOTAMs(callback, "LHBP", "EPWA")
1098 #handler.getMETARs(callback, ["LHBP", "EPWA"])
1099 #time.sleep(5)
1100
1101 handler.updatePlane(callback, "HA-LON", const.PLANE_AWAY, "")
1102 time.sleep(3)
1103
1104#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.