source: src/mlx/rpc.py@ 776:1ca67c3fda73

Last change on this file since 776:1ca67c3fda73 was 769:d10b450fff75, checked in by István Váradi <ivaradi@…>, 9 years ago

The check flight can also be performed and the registration logic is hopefully complete (re #285)

File size: 12.5 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"], value["madeFO"])
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 setCheckFlightPassed(self, type):
296 """Mark the check flight of the user passed with the given type."""
297 self._performCall(lambda sessionID:
298 self._server.setCheckFlightPassed(sessionID, type))
299
300 def _performCall(self, callFn, acceptResults = []):
301 """Perform a call using the given call function.
302
303 acceptResults should be a list of result codes that should be accepted
304 besides RESULT_OK. If this list is not empty, the returned value is a
305 tuple of the result code and the corresponding value. Otherwise only
306 RESULT_OK is accepted, and the value is returned.
307
308 All other error codes are converted to exceptions."""
309 numAttempts = 0
310 while True:
311 reply = Reply(callFn(self._ensureSession()))
312 numAttempts += 1
313 result = reply.result
314 if result==Client.RESULT_SESSION_INVALID:
315 self._sessionID = None
316 if numAttempts==3:
317 raise RPCException(result)
318 elif result!=Client.RESULT_OK and result not in acceptResults:
319 raise RPCException(result)
320 elif acceptResults:
321 return (result, reply.value)
322 else:
323 return reply.value
324
325 def _ensureSession(self):
326 """Ensure that there is a valid session ID."""
327 while self._sessionID is None:
328 if self._userName is not None and self._passwordHash is not None:
329 if not self.login():
330 self._userName = self._passwordHash = None
331
332 if self._userName is None or self._passwordHash is None:
333 (self._userName, password) = self._getCredentialsFn()
334 if self._userName is None:
335 raise RPCException(Client.RESULT_LOGIN_FAILED)
336
337 md5 = hashlib.md5()
338 md5.update(password)
339 self._passwordHash = md5.hexdigest()
340
341 return self._sessionID
342
343#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.