source: src/mlx/rpc.py@ 764:8fd245311db5

Last change on this file since 764:8fd245311db5 was 764:8fd245311db5, checked in by István Váradi <ivaradi@…>, 8 years ago

The entry exam is called in the browser when requested and the status is displayed in text as well (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["entryExamLink"],
264 value["checkFlightStatus"])
265
266 def getFleet(self):
267 """Query and return the fleet."""
268 value = self._performCall(lambda sessionID:
269 self._server.getFleet(sessionID))
270
271 return Fleet(value)
272
273 def updatePlane(self, tailNumber, status, gateNumber):
274 """Update the state and position of the plane with the given tail
275 number."""
276 status = rpccommon.Plane.status2str(status)
277 self._performCall(lambda sessionID:
278 self._server.updatePlane(sessionID, tailNumber,
279 status, gateNumber))
280
281 def addPIREP(self, flightID, pirep):
282 """Add the PIREP for the given flight."""
283 (result, _value) = \
284 self._performCall(lambda sessionID:
285 self._server.addPIREP(sessionID, flightID, pirep),
286 acceptResults = [Client.RESULT_FLIGHT_ALREADY_REPORTED,
287 Client.RESULT_FLIGHT_NOT_EXISTS])
288 return result
289
290 def updateOnlineACARS(self, acars):
291 """Update the online ACARS from the given data."""
292 self._performCall(lambda sessionID:
293 self._server.updateOnlineACARS(sessionID, acars))
294
295 def _performCall(self, callFn, acceptResults = []):
296 """Perform a call using the given call function.
297
298 acceptResults should be a list of result codes that should be accepted
299 besides RESULT_OK. If this list is not empty, the returned value is a
300 tuple of the result code and the corresponding value. Otherwise only
301 RESULT_OK is accepted, and the value is returned.
302
303 All other error codes are converted to exceptions."""
304 numAttempts = 0
305 while True:
306 reply = Reply(callFn(self._ensureSession()))
307 numAttempts += 1
308 result = reply.result
309 if result==Client.RESULT_SESSION_INVALID:
310 self._sessionID = None
311 if numAttempts==3:
312 raise RPCException(result)
313 elif result!=Client.RESULT_OK and result not in acceptResults:
314 raise RPCException(result)
315 elif acceptResults:
316 return (result, reply.value)
317 else:
318 return reply.value
319
320 def _ensureSession(self):
321 """Ensure that there is a valid session ID."""
322 while self._sessionID is None:
323 if self._userName is not None and self._passwordHash is not None:
324 if not self.login():
325 self._userName = self._passwordHash = None
326
327 if self._userName is None or self._passwordHash is None:
328 (self._userName, password) = self._getCredentialsFn()
329 if self._userName is None:
330 raise RPCException(Client.RESULT_LOGIN_FAILED)
331
332 md5 = hashlib.md5()
333 md5.update(password)
334 self._passwordHash = md5.hexdigest()
335
336 return self._sessionID
337
338#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.