source: src/mlx/rpc.py@ 1139:9754005a7e70

python3
Last change on this file since 1139:9754005a7e70 was 1134:93c9e1b528ca, checked in by István Váradi <ivaradi@…>, 8 months ago

Backed out changeset 915f7ac6a389

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