source: src/mlx/rpc.py@ 800:709f86ab4573

Last change on this file since 800:709f86ab4573 was 790:e69a08004f40, checked in by István Váradi <ivaradi@…>, 8 years ago

Added support for the Boeing 737-200 aircraft type (weight data is still missing, re #302)

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