source: src/mlx/rpc.py@ 1191:0db341c8c3b3

python3 tip
Last change on this file since 1191:0db341c8c3b3 was 1191:0db341c8c3b3, checked in by István Váradi <ivaradi@…>, 4 days ago

context is used instead of cafile in urlopen()

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