source: src/mlx/rpc.py@ 1101:7939f3eeb03e

python3
Last change on this file since 1101:7939f3eeb03e was 1101:7939f3eeb03e, checked in by István Váradi <ivaradi@…>, 8 months ago

The flight date is set properly when a flight is read from a file

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