11 from PyQt4 import QtCore
13 from util import qore_utils
14 from util import concurrent
15 from util import misc as misc_utils
18 from backends import gv_backend
21 _moduleLogger = logging.getLogger(__name__)
24 class _DraftContact(object):
26 def __init__(self, title, description, numbersWithDescriptions):
28 self.description = description
29 self.numbers = numbersWithDescriptions
30 self.selectedNumber = numbersWithDescriptions[0][0]
33 class Draft(QtCore.QObject):
35 sendingMessage = QtCore.pyqtSignal()
36 sentMessage = QtCore.pyqtSignal()
37 calling = QtCore.pyqtSignal()
38 called = QtCore.pyqtSignal()
39 cancelling = QtCore.pyqtSignal()
40 cancelled = QtCore.pyqtSignal()
41 error = QtCore.pyqtSignal(str)
43 recipientsChanged = QtCore.pyqtSignal()
45 def __init__(self, pool):
46 QtCore.QObject.__init__(self)
51 assert 0 < len(self._contacts)
52 numbers = [contact.selectedNumber for contact in self._contacts.itervalues()]
53 le = concurrent.AsyncLinearExecution(self._pool, self._send)
54 le.start(numbers, text)
57 assert len(self._contacts) == 1
58 (contact, ) = self._contacts.itervalues()
59 le = concurrent.AsyncLinearExecution(self._pool, self._call)
60 le.start(contact.selectedNumber)
63 le = concurrent.AsyncLinearExecution(self._pool, self._cancel)
66 def add_contact(self, contactId, title, description, numbersWithDescriptions):
67 assert contactId not in self._contacts
68 contactDetails = _DraftContact(title, description, numbersWithDescriptions)
69 self._contacts[contactId] = contactDetails
70 self.recipientsChanged.emit()
72 def remove_contact(self, contactId):
73 assert contactId in self._contacts
74 del self._contacts[contactId]
75 self.recipientsChanged.emit()
77 def get_contacts(self):
78 return self._contacts.iterkeys()
80 def get_num_contacts(self):
81 return len(self._contacts)
83 def get_title(self, cid):
84 return self._contacts[cid].title
86 def get_description(self, cid):
87 return self._contacts[cid].description
89 def get_numbers(self, cid):
90 return self._contacts[cid].numbers
92 def get_selected_number(self, cid):
93 return self._contacts[cid].selectedNumber
95 def set_selected_number(self, cid, number):
96 # @note I'm lazy, this isn't firing any kind of signal since only one
97 # controller right now and that is the viewer
98 return self._contacts[cid].numbers
101 oldContacts = self._contacts
104 self.recipientsChanged.emit()
106 def _send(self, numbers, text):
107 self.sendingMessage.emit()
109 self.error.emit("Not Implemented")
110 self.sentMessage.emit()
113 self.error.emit(str(e))
115 def _call(self, number):
118 self.error.emit("Not Implemented")
122 self.error.emit(str(e))
125 self.cancelling.emit()
128 self._backend.cancel,
132 self.cancelled.emit()
134 self.error.emit(str(e))
137 class Session(QtCore.QObject):
139 stateChange = QtCore.pyqtSignal(str)
140 loggedOut = QtCore.pyqtSignal()
141 loggedIn = QtCore.pyqtSignal()
142 callbackNumberChanged = QtCore.pyqtSignal(str)
144 contactsUpdated = QtCore.pyqtSignal()
145 messagesUpdated = QtCore.pyqtSignal()
146 historyUpdated = QtCore.pyqtSignal()
147 dndStateChange = QtCore.pyqtSignal(bool)
149 error = QtCore.pyqtSignal(str)
151 LOGGEDOUT_STATE = "logged out"
152 LOGGINGIN_STATE = "logging in"
153 LOGGEDIN_STATE = "logged in"
155 _OLDEST_COMPATIBLE_FORMAT_VERSION = misc_utils.parse_version("1.2.0")
160 def __init__(self, cachePath = None):
161 QtCore.QObject.__init__(self)
162 self._pool = qore_utils.AsyncPool()
164 self._loggedInTime = self._LOGGEDOUT_TIME
166 self._cachePath = cachePath
167 self._username = None
168 self._draft = Draft(self._pool)
179 self._LOGGEDOUT_TIME: self.LOGGEDOUT_STATE,
180 self._LOGGINGIN_TIME: self.LOGGINGIN_STATE,
181 }.get(self._loggedInTime, self.LOGGEDIN_STATE)
187 def login(self, username, password):
188 assert self.state == self.LOGGEDOUT_STATE
189 assert username != ""
190 if self._cachePath is not None:
191 cookiePath = os.path.join(self._cachePath, "%s.cookies" % username)
195 if self._username != username or self._backend is None:
196 self._backend = gv_backend.GVDialer(cookiePath)
199 le = concurrent.AsyncLinearExecution(self._pool, self._login)
200 le.start(username, password)
203 assert self.state != self.LOGGEDOUT_STATE
205 self._loggedInTime = self._LOGGEDOUT_TIME
206 self._backend.persist()
207 self._save_to_cache()
210 assert self.state == self.LOGGEDOUT_STATE
211 self._backend.logout()
216 def logout_and_clear(self):
217 assert self.state != self.LOGGEDOUT_STATE
219 self._loggedInTime = self._LOGGEDOUT_TIME
222 def update_contacts(self, force = True):
223 if not force and self._contacts:
225 le = concurrent.AsyncLinearExecution(self._pool, self._update_contacts)
226 self._perform_op_while_loggedin(le)
228 def get_contacts(self):
229 return self._contacts
231 def update_messages(self, force = True):
232 if not force and self._messages:
234 le = concurrent.AsyncLinearExecution(self._pool, self._update_messages)
235 self._perform_op_while_loggedin(le)
237 def get_messages(self):
238 return self._messages
240 def update_history(self, force = True):
241 if not force and self._history:
243 le = concurrent.AsyncLinearExecution(self._pool, self._update_history)
244 self._perform_op_while_loggedin(le)
246 def get_history(self):
249 def update_dnd(self):
250 le = concurrent.AsyncLinearExecution(self._pool, self._update_dnd)
251 self._perform_op_while_loggedin(le)
253 def set_dnd(self, dnd):
254 # I'm paranoid about our state geting out of sync so we set no matter
255 # what but act as if we have the cannonical state
256 assert self.state == self.LOGGEDIN_STATE
260 self._backend.set_dnd,
265 self.error.emit(str(e))
268 if oldDnd != self._dnd:
269 self.dndStateChange.emit(self._dnd)
274 def get_callback_numbers(self):
275 # @todo Remove evilness
276 return self._backend.get_callback_numbers()
278 def get_callback_number(self):
279 return self._callback
281 def set_callback_number(self, callback):
282 # I'm paranoid about our state geting out of sync so we set no matter
283 # what but act as if we have the cannonical state
284 assert self.state == self.LOGGEDIN_STATE
285 oldCallback = self._callback
288 self._backend.set_callback_number,
293 self.error.emit(str(e))
295 self._callback = callback
296 if oldCallback != self._callback:
297 self.callbackNumberChanged.emit(self._callback)
299 def _login(self, username, password):
300 self._loggedInTime = self._LOGGINGIN_TIME
301 self.stateChange.emit(self.LOGGINGIN_STATE)
302 finalState = self.LOGGEDOUT_STATE
306 if not isLoggedIn and self._backend.is_quick_login_possible():
308 self._backend.is_authed,
313 _moduleLogger.info("Logged in through cookies")
315 # Force a clearing of the cookies
317 self._backend.logout,
325 (username, password),
329 _moduleLogger.info("Logged in through credentials")
332 self._loggedInTime = int(time.time())
333 oldUsername = self._username
334 self._username = username
335 finalState = self.LOGGEDIN_STATE
337 if oldUsername != self._username:
338 needOps = not self._load()
342 loginOps = self._loginOps[:]
345 del self._loginOps[:]
346 for asyncOp in loginOps:
349 self.error.emit(str(e))
351 self.stateChange.emit(finalState)
354 updateContacts = len(self._contacts) != 0
355 updateMessages = len(self._messages) != 0
356 updateHistory = len(self._history) != 0
358 oldCallback = self._callback
366 loadedFromCache = self._load_from_cache()
368 updateContacts = True
369 updateMessages = True
373 self.contactsUpdated.emit()
375 self.messagesUpdated.emit()
377 self.historyUpdated.emit()
378 if oldDnd != self._dnd:
379 self.dndStateChange.emit(self._dnd)
380 if oldCallback != self._callback:
381 self.callbackNumberChanged.emit(self._callback)
383 return loadedFromCache
385 def _load_from_cache(self):
386 if self._cachePath is None:
388 cachePath = os.path.join(self._cachePath, "%s.cache" % self._username)
391 with open(cachePath, "rb") as f:
392 dumpedData = pickle.load(f)
393 except (pickle.PickleError, IOError, EOFError, ValueError):
394 _moduleLogger.exception("Pickle fun loading")
397 _moduleLogger.exception("Weirdness loading")
402 contacts, messages, history, dnd, callback
405 if misc_utils.compare_versions(
406 self._OLDEST_COMPATIBLE_FORMAT_VERSION,
407 misc_utils.parse_version(version),
409 _moduleLogger.info("Loaded cache")
410 self._contacts = contacts
411 self._messages = messages
412 self._history = history
414 self._callback = callback
418 "Skipping cache due to version mismatch (%s-%s)" % (
424 def _save_to_cache(self):
425 _moduleLogger.info("Saving cache")
426 if self._cachePath is None:
428 cachePath = os.path.join(self._cachePath, "%s.cache" % self._username)
432 constants.__version__, constants.__build__,
433 self._contacts, self._messages, self._history, self._dnd, self._callback
435 with open(cachePath, "wb") as f:
436 pickle.dump(dataToDump, f, pickle.HIGHEST_PROTOCOL)
437 _moduleLogger.info("Cache saved")
438 except (pickle.PickleError, IOError):
439 _moduleLogger.exception("While saving")
441 def _clear_cache(self):
442 updateContacts = len(self._contacts) != 0
443 updateMessages = len(self._messages) != 0
444 updateHistory = len(self._history) != 0
446 oldCallback = self._callback
455 self.contactsUpdated.emit()
457 self.messagesUpdated.emit()
459 self.historyUpdated.emit()
460 if oldDnd != self._dnd:
461 self.dndStateChange.emit(self._dnd)
462 if oldCallback != self._callback:
463 self.callbackNumberChanged.emit(self._callback)
465 self._save_to_cache()
467 def _update_contacts(self):
469 self._contacts = yield (
470 self._backend.get_contacts,
475 self.error.emit(str(e))
477 self.contactsUpdated.emit()
479 def _update_messages(self):
481 self._messages = yield (
482 self._backend.get_messages,
487 self.error.emit(str(e))
489 self.messagesUpdated.emit()
491 def _update_history(self):
493 self._history = yield (
494 self._backend.get_recent,
499 self.error.emit(str(e))
501 self.historyUpdated.emit()
503 def _update_dnd(self):
507 self._backend.is_dnd,
512 self.error.emit(str(e))
514 if oldDnd != self._dnd:
515 self.dndStateChange(self._dnd)
517 def _perform_op_while_loggedin(self, op):
518 if self.state == self.LOGGEDIN_STATE:
521 self._push_login_op(op)
523 def _push_login_op(self, asyncOp):
524 assert self.state != self.LOGGEDIN_STATE
525 if asyncOp in self._loginOps:
526 _moduleLogger.info("Skipping queueing duplicate op: %r" % asyncOp)
528 self._loginOps.append(asyncOp)