source: src/mlx/rpc.py@ 760:733c148959e9

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

The rank is returned when logging in, and in case of a student, we jump to the student page

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