source: src/mlx/rpc.py@ 1153:2eb474ef830e

python3
Last change on this file since 1153:2eb474ef830e was 1153:2eb474ef830e, checked in by István Váradi <ivaradi@…>, 3 weeks ago

The fuselage length and the wingspan are retrieved for each aircraft in the fleet (re #386).

File size: 36.8 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 "B738C" : const.AIRCRAFT_B738C,
203 "B73H" : const.AIRCRAFT_B738C,
204 "73H" : const.AIRCRAFT_B738C,
205 "B732" : const.AIRCRAFT_B732,
206 "732" : const.AIRCRAFT_B732,
207 "B733" : const.AIRCRAFT_B733,
208 "733" : const.AIRCRAFT_B733,
209 "B734" : const.AIRCRAFT_B734,
210 "734" : const.AIRCRAFT_B734,
211 "B735" : const.AIRCRAFT_B735,
212 "735" : const.AIRCRAFT_B735,
213 "DH8D" : const.AIRCRAFT_DH8D,
214 "DH4" : const.AIRCRAFT_DH8D,
215 "B762" : const.AIRCRAFT_B762,
216 "762" : const.AIRCRAFT_B762,
217 "B763" : const.AIRCRAFT_B763,
218 "763" : const.AIRCRAFT_B763,
219 "CRJ2" : const.AIRCRAFT_CRJ2,
220 "CR2" : const.AIRCRAFT_CRJ2,
221 "F70" : const.AIRCRAFT_F70,
222 "LI2" : const.AIRCRAFT_DC3,
223 "T134" : const.AIRCRAFT_T134,
224 "TU3" : const.AIRCRAFT_T134,
225 "T154" : const.AIRCRAFT_T154,
226 "TU5" : const.AIRCRAFT_T154,
227 "YK40" : const.AIRCRAFT_YK40,
228 "YK4" : const.AIRCRAFT_YK40,
229 "B462" : const.AIRCRAFT_B462,
230 "146" : const.AIRCRAFT_B462,
231 "IL62" : const.AIRCRAFT_IL62 }
232
233 TYPE2TYPECODE = { const.AIRCRAFT_B736 : "B736",
234 const.AIRCRAFT_B737 : "B737",
235 const.AIRCRAFT_B738 : "B738",
236 const.AIRCRAFT_B738C : "B73H",
237 const.AIRCRAFT_B732 : "B732",
238 const.AIRCRAFT_B733 : "B733",
239 const.AIRCRAFT_B734 : "B734",
240 const.AIRCRAFT_B735 : "B735",
241 const.AIRCRAFT_DH8D : "DH8D",
242 const.AIRCRAFT_B762 : "B762",
243 const.AIRCRAFT_B763 : "B763",
244 const.AIRCRAFT_CRJ2 : "CRJ2",
245 const.AIRCRAFT_F70 : "F70",
246 const.AIRCRAFT_DC3 : "LI2",
247 const.AIRCRAFT_T134 : "T134",
248 const.AIRCRAFT_T154 : "T154",
249 const.AIRCRAFT_YK40 : "YK40",
250 const.AIRCRAFT_B462 : "B462",
251 const.AIRCRAFT_IL62 : "IL62" }
252
253 checkFlightTypes = [ const.AIRCRAFT_B736, const.AIRCRAFT_B737,
254 const.AIRCRAFT_B738, const.AIRCRAFT_DH8D ]
255
256 @staticmethod
257 def _decodeAircraftType(typeCode):
258 """Decode the aircraft type from the given typeCode."""
259 if typeCode in BookedFlight.TYPECODE2TYPE:
260 return BookedFlight.TYPECODE2TYPE[typeCode]
261 else:
262 raise Exception("Invalid aircraft type code: '" + typeCode + "'")
263
264 @staticmethod
265 def _decodeStatus(status):
266 """Decode the status from the status string."""
267 if status=="booked":
268 return BookedFlight.STATUS_BOOKED
269 elif status=="reported":
270 return BookedFlight.STATUS_REPORTED
271 elif status=="accepted":
272 return BookedFlight.STATUS_ACCEPTED
273 elif status=="rejected":
274 return BookedFlight.STATUS_REJECTED
275 else:
276 raise Exception("Invalid flight status code: '" + status + "'")
277
278 @staticmethod
279 def _convertFlightType(ft):
280 """Convert the in-database flight-type to one of our constants."""
281 ft = int(ft)
282 if ft==0:
283 return const.FLIGHTTYPE_SCHEDULED
284 elif ft==1:
285 return const.FLIGHTTYPE_VIP
286 elif ft==2:
287 return const.FLIGHTTYPE_CHARTER
288 else:
289 return const.FLIGHTTYPE_SCHEDULED
290
291 @staticmethod
292 def getDateTime(date, time):
293 """Get a datetime object from the given textual date and time."""
294 return datetime.datetime.strptime(date + " " + time,
295 "%Y-%m-%d %H:%M:%S")
296
297 STATUS_BOOKED = 1
298
299 STATUS_REPORTED = 2
300
301 STATUS_ACCEPTED = 3
302
303 STATUS_REJECTED = 4
304
305 @staticmethod
306 def forCheckFlight(aircraftType):
307 """Create a booked flight for a check flight with the given aircraft
308 type."""
309 flight = BookedFlight()
310
311 flight.departureICAO = "LHBP"
312 flight.arrivalICAO = "LHBP"
313
314 flight.aircraftType = aircraftType
315 flight.aircraftTypeName = BookedFlight.TYPE2TYPECODE[aircraftType]
316
317 # FIXME: perhaps find one for the type
318 flight.tailNumber = "HA-CHK"
319 flight.callsign = "HA-CHK"
320
321 flight.numPassengers = 0
322 flight.numChildren = 0
323 flight.numInfants = 0
324 flight.numCabinCrew = 0
325 flight.bagWeight = 0
326 flight.cargoWeight = 0
327 flight.mailWeight = 0
328 flight.route = "DCT"
329
330 t = datetime.datetime.now() + datetime.timedelta(minutes = 20)
331 flight.departureTime = datetime.datetime(t.year, t.month, t.day,
332 t.hour, t.minute)
333 t = flight.departureTime + datetime.timedelta(minutes = 30)
334 flight.arrivalTime = datetime.datetime(t.year, t.month, t.day,
335 t.hour, t.minute)
336
337 return flight
338
339 # The instructions for the construction
340 _instructions = {
341 "numPassengers" : int,
342 "numChildren" : int,
343 "numInfants" : int,
344 "numCabinCrew" : int,
345 "dowNumCabinCrew" : int,
346 "numCockpitCrew" : int,
347 "bagWeight" : int,
348 "cargoWeight" : int,
349 "mailWeight" : int,
350 "flightType" : lambda value: BookedFlight._convertFlightType(value),
351 "dow": int,
352 "maxPassengers": int,
353 "aircraftType" : lambda value: BookedFlight._decodeAircraftType(value),
354 "status" : lambda value: BookedFlight._decodeStatus(value)
355 }
356
357 def __init__(self, value = None, id = None):
358 """Construct the booked flight object from the given RPC result
359 value."""
360 self.status = BookedFlight.STATUS_BOOKED
361 if value is None:
362 self.id = id
363 else:
364 super(BookedFlight, self).__init__(value, BookedFlight._instructions)
365 self.departureTime = \
366 BookedFlight.getDateTime(self.date, self.departureTime)
367 self.arrivalTime = \
368 BookedFlight.getDateTime(self.date, self.arrivalTime)
369 if self.arrivalTime<self.departureTime:
370 self.arrivalTime += datetime.timedelta(days = 1)
371
372 @property
373 def passengerWeight(self):
374 """Get the passenger (adult) weight for the flight."""
375 return const.getPassengerWeight(self.flightType)
376
377 @property
378 def payload(self):
379 """Get the default payload of the flight."""
380 payload= (self.numCabinCrew - self.dowNumCabinCrew) * \
381 const.WEIGHT_CABIN_CREW
382 payload += self.numPassengers * self.passengerWeight
383 payload += self.numChildren * const.WEIGHT_CHILD
384 payload += self.numInfants * const.WEIGHT_INFANT
385 payload += self.bagWeight
386 payload += self.cargoWeight
387 payload += self.mailWeight
388 return payload
389
390 def readFromFile(self, f, fleet):
391 """Read the data of the flight from a file via the given file
392 object."""
393 date = None
394 departureTime = None
395 arrivalTime = None
396
397 line = f.readline()
398 lineNumber = 0
399 self.numChildren = 0
400 self.numInfants = 0
401 self.numCockpitCrew = 2
402 self.flightType = const.FLIGHTTYPE_SCHEDULED
403 while line:
404 lineNumber += 1
405 line = line.strip()
406
407 hashIndex = line.find("#")
408 if hashIndex>=0: line = line[:hashIndex]
409 if line:
410 equalIndex = line.find("=")
411 lineOK = equalIndex>0
412
413 if lineOK:
414 key = line[:equalIndex].strip()
415 value = line[equalIndex+1:].strip().replace("\:", ":")
416
417 lineOK = key and value
418
419 if lineOK:
420 if key=="callsign": self.callsign = value
421 elif key=="date": date = value
422 elif key=="dep_airport": self.departureICAO = value
423 elif key=="dest_airport": self.arrivalICAO = value
424 elif key=="planecode": self.aircraftType = \
425 BookedFlight._decodeAircraftType(value)
426 elif key=="planetype": self.aircraftTypeName = value
427 elif key=="tail_nr": self.tailNumber = value
428 elif key=="passenger": self.numPassengers = int(value)
429 elif key=="child": self.numChildren = int(value)
430 elif key=="infant": self.numInfants = int(value)
431 elif key=="crew": self.numCabinCrew = int(value) - 2
432 elif key=="cabin_crew": self.numCabinCrew = int(value)
433 elif key=="cockpit_crew": self.numCockpitCrew = int(value)
434 elif key=="bag": self.bagWeight = int(value)
435 elif key=="cargo": self.cargoWeight = int(value)
436 elif key=="mail": self.mailWeight = int(value)
437 elif key=="flight_route": self.route = value
438 elif key=="departure_time": departureTime = value
439 elif key=="arrival_time": arrivalTime = value
440 elif key=="foglalas_id":
441 self.id = None if value=="0" else value
442 elif key=="flight_type": self.flightType = int(value)
443 else: lineOK = False
444
445 if not lineOK:
446 print("web.BookedFlight.readFromFile: line %d is invalid" % \
447 (lineNumber,))
448
449 line = f.readline()
450
451 self.date = date
452 if date is not None:
453 if departureTime is not None:
454 self.departureTime = BookedFlight.getDateTime(date,
455 departureTime)
456 if arrivalTime is not None:
457 self.arrivalTime = BookedFlight.getDateTime(date,
458 arrivalTime)
459
460 d = dir(self)
461 for attribute in ["callsign", "departureICAO", "arrivalICAO",
462 "aircraftType", "tailNumber",
463 "numPassengers", "numCockpitCrew", "numCabinCrew",
464 "bagWeight", "cargoWeight", "mailWeight",
465 "route", "departureTime", "arrivalTime"]:
466 if attribute not in d:
467 raise Exception("Attribute %s could not be read" % (attribute,))
468
469 if "aircraftTypeName" not in d:
470 self.aircraftTypeName = \
471 BookedFlight.TYPE2TYPECODE[self.aircraftType]
472
473 plane = fleet[self.tailNumber]
474 if plane is None:
475 self.dow = 0
476 self.maxPassengers = 0
477 self.dowNumCabinCrew = 0
478 else:
479 self.dow = plane.dow
480 self.maxPassengers = plane.maxPassengers
481 self.dowNumCabinCrew = plane.dowNumCabinCrew
482
483 def setupFromPIREPData(self, pirepData):
484 """Setup the booked flight from the given PIREP data."""
485 bookedFlightData = pirepData["bookedFlight"]
486
487 self.callsign = bookedFlightData["callsign"]
488
489 date = bookedFlightData["date"]
490
491 departureTime = bookedFlightData["departureTime"]
492 self.departureTime = BookedFlight.getDateTime(date, departureTime)
493
494 arrivalTime = bookedFlightData["arrivalTime"]
495 self.arrivalTime = BookedFlight.getDateTime(date, arrivalTime)
496 if self.arrivalTime<self.departureTime:
497 self.arrivalTime += datetime.timedelta(days = 1)
498
499 self.departureICAO = bookedFlightData["departureICAO"]
500 self.arrivalICAO = bookedFlightData["arrivalICAO"]
501
502 self.aircraftType = \
503 BookedFlight._decodeAircraftType(bookedFlightData["aircraftType"])
504 self.tailNumber = bookedFlightData["tailNumber"]
505 self.numPassengers = int(bookedFlightData["numPassengers"])
506 self.numChildren = int(bookedFlightData["numChildren"])
507 self.numInfants = int(bookedFlightData["numInfants"])
508 self.maxPassengers = int(bookedFlightData["maxPassengers"])
509 self.numCockpitCrew = int(bookedFlightData["numCockpitCrew"])
510 self.numCabinCrew = int(bookedFlightData["numCabinCrew"])
511 self.bagWeight = int(bookedFlightData["bagWeight"])
512 self.cargoWeight = int(bookedFlightData["cargoWeight"])
513 self.mailWeight = int(bookedFlightData["mailWeight"])
514 self.route = bookedFlightData["route"]
515 self.flightType = BookedFlight._convertFlightType(bookedFlightData["flightType"])
516
517 def writeIntoFile(self, f):
518 """Write the flight into a file."""
519 print("callsign=%s" % (self.callsign,), file=f)
520 date = self.departureTime.date()
521 print("date=%04d-%02d-%0d" % (date.year, date.month, date.day), file=f)
522 print("dep_airport=%s" % (self.departureICAO,), file=f)
523 print("dest_airport=%s" % (self.arrivalICAO,), file=f)
524 print("planecode=%s" % \
525 (BookedFlight.TYPE2TYPECODE[self.aircraftType],), file=f)
526 print("planetype=%s" % (self.aircraftTypeName,), file=f)
527 print("tail_nr=%s" % (self.tailNumber,), file=f)
528 print("passenger=%d" % (self.numPassengers,), file=f)
529 print("child=%d" % (self.numChildren,), file=f)
530 print("infant=%d" % (self.numInfants,), file=f)
531 print("cockpit_crew=%d" % (self.numCockpitCrew,), file=f)
532 print("cabin_crew=%d" % (self.numCabinCrew,), file=f)
533 print("bag=%d" % (self.bagWeight,), file=f)
534 print("cargo=%d" % (self.cargoWeight,), file=f)
535 print("mail=%d" % (self.mailWeight,), file=f)
536 print("flight_route=%s" % (self.route,), file=f)
537 departureTime = self.departureTime
538 print("departure_time=%02d\\:%02d\\:%02d" % \
539 (departureTime.hour, departureTime.minute, departureTime.second), file=f)
540 arrivalTime = self.arrivalTime
541 print("arrival_time=%02d\\:%02d\\:%02d" % \
542 (arrivalTime.hour, arrivalTime.minute, arrivalTime.second), file=f)
543 print("foglalas_id=%s" % ("0" if self.id is None else self.id,), file=f)
544 print("flight_type=%d" % (self.flightType,))
545
546 def __setstate__(self, state):
547 """Set the state from the given unpickled dictionary."""
548 self.__dict__.update(fixUnpickled(state))
549
550 def __repr__(self):
551 """Get a representation of the flight."""
552 s = "<Flight: %s-%s, %s, %s-%s," % (self.departureICAO,
553 self.arrivalICAO,
554 self.route,
555 self.departureTime, self.arrivalTime)
556 s += " %d %s," % (self.aircraftType, self.tailNumber)
557 s += " pax=%d+%d+%d, crew=%d+%d, bag=%d, cargo=%d, mail=%d" % \
558 (self.numPassengers, self.numChildren, self.numInfants,
559 self.numCockpitCrew, self.numCabinCrew,
560 self.bagWeight, self.cargoWeight, self.mailWeight)
561 s += ">"
562 return s
563
564#---------------------------------------------------------------------------------------
565
566class AcceptedFlight(RPCObject):
567 """A flight that has been already accepted."""
568 # The instructions for the construction
569 @staticmethod
570 def parseTimestamp(s):
571 """Parse the given RPC timestamp."""
572 dt = datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
573 return calendar.timegm(dt.utctimetuple())
574
575 _instructions = {
576 "bookedFlight" : lambda value: BookedFlight(value),
577 "numPassengers" : int,
578 "numChildren" : int,
579 "numInfants" : int,
580 "fuelUsed" : int,
581 "rating" : lambda value: float(value) if value else 0.0
582 }
583
584 def __init__(self, value):
585 """Construct the booked flight object from the given RPC result
586 value."""
587 super(AcceptedFlight, self).__init__(value, AcceptedFlight._instructions)
588 self.flightTimeStart = \
589 AcceptedFlight.parseTimestamp(self.flightDate + " " +
590 self.flightTimeStart)
591 self.flightTimeEnd = \
592 AcceptedFlight.parseTimestamp(self.flightDate + " " +
593 self.flightTimeEnd)
594 if self.flightTimeEnd<self.flightTimeStart:
595 self.flightTimeEnd += 24*60*60
596
597 self.totalNumPassengers = self.numPassengers + self.numChildren + self.numInfants
598
599#---------------------------------------------------------------------------------------
600
601class Plane(rpccommon.Plane, RPCObject):
602 """An airplane in the fleet."""
603 _instructions = {
604 "status" : lambda value: rpccommon.Plane.str2status(value),
605 "gateNumber" : lambda value: value if value else None,
606 "typeCode": lambda value: BookedFlight._decodeAircraftType(value),
607 "dow": int,
608 "dowNumCabinCrew": int,
609 "maxPassengers": int,
610 "fuselageLength": float,
611 "wingSpan": float
612 }
613
614 def __init__(self, value):
615 """Construct the plane."""
616 RPCObject.__init__(self, value, instructions = Plane._instructions)
617 self.aircraftType = self.typeCode
618 del self.typeCode
619
620#---------------------------------------------------------------------------------------
621
622class Fleet(rpccommon.Fleet):
623 """The fleet."""
624 def __init__(self, value):
625 """Construct the fleet."""
626 super(Fleet, self).__init__()
627 for planeValue in value:
628 self._addPlane(Plane(planeValue))
629
630#---------------------------------------------------------------------------------------
631
632class Registration(object):
633 """Data for registration."""
634 def __init__(self, surName, firstName, nameOrder,
635 yearOfBirth, emailAddress, emailAddressPublic,
636 vatsimID, ivaoID, phoneNumber, nationality, password):
637 """Construct the registration data."""
638 self.surName = surName
639 self.firstName = firstName
640 self.nameOrder = nameOrder
641 self.yearOfBirth = yearOfBirth
642 self.emailAddress = emailAddress
643 self.emailAddressPublic = 1 if emailAddressPublic is True else \
644 0 if emailAddressPublic is False else emailAddressPublic
645 self.vatsimID = "" if vatsimID is None else vatsimID
646 self.ivaoID = "" if ivaoID is None else ivaoID
647 self.phoneNumber = phoneNumber
648 self.nationality = nationality
649 self.password = password
650
651#---------------------------------------------------------------------------------------
652
653class RPCException(Exception):
654 """An exception thrown by RPC operations."""
655 def __init__(self, result, message = None):
656 """Construct the exception."""
657 self._result = result
658 if message is None:
659 message = "RPC call failed with result code: %d" % (result,)
660 super(RPCException, self).__init__(message)
661
662 @property
663 def result(self):
664 """Get the result code."""
665 return self._result
666
667#---------------------------------------------------------------------------------------
668
669class Client(object):
670 """The RPC client interface."""
671 # The client protocol version
672 VERSION = 2
673
674 # Result code: OK
675 RESULT_OK = 0
676
677 # Result code: the login has failed
678 RESULT_LOGIN_FAILED = 1
679
680 # Result code: the given session ID is unknown (it might have expired).
681 RESULT_SESSION_INVALID = 2
682
683 # Result code: some database error
684 RESULT_DATABASE_ERROR = 3
685
686 # Result code: invalid data
687 RESULT_INVALID_DATA = 4
688
689 # Result code: the flight does not exist
690 RESULT_FLIGHT_NOT_EXISTS = 101
691
692 # Result code: the flight has already been reported.
693 RESULT_FLIGHT_ALREADY_REPORTED = 102
694
695 # Result code: a user with the given e-mail address already exists
696 RESULT_EMAIL_ALREADY_REGISTERED = 103
697
698 def __init__(self, getCredentialsFn):
699 """Construct the client."""
700 self._getCredentialsFn = getCredentialsFn
701
702 sslContext = ssl.SSLContext()
703 sslContext.load_verify_locations(cafile = certifi.where())
704 transport = jsonrpclib.jsonrpc.SafeTransport(jsonrpclib.config.DEFAULT,
705 sslContext)
706
707 self._server = jsonrpclib.Server(MAVA_BASE_URL + "/jsonrpc.php",
708 transport = transport)
709
710 self._userName = None
711 self._passwordHash = None
712 self._sessionID = None
713 self._loginCount = 0
714
715 @property
716 def valid(self):
717 """Determine if the client is valid, i.e. there is a session ID
718 stored."""
719 return self._sessionID is not None
720
721 def setCredentials(self, userName, password):
722 """Set the credentials for future logins."""
723
724 self._userName = userName
725
726 md5 = hashlib.md5()
727 md5.update(password.encode("utf-8"))
728 self._passwordHash = md5.hexdigest()
729
730 self._sessionID = None
731
732 def register(self, registrationData):
733 """Register with the given data.
734
735 Returns a tuple of:
736 - the error code,
737 - the PID if there is no error."""
738 reply = Reply(self._server.register(registrationData))
739
740 return (reply.result,
741 reply.value["pid"] if reply.result==Client.RESULT_OK else None)
742
743 def login(self):
744 """Login using the given previously set credentials.
745
746 The session ID is stored in the object and used for later calls.
747
748 Returns the name of the pilot on success, or None on error."""
749 self._sessionID = None
750
751 reply = Reply(self._server.login(self._userName, self._passwordHash,
752 Client.VERSION))
753 if reply.result == Client.RESULT_OK:
754 self._loginCount += 1
755 self._sessionID = reply.value["sessionID"]
756
757 types = [BookedFlight.TYPECODE2TYPE[typeCode]
758 for typeCode in reply.value["typeCodes"]]
759
760 return (reply.value["name"], reply.value["rank"], types,
761 self._sessionID)
762 else:
763 return None
764
765 def getFlights(self):
766 """Get the flights available for performing."""
767 bookedFlights = []
768 reportedFlights = []
769 rejectedFlights = []
770
771 value = self._performCall(lambda sessionID:
772 self._server.getFlights(sessionID))
773 for flightData in value:
774 flight = BookedFlight(flightData)
775 if flight.status == BookedFlight.STATUS_BOOKED:
776 bookedFlights.append(flight)
777 elif flight.status == BookedFlight.STATUS_REPORTED:
778 reportedFlights.append(flight)
779 elif flight.status == BookedFlight.STATUS_REJECTED:
780 rejectedFlights.append(flight)
781
782 for flights in [bookedFlights, reportedFlights, rejectedFlights]:
783 flights.sort(key = lambda flight: flight.departureTime)
784
785 return (bookedFlights, reportedFlights, rejectedFlights)
786
787 def getAcceptedFlights(self):
788 """Get the flights that are already accepted."""
789 value = self._performCall(lambda sessionID:
790 self._server.getAcceptedFlights(sessionID))
791 flights = []
792 for flight in value:
793 flights.append(AcceptedFlight(flight))
794 return flights
795
796 def getEntryExamStatus(self):
797 """Get the status of the exams needed for joining MAVA."""
798 value = self._performCall(lambda sessionID:
799 self._server.getEntryExamStatus(sessionID))
800 return (value["entryExamPassed"], value["entryExamLink"],
801 value["checkFlightStatus"], value["madeFO"])
802
803 def getFleet(self):
804 """Query and return the fleet."""
805 value = self._performCall(lambda sessionID:
806 self._server.getFleet(sessionID))
807
808 return Fleet(value)
809
810 def updatePlane(self, tailNumber, status, gateNumber):
811 """Update the state and position of the plane with the given tail
812 number."""
813 status = rpccommon.Plane.status2str(status)
814 self._performCall(lambda sessionID:
815 self._server.updatePlane(sessionID, tailNumber,
816 status, gateNumber))
817
818 def addPIREP(self, flightID, pirep, update = False):
819 """Add the PIREP for the given flight."""
820 (result, _value) = \
821 self._performCall(lambda sessionID:
822 self._server.addPIREP(sessionID, flightID, pirep,
823 update),
824 acceptResults = [Client.RESULT_FLIGHT_ALREADY_REPORTED,
825 Client.RESULT_FLIGHT_NOT_EXISTS])
826 return result
827
828 def updateOnlineACARS(self, acars):
829 """Update the online ACARS from the given data."""
830 self._performCall(lambda sessionID:
831 self._server.updateOnlineACARS(sessionID, acars))
832
833 def setCheckFlightPassed(self, type):
834 """Mark the check flight of the user passed with the given type."""
835 self._performCall(lambda sessionID:
836 self._server.setCheckFlightPassed(sessionID, type))
837
838 def getPIREP(self, flightID):
839 """Get the PIREP data for the flight with the given ID."""
840 value = self._performCall(lambda sessionID:
841 self._server.getPIREP(sessionID, flightID))
842 return value
843
844 def reflyFlights(self, flightIDs):
845 """Mark the flights with the given IDs for reflying."""
846 self._performCall(lambda sessionID:
847 self._server.reflyFlights(sessionID, flightIDs))
848
849 def deleteFlights(self, flightIDs):
850 """Delete the flights with the given IDs."""
851 self._performCall(lambda sessionID:
852 self._server.deleteFlights(sessionID, flightIDs))
853
854 def getTimetable(self, date, types = None):
855 """Get the time table for the given date restricted to the given list
856 of type codes, if any."""
857 typeCodes = None if types is None else \
858 [BookedFlight.TYPE2TYPECODE[type] for type in types]
859
860 values = self._performCall(lambda sessionID:
861 self._server.getTimetable(sessionID,
862 date.strftime("%Y-%m-%d"),
863 date.weekday()+1,
864 typeCodes))
865 return ScheduledFlightPair.scheduledFlights2Pairs([ScheduledFlight(value)
866 for value in values],
867 date)
868
869 def bookFlights(self, flightIDs, date, tailNumber):
870 """Book the flights with the given IDs on the given date to be flown
871 with the plane of the given tail number."""
872 values = self._performCall(lambda sessionID:
873 self._server.bookFlights(sessionID,
874 flightIDs,
875 date.strftime("%Y-%m-%d"),
876 tailNumber))
877 return [BookedFlight(value) for value in values]
878
879 def getSimBriefResult(self, timestamp):
880 """Get the SimBrief results for the given timestamp."""
881 return self._performCall(lambda sessionID:
882 self._server.getSimBriefResult(sessionID,
883 timestamp))
884
885 def _performCall(self, callFn, acceptResults = []):
886 """Perform a call using the given call function.
887
888 acceptResults should be a list of result codes that should be accepted
889 besides RESULT_OK. If this list is not empty, the returned value is a
890 tuple of the result code and the corresponding value. Otherwise only
891 RESULT_OK is accepted, and the value is returned.
892
893 All other error codes are converted to exceptions."""
894 numAttempts = 0
895 while True:
896 reply = Reply(callFn(self._ensureSession()))
897 numAttempts += 1
898 result = reply.result
899 if result==Client.RESULT_SESSION_INVALID:
900 self._sessionID = None
901 if numAttempts==3:
902 raise RPCException(result)
903 elif result!=Client.RESULT_OK and result not in acceptResults:
904 raise RPCException(result)
905 elif acceptResults:
906 return (result, reply.value)
907 else:
908 return reply.value
909
910 def _ensureSession(self):
911 """Ensure that there is a valid session ID."""
912 while self._sessionID is None:
913 if self._userName is not None and self._passwordHash is not None:
914 if not self.login():
915 self._userName = self._passwordHash = None
916
917 if self._userName is None or self._passwordHash is None:
918 (self._userName, password) = self._getCredentialsFn()
919 if self._userName is None:
920 raise RPCException(Client.RESULT_LOGIN_FAILED)
921
922 md5 = hashlib.md5()
923 md5.update(password)
924 self._passwordHash = md5.hexdigest()
925
926 return self._sessionID
927
928#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.