source: src/mlx/rpc.py@ 1044:988bfea3e8db

python3
Last change on this file since 1044:988bfea3e8db was 1044:988bfea3e8db, checked in by István Váradi <ivaradi@…>, 3 years ago

RPC is the only communication method (re #357)

File size: 35.6 KB
Line 
1from . import const
2from . import rpccommon
3
4from .common import MAVA_BASE_URL, fixUnpickled
5
6import jsonrpclib
7import hashlib
8import datetime
9import calendar
10import sys
11import ssl
12import certifi
13
14#---------------------------------------------------------------------------------------
15
16class RPCObject(object):
17 """Base class for objects read from RPC calls.
18
19 It is possible to construct it from a dictionary."""
20 def __init__(self, value, instructions = {}):
21 """Construct the object.
22
23 value is the dictionary returned by the call.
24
25 info is a mapping from names to 'instructions' on what to do with the
26 corresponding values. If the instruction is None, it will be ignored.
27 If the instruction is a function, the value will be passed to it and
28 the return value will be stored in the object.
29
30 For all other names, the value will be stored as the same-named
31 attribute."""
32 for (key, value) in value.items():
33 if key in instructions:
34 instruction = instructions[key]
35 if instruction is None:
36 continue
37
38 try:
39 value = instruction(value)
40 except:
41 print("Failed to convert value '%s' of attribute '%s':" % \
42 (value, key), file=sys.stderr)
43 import traceback
44 traceback.print_exc()
45 setattr(self, key, value)
46
47#---------------------------------------------------------------------------------------
48
49class Reply(RPCObject):
50 """The generic reply structure."""
51
52#---------------------------------------------------------------------------------------
53
54class ScheduledFlight(RPCObject):
55 """A scheduled flight in the time table."""
56 # The instructions for the construction
57 # Type: normal flight
58 TYPE_NORMAL = 0
59
60 # Type: VIP flight
61 TYPE_VIP = 1
62
63 _instructions = {
64 "id" : int,
65 "pairID": int,
66 "typeCode": lambda value: BookedFlight._decodeAircraftType(value),
67 "departureTime": lambda value: ScheduledFlight._decodeTime(value),
68 "arrivalTime": lambda value: ScheduledFlight._decodeTime(value),
69 "duration": lambda value: ScheduledFlight._decodeDuration(value),
70 "spec": int,
71 "validFrom": lambda value: ScheduledFlight._decodeDate(value),
72 "validTo": lambda value: ScheduledFlight._decodeDate(value),
73 "date": lambda value: ScheduledFlight._decodeDate(value)
74 }
75
76 @staticmethod
77 def _decodeTime(value):
78 """Decode the given value as a time value."""
79 return datetime.datetime.strptime(value, "%H:%M:%S").time()
80
81 @staticmethod
82 def _decodeDate(value):
83 """Decode the given value as a date value."""
84 if not value or value=="0000-00-00":
85 return const.defaultDate
86 else:
87 return datetime.datetime.strptime(value, "%Y-%m-%d").date()
88
89 @staticmethod
90 def _decodeDuration(value):
91 """Decode the given value as a duration.
92
93 A number of seconds will be returned."""
94 t = datetime.datetime.strptime(value, "%H:%M:%S")
95 return (t.hour*60 + t.minute) * 60 + t.second
96
97 def __init__(self, value):
98 """Construct the scheduled flight object from the given JSON value."""
99 super(ScheduledFlight, self).__init__(value,
100 ScheduledFlight._instructions)
101 self.aircraftType = self.typeCode
102 del self.typeCode
103
104 self.type = ScheduledFlight.TYPE_VIP if self.spec==1 \
105 else ScheduledFlight.TYPE_NORMAL
106 del self.spec
107
108 def compareBy(self, other, name):
109 """Compare this flight with the other one according to the given
110 attribute name."""
111 if name=="callsign":
112 try:
113 cs1 = int(self.callsign[2:])
114 cs2 = int(other.callsign[2:])
115 return 0 if cs1==cs2 else -1 if cs1<cs2 else 1
116 except:
117 return 0 if self.callsign==other.callsign \
118 else -1 if self.callsign<other.callsign else 1
119 else:
120 v1 = getattr(self, name)
121 v2 = getattr(other, name)
122 return 0 if v1==v2 else -1 if v1<v2 else 1
123
124 def __repr__(self):
125 return "ScheduledFlight<%d, %d, %s, %s (%s) - %s (%s) -> %d, %d>" % \
126 (self.id, self.pairID, BookedFlight.TYPE2TYPECODE[self.aircraftType],
127 self.departureICAO, str(self.departureTime),
128 self.arrivalICAO, str(self.arrivalTime),
129 self.duration, self.type)
130
131#---------------------------------------------------------------------------------------
132
133class ScheduledFlightPair(object):
134 """A pair of scheduled flights.
135
136 Occasionally, one of the flights may be missing."""
137 @staticmethod
138 def scheduledFlights2Pairs(scheduledFlights, date):
139 """Convert the given list of scheduled flights into a list of flight
140 pairs."""
141 weekday = str(date.weekday()+1)
142
143 flights = {}
144 weekdayFlights = {}
145 for flight in scheduledFlights:
146 flights[flight.id] = flight
147 if (flight.type==ScheduledFlight.TYPE_NORMAL and
148 flight.arrivalICAO!="LHBP" and weekday in flight.days and
149 flight.validFrom<=date and flight.validTo>=date) or \
150 flight.type==ScheduledFlight.TYPE_VIP:
151 weekdayFlights[flight.id] = flight
152
153 flightPairs = []
154
155 while weekdayFlights:
156 (id, flight) = weekdayFlights.popitem()
157 if flight.type==ScheduledFlight.TYPE_NORMAL:
158 pairID = flight.pairID
159 if pairID in flights:
160 pairFlight = flights[pairID]
161 if flight.departureICAO=="LHBP" or \
162 (pairFlight.departureICAO!="LHBP" and
163 flight.callsign<pairFlight.callsign):
164 flightPairs.append(ScheduledFlightPair(flight, pairFlight))
165 else:
166 flightPairs.append(ScheduledFlightPair(pairFlight, flight))
167 del flights[pairID]
168 if pairID in weekdayFlights:
169 del weekdayFlights[pairID]
170 elif flight.type==ScheduledFlight.TYPE_VIP:
171 flightPairs.append(ScheduledFlightPair(flight))
172
173 flightPairs.sort(key = lambda pair: pair.flight0.date)
174
175 return flightPairs
176
177 def __init__(self, flight0, flight1 = None):
178 """Construct the pair with the given flights."""
179 self.flight0 = flight0
180 self.flight1 = flight1
181
182 def compareBy(self, other, name):
183 """Compare this flight pair with the other one according to the given
184 attribute name, considering the first flights."""
185 return self.flight0.compareBy(other.flight0, name)
186
187 def __repr__(self):
188 return "ScheduledFlightPair<%s, %s, %s>" % \
189 (self.flight0.callsign, self.flight0.departureICAO,
190 self.flight0.arrivalICAO)
191
192#---------------------------------------------------------------------------------------
193
194class BookedFlight(RPCObject):
195 """A booked flight."""
196 TYPECODE2TYPE = { "B736" : const.AIRCRAFT_B736,
197 "736" : const.AIRCRAFT_B736,
198 "B737" : const.AIRCRAFT_B737,
199 "73G" : const.AIRCRAFT_B737,
200 "B738" : const.AIRCRAFT_B738,
201 "738" : const.AIRCRAFT_B738,
202 "B73H" : const.AIRCRAFT_B738C,
203 "73H" : const.AIRCRAFT_B738C,
204 "B732" : const.AIRCRAFT_B732,
205 "732" : const.AIRCRAFT_B732,
206 "B733" : const.AIRCRAFT_B733,
207 "733" : const.AIRCRAFT_B733,
208 "B734" : const.AIRCRAFT_B734,
209 "734" : const.AIRCRAFT_B734,
210 "B735" : const.AIRCRAFT_B735,
211 "735" : const.AIRCRAFT_B735,
212 "DH8D" : const.AIRCRAFT_DH8D,
213 "DH4" : const.AIRCRAFT_DH8D,
214 "B762" : const.AIRCRAFT_B762,
215 "762" : const.AIRCRAFT_B762,
216 "B763" : const.AIRCRAFT_B763,
217 "763" : const.AIRCRAFT_B763,
218 "CRJ2" : const.AIRCRAFT_CRJ2,
219 "CR2" : const.AIRCRAFT_CRJ2,
220 "F70" : const.AIRCRAFT_F70,
221 "LI2" : const.AIRCRAFT_DC3,
222 "T134" : const.AIRCRAFT_T134,
223 "TU3" : const.AIRCRAFT_T134,
224 "T154" : const.AIRCRAFT_T154,
225 "TU5" : const.AIRCRAFT_T154,
226 "YK40" : const.AIRCRAFT_YK40,
227 "YK4" : const.AIRCRAFT_YK40,
228 "B462" : const.AIRCRAFT_B462,
229 "146" : const.AIRCRAFT_B462,
230 "IL62" : const.AIRCRAFT_IL62 }
231
232 TYPE2TYPECODE = { const.AIRCRAFT_B736 : "B736",
233 const.AIRCRAFT_B737 : "B737",
234 const.AIRCRAFT_B738 : "B738",
235 const.AIRCRAFT_B738C : "B73H",
236 const.AIRCRAFT_B732 : "B732",
237 const.AIRCRAFT_B733 : "B733",
238 const.AIRCRAFT_B734 : "B734",
239 const.AIRCRAFT_B735 : "B735",
240 const.AIRCRAFT_DH8D : "DH8D",
241 const.AIRCRAFT_B762 : "B762",
242 const.AIRCRAFT_B763 : "B763",
243 const.AIRCRAFT_CRJ2 : "CRJ2",
244 const.AIRCRAFT_F70 : "F70",
245 const.AIRCRAFT_DC3 : "LI2",
246 const.AIRCRAFT_T134 : "T134",
247 const.AIRCRAFT_T154 : "T155",
248 const.AIRCRAFT_YK40 : "YK40",
249 const.AIRCRAFT_B462 : "B462",
250 const.AIRCRAFT_IL62 : "IL62" }
251
252 checkFlightTypes = [ const.AIRCRAFT_B736, const.AIRCRAFT_B737,
253 const.AIRCRAFT_B738, const.AIRCRAFT_DH8D ]
254
255 @staticmethod
256 def _decodeAircraftType(typeCode):
257 """Decode the aircraft type from the given typeCode."""
258 if typeCode in BookedFlight.TYPECODE2TYPE:
259 return BookedFlight.TYPECODE2TYPE[typeCode]
260 else:
261 raise Exception("Invalid aircraft type code: '" + typeCode + "'")
262
263 @staticmethod
264 def _decodeStatus(status):
265 """Decode the status from the status string."""
266 if status=="booked":
267 return BookedFlight.STATUS_BOOKED
268 elif status=="reported":
269 return BookedFlight.STATUS_REPORTED
270 elif status=="accepted":
271 return BookedFlight.STATUS_ACCEPTED
272 elif status=="rejected":
273 return BookedFlight.STATUS_REJECTED
274 else:
275 raise Exception("Invalid flight status code: '" + status + "'")
276
277 @staticmethod
278 def _convertFlightType(ft):
279 """Convert the in-database flight-type to one of our constants."""
280 ft = int(ft)
281 if ft==0:
282 return const.FLIGHTTYPE_SCHEDULED
283 elif ft==1:
284 return const.FLIGHTTYPE_VIP
285 elif ft==2:
286 return const.FLIGHTTYPE_CHARTER
287 else:
288 return const.FLIGHTTYPE_SCHEDULED
289
290 @staticmethod
291 def getDateTime(date, time):
292 """Get a datetime object from the given textual date and time."""
293 return datetime.datetime.strptime(date + " " + time,
294 "%Y-%m-%d %H:%M:%S")
295
296 STATUS_BOOKED = 1
297
298 STATUS_REPORTED = 2
299
300 STATUS_ACCEPTED = 3
301
302 STATUS_REJECTED = 4
303
304 @staticmethod
305 def forCheckFlight(aircraftType):
306 """Create a booked flight for a check flight with the given aircraft
307 type."""
308 flight = BookedFlight()
309
310 flight.departureICAO = "LHBP"
311 flight.arrivalICAO = "LHBP"
312
313 flight.aircraftType = aircraftType
314 flight.aircraftTypeName = BookedFlight.TYPE2TYPECODE[aircraftType]
315
316 # FIXME: perhaps find one for the type
317 flight.tailNumber = "HA-CHK"
318 flight.callsign = "HA-CHK"
319
320 flight.numPassengers = 0
321 flight.numChildren = 0
322 flight.numInfants = 0
323 flight.numCabinCrew = 0
324 flight.bagWeight = 0
325 flight.cargoWeight = 0
326 flight.mailWeight = 0
327 flight.route = "DCT"
328
329 t = datetime.datetime.now() + datetime.timedelta(minutes = 20)
330 flight.departureTime = datetime.datetime(t.year, t.month, t.day,
331 t.hour, t.minute)
332 t = flight.departureTime + datetime.timedelta(minutes = 30)
333 flight.arrivalTime = datetime.datetime(t.year, t.month, t.day,
334 t.hour, t.minute)
335
336 return flight
337
338 # The instructions for the construction
339 _instructions = {
340 "numPassengers" : int,
341 "numChildren" : int,
342 "numInfants" : int,
343 "numCabinCrew" : int,
344 "dowNumCabinCrew" : int,
345 "numCockpitCrew" : int,
346 "bagWeight" : int,
347 "cargoWeight" : int,
348 "mailWeight" : int,
349 "flightType" : lambda value: BookedFlight._convertFlightType(value),
350 "dow": int,
351 "maxPassengers": int,
352 "aircraftType" : lambda value: BookedFlight._decodeAircraftType(value),
353 "status" : lambda value: BookedFlight._decodeStatus(value)
354 }
355
356 def __init__(self, value = None, id = None):
357 """Construct the booked flight object from the given RPC result
358 value."""
359 self.status = BookedFlight.STATUS_BOOKED
360 if value is None:
361 self.id = id
362 else:
363 super(BookedFlight, self).__init__(value, BookedFlight._instructions)
364 self.departureTime = \
365 BookedFlight.getDateTime(self.date, self.departureTime)
366 self.arrivalTime = \
367 BookedFlight.getDateTime(self.date, self.arrivalTime)
368 if self.arrivalTime<self.departureTime:
369 self.arrivalTime += datetime.timedelta(days = 1)
370
371 def readFromFile(self, f, fleet):
372 """Read the data of the flight from a file via the given file
373 object."""
374 date = None
375 departureTime = None
376 arrivalTime = None
377
378 line = f.readline()
379 lineNumber = 0
380 self.numChildren = 0
381 self.numInfants = 0
382 self.numCockpitCrew = 2
383 self.flightType = const.FLIGHTTYPE_SCHEDULED
384 while line:
385 lineNumber += 1
386 line = line.strip()
387
388 hashIndex = line.find("#")
389 if hashIndex>=0: line = line[:hashIndex]
390 if line:
391 equalIndex = line.find("=")
392 lineOK = equalIndex>0
393
394 if lineOK:
395 key = line[:equalIndex].strip()
396 value = line[equalIndex+1:].strip().replace("\:", ":")
397
398 lineOK = key and value
399
400 if lineOK:
401 if key=="callsign": self.callsign = value
402 elif key=="date": date = value
403 elif key=="dep_airport": self.departureICAO = value
404 elif key=="dest_airport": self.arrivalICAO = value
405 elif key=="planecode": self.aircraftType = \
406 BookedFlight._decodeAircraftType(value)
407 elif key=="planetype": self.aircraftTypeName = value
408 elif key=="tail_nr": self.tailNumber = value
409 elif key=="passenger": self.numPassengers = int(value)
410 elif key=="child": self.numChildren = int(value)
411 elif key=="infant": self.numInfants = int(value)
412 elif key=="crew": self.numCabinCrew = int(value) - 2
413 elif key=="cabin_crew": self.numCabinCrew = int(value)
414 elif key=="cockpit_crew": self.numCockpitCrew = int(value)
415 elif key=="bag": self.bagWeight = int(value)
416 elif key=="cargo": self.cargoWeight = int(value)
417 elif key=="mail": self.mailWeight = int(value)
418 elif key=="flight_route": self.route = value
419 elif key=="departure_time": departureTime = value
420 elif key=="arrival_time": arrivalTime = value
421 elif key=="foglalas_id":
422 self.id = None if value=="0" else value
423 elif key=="flight_type": self.flightType = int(value)
424 else: lineOK = False
425
426 if not lineOK:
427 print("web.BookedFlight.readFromFile: line %d is invalid" % \
428 (lineNumber,))
429
430 line = f.readline()
431
432 if date is not None:
433 if departureTime is not None:
434 self.departureTime = BookedFlight.getDateTime(date,
435 departureTime)
436 if arrivalTime is not None:
437 self.arrivalTime = BookedFlight.getDateTime(date,
438 arrivalTime)
439
440 d = dir(self)
441 for attribute in ["callsign", "departureICAO", "arrivalICAO",
442 "aircraftType", "tailNumber",
443 "numPassengers", "numCockpitCrew", "numCabinCrew",
444 "bagWeight", "cargoWeight", "mailWeight",
445 "route", "departureTime", "arrivalTime"]:
446 if attribute not in d:
447 raise Exception("Attribute %s could not be read" % (attribute,))
448
449 if "aircraftTypeName" not in d:
450 self.aircraftTypeName = \
451 BookedFlight.TYPE2TYPECODE[self.aircraftType]
452
453 plane = fleet[self.tailNumber]
454 if plane is None:
455 self.dow = 0
456 self.maxPassengers = 0
457 self.dowNumCabinCrew = 0
458 else:
459 self.dow = plane.dow
460 self.maxPassengers = plane.maxPassengers
461 self.dowNumCabinCrew = plane.dowNumCabinCrew
462
463 def setupFromPIREPData(self, pirepData):
464 """Setup the booked flight from the given PIREP data."""
465 bookedFlightData = pirepData["bookedFlight"]
466
467 self.callsign = bookedFlightData["callsign"]
468
469 date = bookedFlightData["date"]
470
471 departureTime = bookedFlightData["departureTime"]
472 self.departureTime = BookedFlight.getDateTime(date, departureTime)
473
474 arrivalTime = bookedFlightData["arrivalTime"]
475 self.arrivalTime = BookedFlight.getDateTime(date, arrivalTime)
476 if self.arrivalTime<self.departureTime:
477 self.arrivalTime += datetime.timedelta(days = 1)
478
479 self.departureICAO = bookedFlightData["departureICAO"]
480 self.arrivalICAO = bookedFlightData["arrivalICAO"]
481
482 self.aircraftType = \
483 BookedFlight._decodeAircraftType(bookedFlightData["aircraftType"])
484 self.tailNumber = bookedFlightData["tailNumber"]
485 self.numPassengers = int(bookedFlightData["numPassengers"])
486 self.numChildren = int(bookedFlightData["numChildren"])
487 self.numInfants = int(bookedFlightData["numInfants"])
488 self.maxPassengers = int(bookedFlightData["maxPassengers"])
489 self.numCockpitCrew = int(bookedFlightData["numCockpitCrew"])
490 self.numCabinCrew = int(bookedFlightData["numCabinCrew"])
491 self.bagWeight = int(bookedFlightData["bagWeight"])
492 self.cargoWeight = int(bookedFlightData["cargoWeight"])
493 self.mailWeight = int(bookedFlightData["mailWeight"])
494 self.route = bookedFlightData["route"]
495 self.flightType = BookedFlight._convertFlightType(bookedFlightData["flightType"])
496
497 def writeIntoFile(self, f):
498 """Write the flight into a file."""
499 print("callsign=%s" % (self.callsign,), file=f)
500 date = self.departureTime.date()
501 print("date=%04d-%02d-%0d" % (date.year, date.month, date.day), file=f)
502 print("dep_airport=%s" % (self.departureICAO,), file=f)
503 print("dest_airport=%s" % (self.arrivalICAO,), file=f)
504 print("planecode=%s" % \
505 (BookedFlight.TYPE2TYPECODE[self.aircraftType],), file=f)
506 print("planetype=%s" % (self.aircraftTypeName,), file=f)
507 print("tail_nr=%s" % (self.tailNumber,), file=f)
508 print("passenger=%d" % (self.numPassengers,), file=f)
509 print("child=%d" % (self.numChildren,), file=f)
510 print("infant=%d" % (self.numInfants,), file=f)
511 print("cockpit_crew=%d" % (self.numCockpitCrew,), file=f)
512 print("cabin_crew=%d" % (self.numCabinCrew,), file=f)
513 print("bag=%d" % (self.bagWeight,), file=f)
514 print("cargo=%d" % (self.cargoWeight,), file=f)
515 print("mail=%d" % (self.mailWeight,), file=f)
516 print("flight_route=%s" % (self.route,), file=f)
517 departureTime = self.departureTime
518 print("departure_time=%02d\\:%02d\\:%02d" % \
519 (departureTime.hour, departureTime.minute, departureTime.second), file=f)
520 arrivalTime = self.arrivalTime
521 print("arrival_time=%02d\\:%02d\\:%02d" % \
522 (arrivalTime.hour, arrivalTime.minute, arrivalTime.second), file=f)
523 print("foglalas_id=%s" % ("0" if self.id is None else self.id,), file=f)
524 print("flight_type=%d" % (self.flightType,))
525
526 def __setstate__(self, state):
527 """Set the state from the given unpickled dictionary."""
528 self.__dict__.update(fixUnpickled(state))
529
530 def __repr__(self):
531 """Get a representation of the flight."""
532 s = "<Flight: %s-%s, %s, %s-%s," % (self.departureICAO,
533 self.arrivalICAO,
534 self.route,
535 self.departureTime, self.arrivalTime)
536 s += " %d %s," % (self.aircraftType, self.tailNumber)
537 s += " pax=%d, crew=%d, bag=%d, cargo=%d, mail=%d" % \
538 (self.numPassengers, self.numCrew,
539 self.bagWeight, self.cargoWeight, self.mailWeight)
540 s += ">"
541 return s
542
543#---------------------------------------------------------------------------------------
544
545class AcceptedFlight(RPCObject):
546 """A flight that has been already accepted."""
547 # The instructions for the construction
548 @staticmethod
549 def parseTimestamp(s):
550 """Parse the given RPC timestamp."""
551 dt = datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
552 return calendar.timegm(dt.utctimetuple())
553
554 _instructions = {
555 "bookedFlight" : lambda value: BookedFlight(value),
556 "numPassengers" : int,
557 "numChildren" : int,
558 "numInfants" : int,
559 "fuelUsed" : int,
560 "rating" : lambda value: float(value) if value else 0.0
561 }
562
563 def __init__(self, value):
564 """Construct the booked flight object from the given RPC result
565 value."""
566 super(AcceptedFlight, self).__init__(value, AcceptedFlight._instructions)
567 self.flightTimeStart = \
568 AcceptedFlight.parseTimestamp(self.flightDate + " " +
569 self.flightTimeStart)
570 self.flightTimeEnd = \
571 AcceptedFlight.parseTimestamp(self.flightDate + " " +
572 self.flightTimeEnd)
573 if self.flightTimeEnd<self.flightTimeStart:
574 self.flightTimeEnd += 24*60*60
575
576 self.totalNumPassengers = self.numPassengers + self.numChildren + self.numInfants
577
578#---------------------------------------------------------------------------------------
579
580class Plane(rpccommon.Plane, RPCObject):
581 """An airplane in the fleet."""
582 _instructions = {
583 "status" : lambda value: rpccommon.Plane.str2status(value),
584 "gateNumber" : lambda value: value if value else None,
585 "typeCode": lambda value: BookedFlight._decodeAircraftType(value),
586 "dow": int,
587 "dowNumCabinCrew": int,
588 "maxPassengers": int,
589 }
590
591 def __init__(self, value):
592 """Construct the plane."""
593 RPCObject.__init__(self, value, instructions = Plane._instructions)
594 self.aircraftType = self.typeCode
595 del self.typeCode
596
597#---------------------------------------------------------------------------------------
598
599class Fleet(rpccommon.Fleet):
600 """The fleet."""
601 def __init__(self, value):
602 """Construct the fleet."""
603 super(Fleet, self).__init__()
604 for planeValue in value:
605 self._addPlane(Plane(planeValue))
606
607#---------------------------------------------------------------------------------------
608
609class Registration(object):
610 """Data for registration."""
611 def __init__(self, surName, firstName, nameOrder,
612 yearOfBirth, emailAddress, emailAddressPublic,
613 vatsimID, ivaoID, phoneNumber, nationality, password):
614 """Construct the registration data."""
615 self.surName = surName
616 self.firstName = firstName
617 self.nameOrder = nameOrder
618 self.yearOfBirth = yearOfBirth
619 self.emailAddress = emailAddress
620 self.emailAddressPublic = 1 if emailAddressPublic is True else \
621 0 if emailAddressPublic is False else emailAddressPublic
622 self.vatsimID = "" if vatsimID is None else vatsimID
623 self.ivaoID = "" if ivaoID is None else ivaoID
624 self.phoneNumber = phoneNumber
625 self.nationality = nationality
626 self.password = password
627
628#---------------------------------------------------------------------------------------
629
630class RPCException(Exception):
631 """An exception thrown by RPC operations."""
632 def __init__(self, result, message = None):
633 """Construct the exception."""
634 self._result = result
635 if message is None:
636 message = "RPC call failed with result code: %d" % (result,)
637 super(RPCException, self).__init__(message)
638
639 @property
640 def result(self):
641 """Get the result code."""
642 return self._result
643
644#---------------------------------------------------------------------------------------
645
646class Client(object):
647 """The RPC client interface."""
648 # The client protocol version
649 VERSION = 2
650
651 # Result code: OK
652 RESULT_OK = 0
653
654 # Result code: the login has failed
655 RESULT_LOGIN_FAILED = 1
656
657 # Result code: the given session ID is unknown (it might have expired).
658 RESULT_SESSION_INVALID = 2
659
660 # Result code: some database error
661 RESULT_DATABASE_ERROR = 3
662
663 # Result code: invalid data
664 RESULT_INVALID_DATA = 4
665
666 # Result code: the flight does not exist
667 RESULT_FLIGHT_NOT_EXISTS = 101
668
669 # Result code: the flight has already been reported.
670 RESULT_FLIGHT_ALREADY_REPORTED = 102
671
672 # Result code: a user with the given e-mail address already exists
673 RESULT_EMAIL_ALREADY_REGISTERED = 103
674
675 def __init__(self, getCredentialsFn):
676 """Construct the client."""
677 self._getCredentialsFn = getCredentialsFn
678
679 sslContext = ssl.SSLContext()
680 sslContext.load_verify_locations(cafile = certifi.where())
681 transport = jsonrpclib.jsonrpc.SafeTransport(jsonrpclib.config.DEFAULT,
682 sslContext)
683
684 self._server = jsonrpclib.Server(MAVA_BASE_URL + "/jsonrpc.php",
685 transport = transport)
686
687 self._userName = None
688 self._passwordHash = None
689 self._sessionID = None
690 self._loginCount = 0
691
692 @property
693 def valid(self):
694 """Determine if the client is valid, i.e. there is a session ID
695 stored."""
696 return self._sessionID is not None
697
698 def setCredentials(self, userName, password):
699 """Set the credentials for future logins."""
700
701 self._userName = userName
702
703 md5 = hashlib.md5()
704 md5.update(password.encode("utf-8"))
705 self._passwordHash = md5.hexdigest()
706
707 self._sessionID = None
708
709 def register(self, registrationData):
710 """Register with the given data.
711
712 Returns a tuple of:
713 - the error code,
714 - the PID if there is no error."""
715 reply = Reply(self._server.register(registrationData))
716
717 return (reply.result,
718 reply.value["pid"] if reply.result==Client.RESULT_OK else None)
719
720 def login(self):
721 """Login using the given previously set credentials.
722
723 The session ID is stored in the object and used for later calls.
724
725 Returns the name of the pilot on success, or None on error."""
726 self._sessionID = None
727
728 reply = Reply(self._server.login(self._userName, self._passwordHash,
729 Client.VERSION))
730 if reply.result == Client.RESULT_OK:
731 self._loginCount += 1
732 self._sessionID = reply.value["sessionID"]
733
734 types = [BookedFlight.TYPECODE2TYPE[typeCode]
735 for typeCode in reply.value["typeCodes"]]
736
737 return (reply.value["name"], reply.value["rank"], types)
738 else:
739 return None
740
741 def getFlights(self):
742 """Get the flights available for performing."""
743 bookedFlights = []
744 reportedFlights = []
745 rejectedFlights = []
746
747 value = self._performCall(lambda sessionID:
748 self._server.getFlights(sessionID))
749 for flightData in value:
750 flight = BookedFlight(flightData)
751 if flight.status == BookedFlight.STATUS_BOOKED:
752 bookedFlights.append(flight)
753 elif flight.status == BookedFlight.STATUS_REPORTED:
754 reportedFlights.append(flight)
755 elif flight.status == BookedFlight.STATUS_REJECTED:
756 rejectedFlights.append(flight)
757
758 for flights in [bookedFlights, reportedFlights, rejectedFlights]:
759 flights.sort(key = lambda flight: flight.departureTime)
760
761 return (bookedFlights, reportedFlights, rejectedFlights)
762
763 def getAcceptedFlights(self):
764 """Get the flights that are already accepted."""
765 value = self._performCall(lambda sessionID:
766 self._server.getAcceptedFlights(sessionID))
767 flights = []
768 for flight in value:
769 flights.append(AcceptedFlight(flight))
770 return flights
771
772 def getEntryExamStatus(self):
773 """Get the status of the exams needed for joining MAVA."""
774 value = self._performCall(lambda sessionID:
775 self._server.getEntryExamStatus(sessionID))
776 return (value["entryExamPassed"], value["entryExamLink"],
777 value["checkFlightStatus"], value["madeFO"])
778
779 def getFleet(self):
780 """Query and return the fleet."""
781 value = self._performCall(lambda sessionID:
782 self._server.getFleet(sessionID))
783
784 return Fleet(value)
785
786 def updatePlane(self, tailNumber, status, gateNumber):
787 """Update the state and position of the plane with the given tail
788 number."""
789 status = rpccommon.Plane.status2str(status)
790 self._performCall(lambda sessionID:
791 self._server.updatePlane(sessionID, tailNumber,
792 status, gateNumber))
793
794 def addPIREP(self, flightID, pirep, update = False):
795 """Add the PIREP for the given flight."""
796 (result, _value) = \
797 self._performCall(lambda sessionID:
798 self._server.addPIREP(sessionID, flightID, pirep,
799 update),
800 acceptResults = [Client.RESULT_FLIGHT_ALREADY_REPORTED,
801 Client.RESULT_FLIGHT_NOT_EXISTS])
802 return result
803
804 def updateOnlineACARS(self, acars):
805 """Update the online ACARS from the given data."""
806 self._performCall(lambda sessionID:
807 self._server.updateOnlineACARS(sessionID, acars))
808
809 def setCheckFlightPassed(self, type):
810 """Mark the check flight of the user passed with the given type."""
811 self._performCall(lambda sessionID:
812 self._server.setCheckFlightPassed(sessionID, type))
813
814 def getPIREP(self, flightID):
815 """Get the PIREP data for the flight with the given ID."""
816 value = self._performCall(lambda sessionID:
817 self._server.getPIREP(sessionID, flightID))
818 return value
819
820 def reflyFlights(self, flightIDs):
821 """Mark the flights with the given IDs for reflying."""
822 self._performCall(lambda sessionID:
823 self._server.reflyFlights(sessionID, flightIDs))
824
825 def deleteFlights(self, flightIDs):
826 """Delete the flights with the given IDs."""
827 self._performCall(lambda sessionID:
828 self._server.deleteFlights(sessionID, flightIDs))
829
830 def getTimetable(self, date, types = None):
831 """Get the time table for the given date restricted to the given list
832 of type codes, if any."""
833 typeCodes = None if types is None else \
834 [BookedFlight.TYPE2TYPECODE[type] for type in types]
835
836 values = self._performCall(lambda sessionID:
837 self._server.getTimetable(sessionID,
838 date.strftime("%Y-%m-%d"),
839 date.weekday()+1,
840 typeCodes))
841 return ScheduledFlightPair.scheduledFlights2Pairs([ScheduledFlight(value)
842 for value in values],
843 date)
844
845 def bookFlights(self, flightIDs, date, tailNumber):
846 """Book the flights with the given IDs on the given date to be flown
847 with the plane of the given tail number."""
848 values = self._performCall(lambda sessionID:
849 self._server.bookFlights(sessionID,
850 flightIDs,
851 date.strftime("%Y-%m-%d"),
852 tailNumber))
853 return [BookedFlight(value) for value in values]
854
855 def _performCall(self, callFn, acceptResults = []):
856 """Perform a call using the given call function.
857
858 acceptResults should be a list of result codes that should be accepted
859 besides RESULT_OK. If this list is not empty, the returned value is a
860 tuple of the result code and the corresponding value. Otherwise only
861 RESULT_OK is accepted, and the value is returned.
862
863 All other error codes are converted to exceptions."""
864 numAttempts = 0
865 while True:
866 reply = Reply(callFn(self._ensureSession()))
867 numAttempts += 1
868 result = reply.result
869 if result==Client.RESULT_SESSION_INVALID:
870 self._sessionID = None
871 if numAttempts==3:
872 raise RPCException(result)
873 elif result!=Client.RESULT_OK and result not in acceptResults:
874 raise RPCException(result)
875 elif acceptResults:
876 return (result, reply.value)
877 else:
878 return reply.value
879
880 def _ensureSession(self):
881 """Ensure that there is a valid session ID."""
882 while self._sessionID is None:
883 if self._userName is not None and self._passwordHash is not None:
884 if not self.login():
885 self._userName = self._passwordHash = None
886
887 if self._userName is None or self._passwordHash is None:
888 (self._userName, password) = self._getCredentialsFn()
889 if self._userName is None:
890 raise RPCException(Client.RESULT_LOGIN_FAILED)
891
892 md5 = hashlib.md5()
893 md5.update(password)
894 self._passwordHash = md5.hexdigest()
895
896 return self._sessionID
897
898#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.