source: src/mlx/rpc.py@ 762:c49eee53b7c5

Last change on this file since 762:c49eee53b7c5 was 761:fd39e894ffbe, checked in by István Váradi <ivaradi@…>, 9 years ago

The entry exam status can be and is queried (re #285).

File size: 12.2 KB
Line 
1import const
2import rpccommon
3
4from common import MAVA_BASE_URL
5
6import jsonrpclib
7import hashlib
8import datetime
9
10#---------------------------------------------------------------------------------------
11
12class RPCObject(object):
13 """Base class for objects read from RPC calls.
14
15 It is possible to construct it from a dictionary."""
16 def __init__(self, value, instructions = {}):
17 """Construct the object.
18
19 value is the dictionary returned by the call.
20
21 info is a mapping from names to 'instructions' on what to do with the
22 corresponding values. If the instruction is None, it will be ignored.
23 If the instruction is a function, the value will be passed to it and
24 the return value will be stored in the object.
25
26 For all other names, the value will be stored as the same-named
27 attribute."""
28 for (key, value) in value.iteritems():
29 if key in instructions:
30 instruction = instructions[key]
31 if instruction is None:
32 continue
33
34 value = instruction(value)
35 setattr(self, key, value)
36
37#---------------------------------------------------------------------------------------
38
39class Reply(RPCObject):
40 """The generic reply structure."""
41
42#---------------------------------------------------------------------------------------
43
44class BookedFlight(RPCObject):
45 """A booked flight."""
46 # FIXME: copied from web.BookedFlight
47 TYPECODE2TYPE = { "736" : const.AIRCRAFT_B736,
48 "73G" : const.AIRCRAFT_B737,
49 "738" : const.AIRCRAFT_B738,
50 "73H" : const.AIRCRAFT_B738C,
51 "733" : const.AIRCRAFT_B733,
52 "734" : const.AIRCRAFT_B734,
53 "735" : const.AIRCRAFT_B735,
54 "DH4" : const.AIRCRAFT_DH8D,
55 "762" : const.AIRCRAFT_B762,
56 "763" : const.AIRCRAFT_B763,
57 "CR2" : const.AIRCRAFT_CRJ2,
58 "F70" : const.AIRCRAFT_F70,
59 "LI2" : const.AIRCRAFT_DC3,
60 "TU3" : const.AIRCRAFT_T134,
61 "TU5" : const.AIRCRAFT_T154,
62 "YK4" : const.AIRCRAFT_YK40,
63 "146" : const.AIRCRAFT_B462 }
64
65 # FIXME: copied from web.BookedFlight
66 @staticmethod
67 def _decodeAircraftType(typeCode):
68 """Decode the aircraft type from the given typeCode."""
69 if typeCode in BookedFlight.TYPECODE2TYPE:
70 return BookedFlight.TYPECODE2TYPE[typeCode]
71 else:
72 raise Exception("Invalid aircraft type code: '" + typeCode + "'")
73
74 # FIXME: copied from web.BookedFlight
75 @staticmethod
76 def getDateTime(date, time):
77 """Get a datetime object from the given textual date and time."""
78 return datetime.datetime.strptime(date + " " + time,
79 "%Y-%m-%d %H:%M:%S")
80
81 # The instructions for the construction
82 _instructions = {
83 "numPassengers" : int,
84 "numCrew" : int,
85 "bagWeight" : int,
86 "cargoWeight" : int,
87 "mailWeight" : int,
88 "aircraftType" : lambda value: BookedFlight._decodeAircraftType(value)
89 }
90
91 def __init__(self, value):
92 """Construct the booked flight object from the given RPC result
93 value."""
94 super(BookedFlight, self).__init__(value, BookedFlight._instructions)
95 self.departureTime = \
96 BookedFlight.getDateTime(self.date, self.departureTime)
97 self.arrivalTime = \
98 BookedFlight.getDateTime(self.date, self.arrivalTime)
99 if self.arrivalTime<self.departureTime:
100 self.arrivalTime += datetime.timedelta(days = 1)
101
102#---------------------------------------------------------------------------------------
103
104class Plane(rpccommon.Plane, RPCObject):
105 """An airplane in the fleet."""
106 _instructions = {
107 "status" : lambda value: rpccommon.Plane.str2status(value),
108 "gateNumber" : lambda value: value if value else None
109 }
110
111 def __init__(self, value):
112 """Construct the plane."""
113 RPCObject.__init__(self, value, instructions = Plane._instructions)
114
115#---------------------------------------------------------------------------------------
116
117class Fleet(rpccommon.Fleet):
118 """The fleet."""
119 def __init__(self, value):
120 """Construct the fleet."""
121 super(Fleet, self).__init__()
122 for planeValue in value:
123 self._addPlane(Plane(planeValue))
124
125#---------------------------------------------------------------------------------------
126
127class Registration(object):
128 """Data for registration."""
129 def __init__(self, surName, firstName, nameOrder,
130 yearOfBirth, emailAddress, emailAddressPublic,
131 vatsimID, ivaoID, phoneNumber, nationality, password):
132 """Construct the registration data."""
133 self.surName = surName
134 self.firstName = firstName
135 self.nameOrder = nameOrder
136 self.yearOfBirth = yearOfBirth
137 self.emailAddress = emailAddress
138 self.emailAddressPublic = 1 if emailAddressPublic is True else \
139 0 if emailAddressPublic is False else emailAddressPublic
140 self.vatsimID = "" if vatsimID is None else vatsimID
141 self.ivaoID = "" if ivaoID is None else ivaoID
142 self.phoneNumber = phoneNumber
143 self.nationality = nationality
144 self.password = password
145
146#---------------------------------------------------------------------------------------
147
148class RPCException(Exception):
149 """An exception thrown by RPC operations."""
150 def __init__(self, result, message = None):
151 """Construct the exception."""
152 self._result = result
153 if message is None:
154 message = "RPC call failed with result code: %d" % (result,)
155 super(RPCException, self).__init__(message)
156
157 @property
158 def result(self):
159 """Get the result code."""
160 return self._result
161
162#---------------------------------------------------------------------------------------
163
164class Client(object):
165 """The RPC client interface."""
166 # Result code: OK
167 RESULT_OK = 0
168
169 # Result code: the login has failed
170 RESULT_LOGIN_FAILED = 1
171
172 # Result code: the given session ID is unknown (it might have expired).
173 RESULT_SESSION_INVALID = 2
174
175 # Result code: some database error
176 RESULT_DATABASE_ERROR = 3
177
178 # Result code: invalid data
179 RESULT_INVALID_DATA = 4
180
181 # Result code: the flight does not exist
182 RESULT_FLIGHT_NOT_EXISTS = 101
183
184 # Result code: the flight has already been reported.
185 RESULT_FLIGHT_ALREADY_REPORTED = 102
186
187 # Result code: a user with the given e-mail address already exists
188 RESULT_EMAIL_ALREADY_REGISTERED = 103
189
190 def __init__(self, getCredentialsFn):
191 """Construct the client."""
192 self._getCredentialsFn = getCredentialsFn
193
194 self._server = jsonrpclib.Server(MAVA_BASE_URL + "/jsonrpc.php")
195
196 self._userName = None
197 self._passwordHash = None
198 self._sessionID = None
199 self._loginCount = 0
200
201 @property
202 def valid(self):
203 """Determine if the client is valid, i.e. there is a session ID
204 stored."""
205 return self._sessionID is not None
206
207 def setCredentials(self, userName, password):
208 """Set the credentials for future logins."""
209
210 self._userName = userName
211
212 md5 = hashlib.md5()
213 md5.update(password)
214 self._passwordHash = md5.hexdigest()
215
216 self._sessionID = None
217
218 def register(self, registrationData):
219 """Register with the given data.
220
221 Returns a tuple of:
222 - the error code,
223 - the PID if there is no error."""
224 reply = Reply(self._server.register(registrationData))
225
226 return (reply.result,
227 reply.value["pid"] if reply.result==Client.RESULT_OK else None)
228
229 def login(self):
230 """Login using the given previously set credentials.
231
232 The session ID is stored in the object and used for later calls.
233
234 Returns the name of the pilot on success, or None on error."""
235 self._sessionID = None
236
237 reply = Reply(self._server.login(self._userName, self._passwordHash))
238 if reply.result == Client.RESULT_OK:
239 self._loginCount += 1
240 self._sessionID = reply.value["sessionID"]
241 return (reply.value["name"], reply.value["rank"])
242 else:
243 return None
244
245 def getFlights(self):
246 """Get the flights available for performing."""
247 flights = []
248
249 value = self._performCall(lambda sessionID:
250 self._server.getFlights(sessionID))
251 for flightData in value:
252 flights.append(BookedFlight(flightData))
253
254 flights.sort(cmp = lambda flight1, flight2:
255 cmp(flight1.departureTime, flight2.departureTime))
256
257 return flights
258
259 def getEntryExamStatus(self):
260 """Get the status of the exams needed for joining MAVA."""
261 value = self._performCall(lambda sessionID:
262 self._server.getEntryExamStatus(sessionID))
263 return (value["entryExamPassed"], value["checkFlightStatus"])
264
265 def getFleet(self):
266 """Query and return the fleet."""
267 value = self._performCall(lambda sessionID:
268 self._server.getFleet(sessionID))
269
270 return Fleet(value)
271
272 def updatePlane(self, tailNumber, status, gateNumber):
273 """Update the state and position of the plane with the given tail
274 number."""
275 status = rpccommon.Plane.status2str(status)
276 self._performCall(lambda sessionID:
277 self._server.updatePlane(sessionID, tailNumber,
278 status, gateNumber))
279
280 def addPIREP(self, flightID, pirep):
281 """Add the PIREP for the given flight."""
282 (result, _value) = \
283 self._performCall(lambda sessionID:
284 self._server.addPIREP(sessionID, flightID, pirep),
285 acceptResults = [Client.RESULT_FLIGHT_ALREADY_REPORTED,
286 Client.RESULT_FLIGHT_NOT_EXISTS])
287 return result
288
289 def updateOnlineACARS(self, acars):
290 """Update the online ACARS from the given data."""
291 self._performCall(lambda sessionID:
292 self._server.updateOnlineACARS(sessionID, acars))
293
294 def _performCall(self, callFn, acceptResults = []):
295 """Perform a call using the given call function.
296
297 acceptResults should be a list of result codes that should be accepted
298 besides RESULT_OK. If this list is not empty, the returned value is a
299 tuple of the result code and the corresponding value. Otherwise only
300 RESULT_OK is accepted, and the value is returned.
301
302 All other error codes are converted to exceptions."""
303 numAttempts = 0
304 while True:
305 reply = Reply(callFn(self._ensureSession()))
306 numAttempts += 1
307 result = reply.result
308 if result==Client.RESULT_SESSION_INVALID:
309 self._sessionID = None
310 if numAttempts==3:
311 raise RPCException(result)
312 elif result!=Client.RESULT_OK and result not in acceptResults:
313 raise RPCException(result)
314 elif acceptResults:
315 return (result, reply.value)
316 else:
317 return reply.value
318
319 def _ensureSession(self):
320 """Ensure that there is a valid session ID."""
321 while self._sessionID is None:
322 if self._userName is not None and self._passwordHash is not None:
323 if not self.login():
324 self._userName = self._passwordHash = None
325
326 if self._userName is None or self._passwordHash is None:
327 (self._userName, password) = self._getCredentialsFn()
328 if self._userName is None:
329 raise RPCException(Client.RESULT_LOGIN_FAILED)
330
331 md5 = hashlib.md5()
332 md5.update(password)
333 self._passwordHash = md5.hexdigest()
334
335 return self._sessionID
336
337#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.