source: src/mlx/rpc.py@ 1034:4836f52b49cd

python3
Last change on this file since 1034:4836f52b49cd was 1034:4836f52b49cd, checked in by István Váradi <ivaradi@…>, 2 years ago

Updated the flight type handling (re #357)

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