source: src/mlx/rpc.py@ 1143:dbe6c974f227

python3
Last change on this file since 1143:dbe6c974f227 was 1141:83f0b56057bf, checked in by István Váradi <ivaradi@…>, 6 months ago

Function on BookedFlight to get the adult passenger weight (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 }
611
612 def __init__(self, value):
613 """Construct the plane."""
614 RPCObject.__init__(self, value, instructions = Plane._instructions)
615 self.aircraftType = self.typeCode
616 del self.typeCode
617
618#---------------------------------------------------------------------------------------
619
620class Fleet(rpccommon.Fleet):
621 """The fleet."""
622 def __init__(self, value):
623 """Construct the fleet."""
624 super(Fleet, self).__init__()
625 for planeValue in value:
626 self._addPlane(Plane(planeValue))
627
628#---------------------------------------------------------------------------------------
629
630class Registration(object):
631 """Data for registration."""
632 def __init__(self, surName, firstName, nameOrder,
633 yearOfBirth, emailAddress, emailAddressPublic,
634 vatsimID, ivaoID, phoneNumber, nationality, password):
635 """Construct the registration data."""
636 self.surName = surName
637 self.firstName = firstName
638 self.nameOrder = nameOrder
639 self.yearOfBirth = yearOfBirth
640 self.emailAddress = emailAddress
641 self.emailAddressPublic = 1 if emailAddressPublic is True else \
642 0 if emailAddressPublic is False else emailAddressPublic
643 self.vatsimID = "" if vatsimID is None else vatsimID
644 self.ivaoID = "" if ivaoID is None else ivaoID
645 self.phoneNumber = phoneNumber
646 self.nationality = nationality
647 self.password = password
648
649#---------------------------------------------------------------------------------------
650
651class RPCException(Exception):
652 """An exception thrown by RPC operations."""
653 def __init__(self, result, message = None):
654 """Construct the exception."""
655 self._result = result
656 if message is None:
657 message = "RPC call failed with result code: %d" % (result,)
658 super(RPCException, self).__init__(message)
659
660 @property
661 def result(self):
662 """Get the result code."""
663 return self._result
664
665#---------------------------------------------------------------------------------------
666
667class Client(object):
668 """The RPC client interface."""
669 # The client protocol version
670 VERSION = 2
671
672 # Result code: OK
673 RESULT_OK = 0
674
675 # Result code: the login has failed
676 RESULT_LOGIN_FAILED = 1
677
678 # Result code: the given session ID is unknown (it might have expired).
679 RESULT_SESSION_INVALID = 2
680
681 # Result code: some database error
682 RESULT_DATABASE_ERROR = 3
683
684 # Result code: invalid data
685 RESULT_INVALID_DATA = 4
686
687 # Result code: the flight does not exist
688 RESULT_FLIGHT_NOT_EXISTS = 101
689
690 # Result code: the flight has already been reported.
691 RESULT_FLIGHT_ALREADY_REPORTED = 102
692
693 # Result code: a user with the given e-mail address already exists
694 RESULT_EMAIL_ALREADY_REGISTERED = 103
695
696 def __init__(self, getCredentialsFn):
697 """Construct the client."""
698 self._getCredentialsFn = getCredentialsFn
699
700 sslContext = ssl.SSLContext()
701 sslContext.load_verify_locations(cafile = certifi.where())
702 transport = jsonrpclib.jsonrpc.SafeTransport(jsonrpclib.config.DEFAULT,
703 sslContext)
704
705 self._server = jsonrpclib.Server(MAVA_BASE_URL + "/jsonrpc.php",
706 transport = transport)
707
708 self._userName = None
709 self._passwordHash = None
710 self._sessionID = None
711 self._loginCount = 0
712
713 @property
714 def valid(self):
715 """Determine if the client is valid, i.e. there is a session ID
716 stored."""
717 return self._sessionID is not None
718
719 def setCredentials(self, userName, password):
720 """Set the credentials for future logins."""
721
722 self._userName = userName
723
724 md5 = hashlib.md5()
725 md5.update(password.encode("utf-8"))
726 self._passwordHash = md5.hexdigest()
727
728 self._sessionID = None
729
730 def register(self, registrationData):
731 """Register with the given data.
732
733 Returns a tuple of:
734 - the error code,
735 - the PID if there is no error."""
736 reply = Reply(self._server.register(registrationData))
737
738 return (reply.result,
739 reply.value["pid"] if reply.result==Client.RESULT_OK else None)
740
741 def login(self):
742 """Login using the given previously set credentials.
743
744 The session ID is stored in the object and used for later calls.
745
746 Returns the name of the pilot on success, or None on error."""
747 self._sessionID = None
748
749 reply = Reply(self._server.login(self._userName, self._passwordHash,
750 Client.VERSION))
751 if reply.result == Client.RESULT_OK:
752 self._loginCount += 1
753 self._sessionID = reply.value["sessionID"]
754
755 types = [BookedFlight.TYPECODE2TYPE[typeCode]
756 for typeCode in reply.value["typeCodes"]]
757
758 return (reply.value["name"], reply.value["rank"], types,
759 self._sessionID)
760 else:
761 return None
762
763 def getFlights(self):
764 """Get the flights available for performing."""
765 bookedFlights = []
766 reportedFlights = []
767 rejectedFlights = []
768
769 value = self._performCall(lambda sessionID:
770 self._server.getFlights(sessionID))
771 for flightData in value:
772 flight = BookedFlight(flightData)
773 if flight.status == BookedFlight.STATUS_BOOKED:
774 bookedFlights.append(flight)
775 elif flight.status == BookedFlight.STATUS_REPORTED:
776 reportedFlights.append(flight)
777 elif flight.status == BookedFlight.STATUS_REJECTED:
778 rejectedFlights.append(flight)
779
780 for flights in [bookedFlights, reportedFlights, rejectedFlights]:
781 flights.sort(key = lambda flight: flight.departureTime)
782
783 return (bookedFlights, reportedFlights, rejectedFlights)
784
785 def getAcceptedFlights(self):
786 """Get the flights that are already accepted."""
787 value = self._performCall(lambda sessionID:
788 self._server.getAcceptedFlights(sessionID))
789 flights = []
790 for flight in value:
791 flights.append(AcceptedFlight(flight))
792 return flights
793
794 def getEntryExamStatus(self):
795 """Get the status of the exams needed for joining MAVA."""
796 value = self._performCall(lambda sessionID:
797 self._server.getEntryExamStatus(sessionID))
798 return (value["entryExamPassed"], value["entryExamLink"],
799 value["checkFlightStatus"], value["madeFO"])
800
801 def getFleet(self):
802 """Query and return the fleet."""
803 value = self._performCall(lambda sessionID:
804 self._server.getFleet(sessionID))
805
806 return Fleet(value)
807
808 def updatePlane(self, tailNumber, status, gateNumber):
809 """Update the state and position of the plane with the given tail
810 number."""
811 status = rpccommon.Plane.status2str(status)
812 self._performCall(lambda sessionID:
813 self._server.updatePlane(sessionID, tailNumber,
814 status, gateNumber))
815
816 def addPIREP(self, flightID, pirep, update = False):
817 """Add the PIREP for the given flight."""
818 (result, _value) = \
819 self._performCall(lambda sessionID:
820 self._server.addPIREP(sessionID, flightID, pirep,
821 update),
822 acceptResults = [Client.RESULT_FLIGHT_ALREADY_REPORTED,
823 Client.RESULT_FLIGHT_NOT_EXISTS])
824 return result
825
826 def updateOnlineACARS(self, acars):
827 """Update the online ACARS from the given data."""
828 self._performCall(lambda sessionID:
829 self._server.updateOnlineACARS(sessionID, acars))
830
831 def setCheckFlightPassed(self, type):
832 """Mark the check flight of the user passed with the given type."""
833 self._performCall(lambda sessionID:
834 self._server.setCheckFlightPassed(sessionID, type))
835
836 def getPIREP(self, flightID):
837 """Get the PIREP data for the flight with the given ID."""
838 value = self._performCall(lambda sessionID:
839 self._server.getPIREP(sessionID, flightID))
840 return value
841
842 def reflyFlights(self, flightIDs):
843 """Mark the flights with the given IDs for reflying."""
844 self._performCall(lambda sessionID:
845 self._server.reflyFlights(sessionID, flightIDs))
846
847 def deleteFlights(self, flightIDs):
848 """Delete the flights with the given IDs."""
849 self._performCall(lambda sessionID:
850 self._server.deleteFlights(sessionID, flightIDs))
851
852 def getTimetable(self, date, types = None):
853 """Get the time table for the given date restricted to the given list
854 of type codes, if any."""
855 typeCodes = None if types is None else \
856 [BookedFlight.TYPE2TYPECODE[type] for type in types]
857
858 values = self._performCall(lambda sessionID:
859 self._server.getTimetable(sessionID,
860 date.strftime("%Y-%m-%d"),
861 date.weekday()+1,
862 typeCodes))
863 return ScheduledFlightPair.scheduledFlights2Pairs([ScheduledFlight(value)
864 for value in values],
865 date)
866
867 def bookFlights(self, flightIDs, date, tailNumber):
868 """Book the flights with the given IDs on the given date to be flown
869 with the plane of the given tail number."""
870 values = self._performCall(lambda sessionID:
871 self._server.bookFlights(sessionID,
872 flightIDs,
873 date.strftime("%Y-%m-%d"),
874 tailNumber))
875 return [BookedFlight(value) for value in values]
876
877 def getSimBriefResult(self, timestamp):
878 """Get the SimBrief results for the given timestamp."""
879 return self._performCall(lambda sessionID:
880 self._server.getSimBriefResult(sessionID,
881 timestamp))
882
883 def _performCall(self, callFn, acceptResults = []):
884 """Perform a call using the given call function.
885
886 acceptResults should be a list of result codes that should be accepted
887 besides RESULT_OK. If this list is not empty, the returned value is a
888 tuple of the result code and the corresponding value. Otherwise only
889 RESULT_OK is accepted, and the value is returned.
890
891 All other error codes are converted to exceptions."""
892 numAttempts = 0
893 while True:
894 reply = Reply(callFn(self._ensureSession()))
895 numAttempts += 1
896 result = reply.result
897 if result==Client.RESULT_SESSION_INVALID:
898 self._sessionID = None
899 if numAttempts==3:
900 raise RPCException(result)
901 elif result!=Client.RESULT_OK and result not in acceptResults:
902 raise RPCException(result)
903 elif acceptResults:
904 return (result, reply.value)
905 else:
906 return reply.value
907
908 def _ensureSession(self):
909 """Ensure that there is a valid session ID."""
910 while self._sessionID is None:
911 if self._userName is not None and self._passwordHash is not None:
912 if not self.login():
913 self._userName = self._passwordHash = None
914
915 if self._userName is None or self._passwordHash is None:
916 (self._userName, password) = self._getCredentialsFn()
917 if self._userName is None:
918 raise RPCException(Client.RESULT_LOGIN_FAILED)
919
920 md5 = hashlib.md5()
921 md5.update(password)
922 self._passwordHash = md5.hexdigest()
923
924 return self._sessionID
925
926#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.