source: src/mlx/rpc.py@ 754:8547ad78cc29

Last change on this file since 754:8547ad78cc29 was 745:14b7e950265f, checked in by István Váradi <ivaradi@…>, 9 years ago

Implemented the client for the remote calls (re #283)

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