source: src/mlx/rpc.py@ 1092:f3c535a548c2

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

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

File size: 36.7 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 @property
372 def payload(self):
373 """Get the default payload of the flight."""
374 payload= (self.numCabinCrew - self.dowNumCabinCrew) * \
375 const.WEIGHT_CABIN_CREW
376 payload += self.numPassengers * \
377 (const.WEIGHT_PASSENGER_CHARTER
378 if self.flightType==const.FLIGHTTYPE_CHARTER
379 else const.WEIGHT_PASSENGER)
380 payload += self.numChildren * const.WEIGHT_CHILD
381 payload += self.numInfants * const.WEIGHT_INFANT
382 payload += self.bagWeight
383 payload += self.cargoWeight
384 payload += self.mailWeight
385 return payload
386
387 def readFromFile(self, f, fleet):
388 """Read the data of the flight from a file via the given file
389 object."""
390 date = None
391 departureTime = None
392 arrivalTime = None
393
394 line = f.readline()
395 lineNumber = 0
396 self.numChildren = 0
397 self.numInfants = 0
398 self.numCockpitCrew = 2
399 self.flightType = const.FLIGHTTYPE_SCHEDULED
400 while line:
401 lineNumber += 1
402 line = line.strip()
403
404 hashIndex = line.find("#")
405 if hashIndex>=0: line = line[:hashIndex]
406 if line:
407 equalIndex = line.find("=")
408 lineOK = equalIndex>0
409
410 if lineOK:
411 key = line[:equalIndex].strip()
412 value = line[equalIndex+1:].strip().replace("\:", ":")
413
414 lineOK = key and value
415
416 if lineOK:
417 if key=="callsign": self.callsign = value
418 elif key=="date": date = value
419 elif key=="dep_airport": self.departureICAO = value
420 elif key=="dest_airport": self.arrivalICAO = value
421 elif key=="planecode": self.aircraftType = \
422 BookedFlight._decodeAircraftType(value)
423 elif key=="planetype": self.aircraftTypeName = value
424 elif key=="tail_nr": self.tailNumber = value
425 elif key=="passenger": self.numPassengers = int(value)
426 elif key=="child": self.numChildren = int(value)
427 elif key=="infant": self.numInfants = int(value)
428 elif key=="crew": self.numCabinCrew = int(value) - 2
429 elif key=="cabin_crew": self.numCabinCrew = int(value)
430 elif key=="cockpit_crew": self.numCockpitCrew = int(value)
431 elif key=="bag": self.bagWeight = int(value)
432 elif key=="cargo": self.cargoWeight = int(value)
433 elif key=="mail": self.mailWeight = int(value)
434 elif key=="flight_route": self.route = value
435 elif key=="departure_time": departureTime = value
436 elif key=="arrival_time": arrivalTime = value
437 elif key=="foglalas_id":
438 self.id = None if value=="0" else value
439 elif key=="flight_type": self.flightType = int(value)
440 else: lineOK = False
441
442 if not lineOK:
443 print("web.BookedFlight.readFromFile: line %d is invalid" % \
444 (lineNumber,))
445
446 line = f.readline()
447
448 if date is not None:
449 if departureTime is not None:
450 self.departureTime = BookedFlight.getDateTime(date,
451 departureTime)
452 if arrivalTime is not None:
453 self.arrivalTime = BookedFlight.getDateTime(date,
454 arrivalTime)
455
456 d = dir(self)
457 for attribute in ["callsign", "departureICAO", "arrivalICAO",
458 "aircraftType", "tailNumber",
459 "numPassengers", "numCockpitCrew", "numCabinCrew",
460 "bagWeight", "cargoWeight", "mailWeight",
461 "route", "departureTime", "arrivalTime"]:
462 if attribute not in d:
463 raise Exception("Attribute %s could not be read" % (attribute,))
464
465 if "aircraftTypeName" not in d:
466 self.aircraftTypeName = \
467 BookedFlight.TYPE2TYPECODE[self.aircraftType]
468
469 plane = fleet[self.tailNumber]
470 if plane is None:
471 self.dow = 0
472 self.maxPassengers = 0
473 self.dowNumCabinCrew = 0
474 else:
475 self.dow = plane.dow
476 self.maxPassengers = plane.maxPassengers
477 self.dowNumCabinCrew = plane.dowNumCabinCrew
478
479 def setupFromPIREPData(self, pirepData):
480 """Setup the booked flight from the given PIREP data."""
481 bookedFlightData = pirepData["bookedFlight"]
482
483 self.callsign = bookedFlightData["callsign"]
484
485 date = bookedFlightData["date"]
486
487 departureTime = bookedFlightData["departureTime"]
488 self.departureTime = BookedFlight.getDateTime(date, departureTime)
489
490 arrivalTime = bookedFlightData["arrivalTime"]
491 self.arrivalTime = BookedFlight.getDateTime(date, arrivalTime)
492 if self.arrivalTime<self.departureTime:
493 self.arrivalTime += datetime.timedelta(days = 1)
494
495 self.departureICAO = bookedFlightData["departureICAO"]
496 self.arrivalICAO = bookedFlightData["arrivalICAO"]
497
498 self.aircraftType = \
499 BookedFlight._decodeAircraftType(bookedFlightData["aircraftType"])
500 self.tailNumber = bookedFlightData["tailNumber"]
501 self.numPassengers = int(bookedFlightData["numPassengers"])
502 self.numChildren = int(bookedFlightData["numChildren"])
503 self.numInfants = int(bookedFlightData["numInfants"])
504 self.maxPassengers = int(bookedFlightData["maxPassengers"])
505 self.numCockpitCrew = int(bookedFlightData["numCockpitCrew"])
506 self.numCabinCrew = int(bookedFlightData["numCabinCrew"])
507 self.bagWeight = int(bookedFlightData["bagWeight"])
508 self.cargoWeight = int(bookedFlightData["cargoWeight"])
509 self.mailWeight = int(bookedFlightData["mailWeight"])
510 self.route = bookedFlightData["route"]
511 self.flightType = BookedFlight._convertFlightType(bookedFlightData["flightType"])
512
513 def writeIntoFile(self, f):
514 """Write the flight into a file."""
515 print("callsign=%s" % (self.callsign,), file=f)
516 date = self.departureTime.date()
517 print("date=%04d-%02d-%0d" % (date.year, date.month, date.day), file=f)
518 print("dep_airport=%s" % (self.departureICAO,), file=f)
519 print("dest_airport=%s" % (self.arrivalICAO,), file=f)
520 print("planecode=%s" % \
521 (BookedFlight.TYPE2TYPECODE[self.aircraftType],), file=f)
522 print("planetype=%s" % (self.aircraftTypeName,), file=f)
523 print("tail_nr=%s" % (self.tailNumber,), file=f)
524 print("passenger=%d" % (self.numPassengers,), file=f)
525 print("child=%d" % (self.numChildren,), file=f)
526 print("infant=%d" % (self.numInfants,), file=f)
527 print("cockpit_crew=%d" % (self.numCockpitCrew,), file=f)
528 print("cabin_crew=%d" % (self.numCabinCrew,), file=f)
529 print("bag=%d" % (self.bagWeight,), file=f)
530 print("cargo=%d" % (self.cargoWeight,), file=f)
531 print("mail=%d" % (self.mailWeight,), file=f)
532 print("flight_route=%s" % (self.route,), file=f)
533 departureTime = self.departureTime
534 print("departure_time=%02d\\:%02d\\:%02d" % \
535 (departureTime.hour, departureTime.minute, departureTime.second), file=f)
536 arrivalTime = self.arrivalTime
537 print("arrival_time=%02d\\:%02d\\:%02d" % \
538 (arrivalTime.hour, arrivalTime.minute, arrivalTime.second), file=f)
539 print("foglalas_id=%s" % ("0" if self.id is None else self.id,), file=f)
540 print("flight_type=%d" % (self.flightType,))
541
542 def __setstate__(self, state):
543 """Set the state from the given unpickled dictionary."""
544 self.__dict__.update(fixUnpickled(state))
545
546 def __repr__(self):
547 """Get a representation of the flight."""
548 s = "<Flight: %s-%s, %s, %s-%s," % (self.departureICAO,
549 self.arrivalICAO,
550 self.route,
551 self.departureTime, self.arrivalTime)
552 s += " %d %s," % (self.aircraftType, self.tailNumber)
553 s += " pax=%d+%d+%d, crew=%d+%d, bag=%d, cargo=%d, mail=%d" % \
554 (self.numPassengers, self.numChildren, self.numInfants,
555 self.numCockpitCrew, self.numCabinCrew,
556 self.bagWeight, self.cargoWeight, self.mailWeight)
557 s += ">"
558 return s
559
560#---------------------------------------------------------------------------------------
561
562class AcceptedFlight(RPCObject):
563 """A flight that has been already accepted."""
564 # The instructions for the construction
565 @staticmethod
566 def parseTimestamp(s):
567 """Parse the given RPC timestamp."""
568 dt = datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
569 return calendar.timegm(dt.utctimetuple())
570
571 _instructions = {
572 "bookedFlight" : lambda value: BookedFlight(value),
573 "numPassengers" : int,
574 "numChildren" : int,
575 "numInfants" : int,
576 "fuelUsed" : int,
577 "rating" : lambda value: float(value) if value else 0.0
578 }
579
580 def __init__(self, value):
581 """Construct the booked flight object from the given RPC result
582 value."""
583 super(AcceptedFlight, self).__init__(value, AcceptedFlight._instructions)
584 self.flightTimeStart = \
585 AcceptedFlight.parseTimestamp(self.flightDate + " " +
586 self.flightTimeStart)
587 self.flightTimeEnd = \
588 AcceptedFlight.parseTimestamp(self.flightDate + " " +
589 self.flightTimeEnd)
590 if self.flightTimeEnd<self.flightTimeStart:
591 self.flightTimeEnd += 24*60*60
592
593 self.totalNumPassengers = self.numPassengers + self.numChildren + self.numInfants
594
595#---------------------------------------------------------------------------------------
596
597class Plane(rpccommon.Plane, RPCObject):
598 """An airplane in the fleet."""
599 _instructions = {
600 "status" : lambda value: rpccommon.Plane.str2status(value),
601 "gateNumber" : lambda value: value if value else None,
602 "typeCode": lambda value: BookedFlight._decodeAircraftType(value),
603 "dow": int,
604 "dowNumCabinCrew": int,
605 "maxPassengers": int,
606 }
607
608 def __init__(self, value):
609 """Construct the plane."""
610 RPCObject.__init__(self, value, instructions = Plane._instructions)
611 self.aircraftType = self.typeCode
612 del self.typeCode
613
614#---------------------------------------------------------------------------------------
615
616class Fleet(rpccommon.Fleet):
617 """The fleet."""
618 def __init__(self, value):
619 """Construct the fleet."""
620 super(Fleet, self).__init__()
621 for planeValue in value:
622 self._addPlane(Plane(planeValue))
623
624#---------------------------------------------------------------------------------------
625
626class Registration(object):
627 """Data for registration."""
628 def __init__(self, surName, firstName, nameOrder,
629 yearOfBirth, emailAddress, emailAddressPublic,
630 vatsimID, ivaoID, phoneNumber, nationality, password):
631 """Construct the registration data."""
632 self.surName = surName
633 self.firstName = firstName
634 self.nameOrder = nameOrder
635 self.yearOfBirth = yearOfBirth
636 self.emailAddress = emailAddress
637 self.emailAddressPublic = 1 if emailAddressPublic is True else \
638 0 if emailAddressPublic is False else emailAddressPublic
639 self.vatsimID = "" if vatsimID is None else vatsimID
640 self.ivaoID = "" if ivaoID is None else ivaoID
641 self.phoneNumber = phoneNumber
642 self.nationality = nationality
643 self.password = password
644
645#---------------------------------------------------------------------------------------
646
647class RPCException(Exception):
648 """An exception thrown by RPC operations."""
649 def __init__(self, result, message = None):
650 """Construct the exception."""
651 self._result = result
652 if message is None:
653 message = "RPC call failed with result code: %d" % (result,)
654 super(RPCException, self).__init__(message)
655
656 @property
657 def result(self):
658 """Get the result code."""
659 return self._result
660
661#---------------------------------------------------------------------------------------
662
663class Client(object):
664 """The RPC client interface."""
665 # The client protocol version
666 VERSION = 2
667
668 # Result code: OK
669 RESULT_OK = 0
670
671 # Result code: the login has failed
672 RESULT_LOGIN_FAILED = 1
673
674 # Result code: the given session ID is unknown (it might have expired).
675 RESULT_SESSION_INVALID = 2
676
677 # Result code: some database error
678 RESULT_DATABASE_ERROR = 3
679
680 # Result code: invalid data
681 RESULT_INVALID_DATA = 4
682
683 # Result code: the flight does not exist
684 RESULT_FLIGHT_NOT_EXISTS = 101
685
686 # Result code: the flight has already been reported.
687 RESULT_FLIGHT_ALREADY_REPORTED = 102
688
689 # Result code: a user with the given e-mail address already exists
690 RESULT_EMAIL_ALREADY_REGISTERED = 103
691
692 def __init__(self, getCredentialsFn):
693 """Construct the client."""
694 self._getCredentialsFn = getCredentialsFn
695
696 sslContext = ssl.SSLContext()
697 sslContext.load_verify_locations(cafile = certifi.where())
698 transport = jsonrpclib.jsonrpc.SafeTransport(jsonrpclib.config.DEFAULT,
699 sslContext)
700
701 self._server = jsonrpclib.Server(MAVA_BASE_URL + "/jsonrpc.php",
702 transport = transport)
703
704 self._userName = None
705 self._passwordHash = None
706 self._sessionID = None
707 self._loginCount = 0
708
709 @property
710 def valid(self):
711 """Determine if the client is valid, i.e. there is a session ID
712 stored."""
713 return self._sessionID is not None
714
715 def setCredentials(self, userName, password):
716 """Set the credentials for future logins."""
717
718 self._userName = userName
719
720 md5 = hashlib.md5()
721 md5.update(password.encode("utf-8"))
722 self._passwordHash = md5.hexdigest()
723
724 self._sessionID = None
725
726 def register(self, registrationData):
727 """Register with the given data.
728
729 Returns a tuple of:
730 - the error code,
731 - the PID if there is no error."""
732 reply = Reply(self._server.register(registrationData))
733
734 return (reply.result,
735 reply.value["pid"] if reply.result==Client.RESULT_OK else None)
736
737 def login(self):
738 """Login using the given previously set credentials.
739
740 The session ID is stored in the object and used for later calls.
741
742 Returns the name of the pilot on success, or None on error."""
743 self._sessionID = None
744
745 reply = Reply(self._server.login(self._userName, self._passwordHash,
746 Client.VERSION))
747 if reply.result == Client.RESULT_OK:
748 self._loginCount += 1
749 self._sessionID = reply.value["sessionID"]
750
751 types = [BookedFlight.TYPECODE2TYPE[typeCode]
752 for typeCode in reply.value["typeCodes"]]
753
754 return (reply.value["name"], reply.value["rank"], types,
755 self._sessionID)
756 else:
757 return None
758
759 def getFlights(self):
760 """Get the flights available for performing."""
761 bookedFlights = []
762 reportedFlights = []
763 rejectedFlights = []
764
765 value = self._performCall(lambda sessionID:
766 self._server.getFlights(sessionID))
767 for flightData in value:
768 flight = BookedFlight(flightData)
769 if flight.status == BookedFlight.STATUS_BOOKED:
770 bookedFlights.append(flight)
771 elif flight.status == BookedFlight.STATUS_REPORTED:
772 reportedFlights.append(flight)
773 elif flight.status == BookedFlight.STATUS_REJECTED:
774 rejectedFlights.append(flight)
775
776 for flights in [bookedFlights, reportedFlights, rejectedFlights]:
777 flights.sort(key = lambda flight: flight.departureTime)
778
779 return (bookedFlights, reportedFlights, rejectedFlights)
780
781 def getAcceptedFlights(self):
782 """Get the flights that are already accepted."""
783 value = self._performCall(lambda sessionID:
784 self._server.getAcceptedFlights(sessionID))
785 flights = []
786 for flight in value:
787 flights.append(AcceptedFlight(flight))
788 return flights
789
790 def getEntryExamStatus(self):
791 """Get the status of the exams needed for joining MAVA."""
792 value = self._performCall(lambda sessionID:
793 self._server.getEntryExamStatus(sessionID))
794 return (value["entryExamPassed"], value["entryExamLink"],
795 value["checkFlightStatus"], value["madeFO"])
796
797 def getFleet(self):
798 """Query and return the fleet."""
799 value = self._performCall(lambda sessionID:
800 self._server.getFleet(sessionID))
801
802 return Fleet(value)
803
804 def updatePlane(self, tailNumber, status, gateNumber):
805 """Update the state and position of the plane with the given tail
806 number."""
807 status = rpccommon.Plane.status2str(status)
808 self._performCall(lambda sessionID:
809 self._server.updatePlane(sessionID, tailNumber,
810 status, gateNumber))
811
812 def addPIREP(self, flightID, pirep, update = False):
813 """Add the PIREP for the given flight."""
814 (result, _value) = \
815 self._performCall(lambda sessionID:
816 self._server.addPIREP(sessionID, flightID, pirep,
817 update),
818 acceptResults = [Client.RESULT_FLIGHT_ALREADY_REPORTED,
819 Client.RESULT_FLIGHT_NOT_EXISTS])
820 return result
821
822 def updateOnlineACARS(self, acars):
823 """Update the online ACARS from the given data."""
824 self._performCall(lambda sessionID:
825 self._server.updateOnlineACARS(sessionID, acars))
826
827 def setCheckFlightPassed(self, type):
828 """Mark the check flight of the user passed with the given type."""
829 self._performCall(lambda sessionID:
830 self._server.setCheckFlightPassed(sessionID, type))
831
832 def getPIREP(self, flightID):
833 """Get the PIREP data for the flight with the given ID."""
834 value = self._performCall(lambda sessionID:
835 self._server.getPIREP(sessionID, flightID))
836 return value
837
838 def reflyFlights(self, flightIDs):
839 """Mark the flights with the given IDs for reflying."""
840 self._performCall(lambda sessionID:
841 self._server.reflyFlights(sessionID, flightIDs))
842
843 def deleteFlights(self, flightIDs):
844 """Delete the flights with the given IDs."""
845 self._performCall(lambda sessionID:
846 self._server.deleteFlights(sessionID, flightIDs))
847
848 def getTimetable(self, date, types = None):
849 """Get the time table for the given date restricted to the given list
850 of type codes, if any."""
851 typeCodes = None if types is None else \
852 [BookedFlight.TYPE2TYPECODE[type] for type in types]
853
854 values = self._performCall(lambda sessionID:
855 self._server.getTimetable(sessionID,
856 date.strftime("%Y-%m-%d"),
857 date.weekday()+1,
858 typeCodes))
859 return ScheduledFlightPair.scheduledFlights2Pairs([ScheduledFlight(value)
860 for value in values],
861 date)
862
863 def bookFlights(self, flightIDs, date, tailNumber):
864 """Book the flights with the given IDs on the given date to be flown
865 with the plane of the given tail number."""
866 values = self._performCall(lambda sessionID:
867 self._server.bookFlights(sessionID,
868 flightIDs,
869 date.strftime("%Y-%m-%d"),
870 tailNumber))
871 return [BookedFlight(value) for value in values]
872
873 def getSimBriefResult(self, timestamp):
874 """Get the SimBrief results for the given timestamp."""
875 return self._performCall(lambda sessionID:
876 self._server.getSimBriefResult(sessionID,
877 timestamp))
878
879 def _performCall(self, callFn, acceptResults = []):
880 """Perform a call using the given call function.
881
882 acceptResults should be a list of result codes that should be accepted
883 besides RESULT_OK. If this list is not empty, the returned value is a
884 tuple of the result code and the corresponding value. Otherwise only
885 RESULT_OK is accepted, and the value is returned.
886
887 All other error codes are converted to exceptions."""
888 numAttempts = 0
889 while True:
890 reply = Reply(callFn(self._ensureSession()))
891 numAttempts += 1
892 result = reply.result
893 if result==Client.RESULT_SESSION_INVALID:
894 self._sessionID = None
895 if numAttempts==3:
896 raise RPCException(result)
897 elif result!=Client.RESULT_OK and result not in acceptResults:
898 raise RPCException(result)
899 elif acceptResults:
900 return (result, reply.value)
901 else:
902 return reply.value
903
904 def _ensureSession(self):
905 """Ensure that there is a valid session ID."""
906 while self._sessionID is None:
907 if self._userName is not None and self._passwordHash is not None:
908 if not self.login():
909 self._userName = self._passwordHash = None
910
911 if self._userName is None or self._passwordHash is None:
912 (self._userName, password) = self._getCredentialsFn()
913 if self._userName is None:
914 raise RPCException(Client.RESULT_LOGIN_FAILED)
915
916 md5 = hashlib.md5()
917 md5.update(password)
918 self._passwordHash = md5.hexdigest()
919
920 return self._sessionID
921
922#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.