source: src/mlx/rpc.py@ 1040:1c0a2408634b

python3
Last change on this file since 1040:1c0a2408634b was 1039:928f23d94583, checked in by István Váradi <ivaradi@…>, 3 years ago

Fixed the HTTPS access to the JSON-RPC handler (re #357)

File size: 27.3 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 # FIXME: copied from web.BookedFlight
197 TYPECODE2TYPE = { "B736" : const.AIRCRAFT_B736,
198 "B737" : const.AIRCRAFT_B737,
199 "B738" : const.AIRCRAFT_B738,
200 "B73H" : const.AIRCRAFT_B738C,
201 "B732" : const.AIRCRAFT_B732,
202 "B733" : const.AIRCRAFT_B733,
203 "B734" : const.AIRCRAFT_B734,
204 "B735" : const.AIRCRAFT_B735,
205 "DH8D" : const.AIRCRAFT_DH8D,
206 "B762" : const.AIRCRAFT_B762,
207 "B763" : const.AIRCRAFT_B763,
208 "CRJ2" : const.AIRCRAFT_CRJ2,
209 "F70" : const.AIRCRAFT_F70,
210 "LI2" : const.AIRCRAFT_DC3,
211 "T134" : const.AIRCRAFT_T134,
212 "T154" : const.AIRCRAFT_T154,
213 "YK40" : const.AIRCRAFT_YK40,
214 "B462" : const.AIRCRAFT_B462,
215 "IL62" : const.AIRCRAFT_IL62 }
216
217 # FIXME: copied from web.BookedFlight
218 TYPE2TYPECODE = { const.AIRCRAFT_B736 : "B736",
219 const.AIRCRAFT_B737 : "B737",
220 const.AIRCRAFT_B738 : "B738",
221 const.AIRCRAFT_B738C : "B73H",
222 const.AIRCRAFT_B732 : "B732",
223 const.AIRCRAFT_B733 : "B733",
224 const.AIRCRAFT_B734 : "B734",
225 const.AIRCRAFT_B735 : "B735",
226 const.AIRCRAFT_DH8D : "DH8D",
227 const.AIRCRAFT_B762 : "B762",
228 const.AIRCRAFT_B763 : "B763",
229 const.AIRCRAFT_CRJ2 : "CRJ2",
230 const.AIRCRAFT_F70 : "F70",
231 const.AIRCRAFT_DC3 : "LI2",
232 const.AIRCRAFT_T134 : "T134",
233 const.AIRCRAFT_T154 : "T155",
234 const.AIRCRAFT_YK40 : "YK40",
235 const.AIRCRAFT_B462 : "B462",
236 const.AIRCRAFT_IL62 : "IL62" }
237
238 # FIXME: copied from web.BookedFlight
239 @staticmethod
240 def _decodeAircraftType(typeCode):
241 """Decode the aircraft type from the given typeCode."""
242 if typeCode in BookedFlight.TYPECODE2TYPE:
243 return BookedFlight.TYPECODE2TYPE[typeCode]
244 else:
245 raise Exception("Invalid aircraft type code: '" + typeCode + "'")
246
247 @staticmethod
248 def _decodeStatus(status):
249 """Decode the status from the status string."""
250 if status=="booked":
251 return BookedFlight.STATUS_BOOKED
252 elif status=="reported":
253 return BookedFlight.STATUS_REPORTED
254 elif status=="accepted":
255 return BookedFlight.STATUS_ACCEPTED
256 elif status=="rejected":
257 return BookedFlight.STATUS_REJECTED
258 else:
259 raise Exception("Invalid flight status code: '" + status + "'")
260
261 @staticmethod
262 def _convertFlightType(ft):
263 """Convert the in-database flight-type to one of our constants."""
264 ft = int(ft)
265 if ft==0:
266 return const.FLIGHTTYPE_SCHEDULED
267 elif ft==1:
268 return const.FLIGHTTYPE_VIP
269 elif ft==2:
270 return const.FLIGHTTYPE_CHARTER
271 else:
272 return const.FLIGHTTYPE_SCHEDULED
273
274 # FIXME: copied from web.BookedFlight
275 @staticmethod
276 def getDateTime(date, time):
277 """Get a datetime object from the given textual date and time."""
278 return datetime.datetime.strptime(date + " " + time,
279 "%Y-%m-%d %H:%M:%S")
280
281 # FIXME: copied from web.BookedFlight
282 STATUS_BOOKED = 1
283
284 # FIXME: copied from web.BookedFlight
285 STATUS_REPORTED = 2
286
287 # FIXME: copied from web.BookedFlight
288 STATUS_ACCEPTED = 3
289
290 # FIXME: copied from web.BookedFlight
291 STATUS_REJECTED = 4
292
293 # The instructions for the construction
294 _instructions = {
295 "numPassengers" : int,
296 "numChildren" : int,
297 "numInfants" : int,
298 "numCabinCrew" : int,
299 "dowNumCabinCrew" : int,
300 "numCockpitCrew" : int,
301 "bagWeight" : int,
302 "cargoWeight" : int,
303 "mailWeight" : int,
304 "flightType" : lambda value: BookedFlight._convertFlightType(value),
305 "dow": int,
306 "maxPassengers": int,
307 "aircraftType" : lambda value: BookedFlight._decodeAircraftType(value),
308 "status" : lambda value: BookedFlight._decodeStatus(value)
309 }
310
311 def __init__(self, value):
312 """Construct the booked flight object from the given RPC result
313 value."""
314 self.status = BookedFlight.STATUS_BOOKED
315 super(BookedFlight, self).__init__(value, BookedFlight._instructions)
316 self.departureTime = \
317 BookedFlight.getDateTime(self.date, self.departureTime)
318 self.arrivalTime = \
319 BookedFlight.getDateTime(self.date, self.arrivalTime)
320 if self.arrivalTime<self.departureTime:
321 self.arrivalTime += datetime.timedelta(days = 1)
322
323 def writeIntoFile(self, f):
324 """Write the flight into a file."""
325 print("callsign=%s" % (self.callsign,), file=f)
326 date = self.departureTime.date()
327 print("date=%04d-%02d-%0d" % (date.year, date.month, date.day), file=f)
328 print("dep_airport=%s" % (self.departureICAO,), file=f)
329 print("dest_airport=%s" % (self.arrivalICAO,), file=f)
330 print("planecode=%s" % \
331 (BookedFlight.TYPE2TYPECODE[self.aircraftType],), file=f)
332 print("planetype=%s" % (self.aircraftTypeName,), file=f)
333 print("tail_nr=%s" % (self.tailNumber,), file=f)
334 print("passenger=%d" % (self.numPassengers,), file=f)
335 print("crew=%d" % (self.numCrew,), file=f)
336 print("bag=%d" % (self.bagWeight,), file=f)
337 print("cargo=%d" % (self.cargoWeight,), file=f)
338 print("mail=%d" % (self.mailWeight,), file=f)
339 print("flight_route=%s" % (self.route,), file=f)
340 departureTime = self.departureTime
341 print("departure_time=%02d\\:%02d\\:%02d" % \
342 (departureTime.hour, departureTime.minute, departureTime.second), file=f)
343 arrivalTime = self.arrivalTime
344 print("arrival_time=%02d\\:%02d\\:%02d" % \
345 (arrivalTime.hour, arrivalTime.minute, arrivalTime.second), file=f)
346 print("foglalas_id=%s" % ("0" if self.id is None else self.id,), file=f)
347
348 def __setstate__(self, state):
349 """Set the state from the given unpickled dictionary."""
350 self.__dict__.update(fixUnpickled(state))
351
352#---------------------------------------------------------------------------------------
353
354class AcceptedFlight(RPCObject):
355 """A flight that has been already accepted."""
356 # The instructions for the construction
357 @staticmethod
358 def parseTimestamp(s):
359 """Parse the given RPC timestamp."""
360 dt = datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S")
361 return calendar.timegm(dt.utctimetuple())
362
363 _instructions = {
364 "bookedFlight" : lambda value: BookedFlight(value),
365 "numPassengers" : int,
366 "numChildren" : int,
367 "numInfants" : int,
368 "fuelUsed" : int,
369 "rating" : lambda value: float(value) if value else 0.0
370 }
371
372 def __init__(self, value):
373 """Construct the booked flight object from the given RPC result
374 value."""
375 super(AcceptedFlight, self).__init__(value, AcceptedFlight._instructions)
376 self.flightTimeStart = \
377 AcceptedFlight.parseTimestamp(self.flightDate + " " +
378 self.flightTimeStart)
379 self.flightTimeEnd = \
380 AcceptedFlight.parseTimestamp(self.flightDate + " " +
381 self.flightTimeEnd)
382 if self.flightTimeEnd<self.flightTimeStart:
383 self.flightTimeEnd += 24*60*60
384
385 self.totalNumPassengers = self.numPassengers + self.numChildren + self.numInfants
386
387#---------------------------------------------------------------------------------------
388
389class Plane(rpccommon.Plane, RPCObject):
390 """An airplane in the fleet."""
391 _instructions = {
392 "status" : lambda value: rpccommon.Plane.str2status(value),
393 "gateNumber" : lambda value: value if value else None,
394 "typeCode": lambda value: BookedFlight._decodeAircraftType(value)
395 }
396
397 def __init__(self, value):
398 """Construct the plane."""
399 RPCObject.__init__(self, value, instructions = Plane._instructions)
400 self.aircraftType = self.typeCode
401 del self.typeCode
402
403#---------------------------------------------------------------------------------------
404
405class Fleet(rpccommon.Fleet):
406 """The fleet."""
407 def __init__(self, value):
408 """Construct the fleet."""
409 super(Fleet, self).__init__()
410 for planeValue in value:
411 self._addPlane(Plane(planeValue))
412
413#---------------------------------------------------------------------------------------
414
415class Registration(object):
416 """Data for registration."""
417 def __init__(self, surName, firstName, nameOrder,
418 yearOfBirth, emailAddress, emailAddressPublic,
419 vatsimID, ivaoID, phoneNumber, nationality, password):
420 """Construct the registration data."""
421 self.surName = surName
422 self.firstName = firstName
423 self.nameOrder = nameOrder
424 self.yearOfBirth = yearOfBirth
425 self.emailAddress = emailAddress
426 self.emailAddressPublic = 1 if emailAddressPublic is True else \
427 0 if emailAddressPublic is False else emailAddressPublic
428 self.vatsimID = "" if vatsimID is None else vatsimID
429 self.ivaoID = "" if ivaoID is None else ivaoID
430 self.phoneNumber = phoneNumber
431 self.nationality = nationality
432 self.password = password
433
434#---------------------------------------------------------------------------------------
435
436class RPCException(Exception):
437 """An exception thrown by RPC operations."""
438 def __init__(self, result, message = None):
439 """Construct the exception."""
440 self._result = result
441 if message is None:
442 message = "RPC call failed with result code: %d" % (result,)
443 super(RPCException, self).__init__(message)
444
445 @property
446 def result(self):
447 """Get the result code."""
448 return self._result
449
450#---------------------------------------------------------------------------------------
451
452class Client(object):
453 """The RPC client interface."""
454 # The client protocol version
455 VERSION = 2
456
457 # Result code: OK
458 RESULT_OK = 0
459
460 # Result code: the login has failed
461 RESULT_LOGIN_FAILED = 1
462
463 # Result code: the given session ID is unknown (it might have expired).
464 RESULT_SESSION_INVALID = 2
465
466 # Result code: some database error
467 RESULT_DATABASE_ERROR = 3
468
469 # Result code: invalid data
470 RESULT_INVALID_DATA = 4
471
472 # Result code: the flight does not exist
473 RESULT_FLIGHT_NOT_EXISTS = 101
474
475 # Result code: the flight has already been reported.
476 RESULT_FLIGHT_ALREADY_REPORTED = 102
477
478 # Result code: a user with the given e-mail address already exists
479 RESULT_EMAIL_ALREADY_REGISTERED = 103
480
481 def __init__(self, getCredentialsFn):
482 """Construct the client."""
483 self._getCredentialsFn = getCredentialsFn
484
485 sslContext = ssl.SSLContext()
486 sslContext.load_verify_locations(cafile = certifi.where())
487 transport = jsonrpclib.jsonrpc.SafeTransport(jsonrpclib.config.DEFAULT,
488 sslContext)
489
490 self._server = jsonrpclib.Server(MAVA_BASE_URL + "/jsonrpc.php",
491 transport = transport)
492
493 self._userName = None
494 self._passwordHash = None
495 self._sessionID = None
496 self._loginCount = 0
497
498 @property
499 def valid(self):
500 """Determine if the client is valid, i.e. there is a session ID
501 stored."""
502 return self._sessionID is not None
503
504 def setCredentials(self, userName, password):
505 """Set the credentials for future logins."""
506
507 self._userName = userName
508
509 md5 = hashlib.md5()
510 md5.update(password.encode("utf-8"))
511 self._passwordHash = md5.hexdigest()
512
513 self._sessionID = None
514
515 def register(self, registrationData):
516 """Register with the given data.
517
518 Returns a tuple of:
519 - the error code,
520 - the PID if there is no error."""
521 reply = Reply(self._server.register(registrationData))
522
523 return (reply.result,
524 reply.value["pid"] if reply.result==Client.RESULT_OK else None)
525
526 def login(self):
527 """Login using the given previously set credentials.
528
529 The session ID is stored in the object and used for later calls.
530
531 Returns the name of the pilot on success, or None on error."""
532 self._sessionID = None
533
534 reply = Reply(self._server.login(self._userName, self._passwordHash,
535 Client.VERSION))
536 if reply.result == Client.RESULT_OK:
537 self._loginCount += 1
538 self._sessionID = reply.value["sessionID"]
539
540 types = [BookedFlight.TYPECODE2TYPE[typeCode]
541 for typeCode in reply.value["typeCodes"]]
542
543 return (reply.value["name"], reply.value["rank"], types)
544 else:
545 return None
546
547 def getFlights(self):
548 """Get the flights available for performing."""
549 bookedFlights = []
550 reportedFlights = []
551 rejectedFlights = []
552
553 value = self._performCall(lambda sessionID:
554 self._server.getFlights(sessionID))
555 for flightData in value:
556 flight = BookedFlight(flightData)
557 if flight.status == BookedFlight.STATUS_BOOKED:
558 bookedFlights.append(flight)
559 elif flight.status == BookedFlight.STATUS_REPORTED:
560 reportedFlights.append(flight)
561 elif flight.status == BookedFlight.STATUS_REJECTED:
562 rejectedFlights.append(flight)
563
564 for flights in [bookedFlights, reportedFlights, rejectedFlights]:
565 flights.sort(key = lambda flight: flight.departureTime)
566
567 return (bookedFlights, reportedFlights, rejectedFlights)
568
569 def getAcceptedFlights(self):
570 """Get the flights that are already accepted."""
571 value = self._performCall(lambda sessionID:
572 self._server.getAcceptedFlights(sessionID))
573 flights = []
574 for flight in value:
575 flights.append(AcceptedFlight(flight))
576 return flights
577
578 def getEntryExamStatus(self):
579 """Get the status of the exams needed for joining MAVA."""
580 value = self._performCall(lambda sessionID:
581 self._server.getEntryExamStatus(sessionID))
582 return (value["entryExamPassed"], value["entryExamLink"],
583 value["checkFlightStatus"], value["madeFO"])
584
585 def getFleet(self):
586 """Query and return the fleet."""
587 value = self._performCall(lambda sessionID:
588 self._server.getFleet(sessionID))
589
590 return Fleet(value)
591
592 def updatePlane(self, tailNumber, status, gateNumber):
593 """Update the state and position of the plane with the given tail
594 number."""
595 status = rpccommon.Plane.status2str(status)
596 self._performCall(lambda sessionID:
597 self._server.updatePlane(sessionID, tailNumber,
598 status, gateNumber))
599
600 def addPIREP(self, flightID, pirep, update = False):
601 """Add the PIREP for the given flight."""
602 (result, _value) = \
603 self._performCall(lambda sessionID:
604 self._server.addPIREP(sessionID, flightID, pirep,
605 update),
606 acceptResults = [Client.RESULT_FLIGHT_ALREADY_REPORTED,
607 Client.RESULT_FLIGHT_NOT_EXISTS])
608 return result
609
610 def updateOnlineACARS(self, acars):
611 """Update the online ACARS from the given data."""
612 self._performCall(lambda sessionID:
613 self._server.updateOnlineACARS(sessionID, acars))
614
615 def setCheckFlightPassed(self, type):
616 """Mark the check flight of the user passed with the given type."""
617 self._performCall(lambda sessionID:
618 self._server.setCheckFlightPassed(sessionID, type))
619
620 def getPIREP(self, flightID):
621 """Get the PIREP data for the flight with the given ID."""
622 value = self._performCall(lambda sessionID:
623 self._server.getPIREP(sessionID, flightID))
624 return value
625
626 def reflyFlights(self, flightIDs):
627 """Mark the flights with the given IDs for reflying."""
628 self._performCall(lambda sessionID:
629 self._server.reflyFlights(sessionID, flightIDs))
630
631 def deleteFlights(self, flightIDs):
632 """Delete the flights with the given IDs."""
633 self._performCall(lambda sessionID:
634 self._server.deleteFlights(sessionID, flightIDs))
635
636 def getTimetable(self, date, types = None):
637 """Get the time table for the given date restricted to the given list
638 of type codes, if any."""
639 typeCodes = None if types is None else \
640 [BookedFlight.TYPE2TYPECODE[type] for type in types]
641
642 values = self._performCall(lambda sessionID:
643 self._server.getTimetable(sessionID,
644 date.strftime("%Y-%m-%d"),
645 date.weekday()+1,
646 typeCodes))
647 return ScheduledFlightPair.scheduledFlights2Pairs([ScheduledFlight(value)
648 for value in values],
649 date)
650
651 def bookFlights(self, flightIDs, date, tailNumber):
652 """Book the flights with the given IDs on the given date to be flown
653 with the plane of the given tail number."""
654 values = self._performCall(lambda sessionID:
655 self._server.bookFlights(sessionID,
656 flightIDs,
657 date.strftime("%Y-%m-%d"),
658 tailNumber))
659 return [BookedFlight(value) for value in values]
660
661 def _performCall(self, callFn, acceptResults = []):
662 """Perform a call using the given call function.
663
664 acceptResults should be a list of result codes that should be accepted
665 besides RESULT_OK. If this list is not empty, the returned value is a
666 tuple of the result code and the corresponding value. Otherwise only
667 RESULT_OK is accepted, and the value is returned.
668
669 All other error codes are converted to exceptions."""
670 numAttempts = 0
671 while True:
672 reply = Reply(callFn(self._ensureSession()))
673 numAttempts += 1
674 result = reply.result
675 if result==Client.RESULT_SESSION_INVALID:
676 self._sessionID = None
677 if numAttempts==3:
678 raise RPCException(result)
679 elif result!=Client.RESULT_OK and result not in acceptResults:
680 raise RPCException(result)
681 elif acceptResults:
682 return (result, reply.value)
683 else:
684 return reply.value
685
686 def _ensureSession(self):
687 """Ensure that there is a valid session ID."""
688 while self._sessionID is None:
689 if self._userName is not None and self._passwordHash is not None:
690 if not self.login():
691 self._userName = self._passwordHash = None
692
693 if self._userName is None or self._passwordHash is None:
694 (self._userName, password) = self._getCredentialsFn()
695 if self._userName is None:
696 raise RPCException(Client.RESULT_LOGIN_FAILED)
697
698 md5 = hashlib.md5()
699 md5.update(password)
700 self._passwordHash = md5.hexdigest()
701
702 return self._sessionID
703
704#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.