source: src/mlx/rpc.py@ 816:4f970cbc02b9

Last change on this file since 816:4f970cbc02b9 was 809:4da4f80a2ab4, checked in by István Váradi <ivaradi@…>, 8 years ago

The past, pending flights are retrieved (re #307)

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