source: src/mlx/rpc.py@ 1048:e5bfc691ad08

python3
Last change on this file since 1048:e5bfc691ad08 was 1046:12bbf8604117, checked in by István Váradi <ivaradi@…>, 3 years ago

New style briefing (re #357)

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