source: src/mlx/rpc.py@ 756:673a9809df08

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

Implemented the basic GUI logic of the registration (re #285)

File size: 11.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 "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, name, yearOfBirth, emailAddress, emailAddressPublic,
130 vatsimID, ivaoID, phoneNumber, nationality, password):
131 """Construct the registration data."""
132 self.name = name
133 self.yearOfBirth = yearOfBirth
134 self.emailAddress = emailAddress
135 self.emailAddressPublic = 1 if emailAddressPublic is True else \
136 0 if emailAddressPublic is False else emailAddressPublic
137 self.vatsimID = "" if vatsimID is None else vatsimID
138 self.ivaoID = "" if ivaoID is None else ivaoID
139 self.phoneNumber = phoneNumber
140 self.nationality = nationality
141 self.password = password
142
143#---------------------------------------------------------------------------------------
144
145class RPCException(Exception):
146 """An exception thrown by RPC operations."""
147 def __init__(self, result, message = None):
148 """Construct the exception."""
149 self._result = result
150 if message is None:
151 message = "RPC call failed with result code: %d" % (result,)
152 super(RPCException, self).__init__(message)
153
154 @property
155 def result(self):
156 """Get the result code."""
157 return self._result
158
159#---------------------------------------------------------------------------------------
160
161class Client(object):
162 """The RPC client interface."""
163 # Result code: OK
164 RESULT_OK = 0
165
166 # Result code: the login has failed
167 RESULT_LOGIN_FAILED = 1
168
169 # Result code: the given session ID is unknown (it might have expired).
170 RESULT_SESSION_INVALID = 2
171
172 # Result code: some database error
173 RESULT_DATABASE_ERROR = 3
174
175 # Result code: invalid data
176 RESULT_INVALID_DATA = 4
177
178 # Result code: the flight does not exist
179 RESULT_FLIGHT_NOT_EXISTS = 101
180
181 # Result code: the flight has already been reported.
182 RESULT_FLIGHT_ALREADY_REPORTED = 102
183
184 # Result code: a user with the given e-mail address already exists
185 RESULT_EMAIL_ALREADY_REGISTERED = 103
186
187 def __init__(self, getCredentialsFn):
188 """Construct the client."""
189 self._getCredentialsFn = getCredentialsFn
190
191 self._server = jsonrpclib.Server(MAVA_BASE_URL + "/jsonrpc.php")
192
193 self._userName = None
194 self._passwordHash = None
195 self._sessionID = None
196 self._loginCount = 0
197
198 @property
199 def valid(self):
200 """Determine if the client is valid, i.e. there is a session ID
201 stored."""
202 return self._sessionID is not None
203
204 def setCredentials(self, userName, password):
205 """Set the credentials for future logins."""
206
207 self._userName = userName
208
209 md5 = hashlib.md5()
210 md5.update(password)
211 self._passwordHash = md5.hexdigest()
212
213 self._sessionID = None
214
215 def register(self, registrationData):
216 """Register with the given data.
217
218 Returns a tuple of:
219 - the error code,
220 - the PID if there is no error."""
221 reply = Reply(self._server.register(registrationData))
222
223 return (reply.result,
224 reply.value["pid"] if reply.result==Client.RESULT_OK else None)
225
226 def login(self):
227 """Login using the given previously set credentials.
228
229 The session ID is stored in the object and used for later calls.
230
231 Returns the name of the pilot on success, or None on error."""
232 self._sessionID = None
233
234 reply = Reply(self._server.login(self._userName, self._passwordHash))
235 if reply.result == Client.RESULT_OK:
236 self._loginCount += 1
237 self._sessionID = reply.value["sessionID"]
238 return reply.value["name"]
239 else:
240 return None
241
242 def getFlights(self):
243 """Get the flights available for performing."""
244 flights = []
245
246 value = self._performCall(lambda sessionID:
247 self._server.getFlights(sessionID))
248 for flightData in value:
249 flights.append(BookedFlight(flightData))
250
251 flights.sort(cmp = lambda flight1, flight2:
252 cmp(flight1.departureTime, flight2.departureTime))
253
254 return flights
255
256 def getFleet(self):
257 """Query and return the fleet."""
258 value = self._performCall(lambda sessionID:
259 self._server.getFleet(sessionID))
260
261 return Fleet(value)
262
263 def updatePlane(self, tailNumber, status, gateNumber):
264 """Update the state and position of the plane with the given tail
265 number."""
266 status = rpccommon.Plane.status2str(status)
267 self._performCall(lambda sessionID:
268 self._server.updatePlane(sessionID, tailNumber,
269 status, gateNumber))
270
271 def addPIREP(self, flightID, pirep):
272 """Add the PIREP for the given flight."""
273 (result, _value) = \
274 self._performCall(lambda sessionID:
275 self._server.addPIREP(sessionID, flightID, pirep),
276 acceptResults = [Client.RESULT_FLIGHT_ALREADY_REPORTED,
277 Client.RESULT_FLIGHT_NOT_EXISTS])
278 return result
279
280 def updateOnlineACARS(self, acars):
281 """Update the online ACARS from the given data."""
282 self._performCall(lambda sessionID:
283 self._server.updateOnlineACARS(sessionID, acars))
284
285 def _performCall(self, callFn, acceptResults = []):
286 """Perform a call using the given call function.
287
288 acceptResults should be a list of result codes that should be accepted
289 besides RESULT_OK. If this list is not empty, the returned value is a
290 tuple of the result code and the corresponding value. Otherwise only
291 RESULT_OK is accepted, and the value is returned.
292
293 All other error codes are converted to exceptions."""
294 numAttempts = 0
295 while True:
296 reply = Reply(callFn(self._ensureSession()))
297 numAttempts += 1
298 result = reply.result
299 if result==Client.RESULT_SESSION_INVALID:
300 self._sessionID = None
301 if numAttempts==3:
302 raise RPCException(result)
303 elif result!=Client.RESULT_OK and result not in acceptResults:
304 raise RPCException(result)
305 elif acceptResults:
306 return (result, reply.value)
307 else:
308 return reply.value
309
310 def _ensureSession(self):
311 """Ensure that there is a valid session ID."""
312 while self._sessionID is None:
313 if self._userName is not None and self._passwordHash is not None:
314 if not self.login():
315 self._userName = self._passwordHash = None
316
317 if self._userName is None or self._passwordHash is None:
318 (self._userName, password) = self._getCredentialsFn()
319 if self._userName is None:
320 raise RPCException(Client.RESULT_LOGIN_FAILED)
321
322 md5 = hashlib.md5()
323 md5.update(password)
324 self._passwordHash = md5.hexdigest()
325
326 return self._sessionID
327
328#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.