11 from PyQt4 import QtCore
13 from util import qore_utils
14 from util import concurrent
15 from util import misc as misc_utils
20 _moduleLogger = logging.getLogger(__name__)
23 class _DraftContact(object):
25 def __init__(self, title, description, numbersWithDescriptions):
27 self.description = description
28 self.numbers = numbersWithDescriptions
29 self.selectedNumber = numbersWithDescriptions[0][0]
32 class Draft(QtCore.QObject):
34 sendingMessage = QtCore.pyqtSignal()
35 sentMessage = QtCore.pyqtSignal()
36 calling = QtCore.pyqtSignal()
37 called = QtCore.pyqtSignal()
38 cancelling = QtCore.pyqtSignal()
39 cancelled = QtCore.pyqtSignal()
40 error = QtCore.pyqtSignal(str)
42 recipientsChanged = QtCore.pyqtSignal()
44 def __init__(self, pool):
45 QtCore.QObject.__init__(self)
50 assert 0 < len(self._contacts)
51 numbers = [contact.selectedNumber for contact in self._contacts.itervalues()]
52 le = concurrent.AsyncLinearExecution(self._pool, self._send)
53 le.start(numbers, text)
56 assert len(self._contacts) == 1
57 (contact, ) = self._contacts.itervalues()
58 le = concurrent.AsyncLinearExecution(self._pool, self._call)
59 le.start(contact.selectedNumber)
62 le = concurrent.AsyncLinearExecution(self._pool, self._cancel)
65 def add_contact(self, contactId, title, description, numbersWithDescriptions):
66 assert contactId not in self._contacts
67 contactDetails = _DraftContact(title, description, numbersWithDescriptions)
68 self._contacts[contactId] = contactDetails
69 self.recipientsChanged.emit()
71 def remove_contact(self, contactId):
72 assert contactId in self._contacts
73 del self._contacts[contactId]
74 self.recipientsChanged.emit()
76 def get_contacts(self):
77 return self._contacts.iterkeys()
79 def get_num_contacts(self):
80 return len(self._contacts)
82 def get_title(self, cid):
83 return self._contacts[cid].title
85 def get_description(self, cid):
86 return self._contacts[cid].description
88 def get_numbers(self, cid):
89 return self._contacts[cid].numbers
91 def get_selected_number(self, cid):
92 return self._contacts[cid].selectedNumber
94 def set_selected_number(self, cid, number):
95 # @note I'm lazy, this isn't firing any kind of signal since only one
96 # controller right now and that is the viewer
97 return self._contacts[cid].numbers
100 oldContacts = self._contacts
103 self.recipientsChanged.emit()
105 def _send(self, numbers, text):
106 self.sendingMessage.emit()
108 self.error.emit("Not Implemented")
109 self.sentMessage.emit()
112 self.error.emit(str(e))
114 def _call(self, number):
117 self.error.emit("Not Implemented")
121 self.error.emit(str(e))
124 self.cancelling.emit()
127 self._backend.cancel,
131 self.cancelled.emit()
133 self.error.emit(str(e))
136 class Session(QtCore.QObject):
138 stateChange = QtCore.pyqtSignal(str)
139 loggedOut = QtCore.pyqtSignal()
140 loggedIn = QtCore.pyqtSignal()
141 callbackNumberChanged = QtCore.pyqtSignal(str)
143 contactsUpdated = QtCore.pyqtSignal()
144 messagesUpdated = QtCore.pyqtSignal()
145 historyUpdated = QtCore.pyqtSignal()
146 dndStateChange = QtCore.pyqtSignal(bool)
148 error = QtCore.pyqtSignal(str)
150 LOGGEDOUT_STATE = "logged out"
151 LOGGINGIN_STATE = "logging in"
152 LOGGEDIN_STATE = "logged in"
154 _OLDEST_COMPATIBLE_FORMAT_VERSION = misc_utils.parse_version("1.2.0")
159 def __init__(self, cachePath = None):
160 QtCore.QObject.__init__(self)
161 self._pool = qore_utils.AsyncPool()
163 self._loggedInTime = self._LOGGEDOUT_TIME
165 self._cachePath = cachePath
166 self._username = None
167 self._draft = Draft(self._pool)
178 self._LOGGEDOUT_TIME: self.LOGGEDOUT_STATE,
179 self._LOGGINGIN_TIME: self.LOGGINGIN_STATE,
180 }.get(self._loggedInTime, self.LOGGEDIN_STATE)
186 def login(self, username, password):
187 assert self.state == self.LOGGEDOUT_STATE
188 assert username != ""
189 if self._cachePath is not None:
190 cookiePath = os.path.join(self._cachePath, "%s.cookies" % username)
194 if self._username != username or self._backend is None:
195 from backends import gv_backend
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_account_number(self):
275 return self._backend.get_account_number()
277 def get_callback_numbers(self):
278 # @todo Remove evilness
279 return self._backend.get_callback_numbers()
281 def get_callback_number(self):
282 return self._callback
284 def set_callback_number(self, callback):
285 # I'm paranoid about our state geting out of sync so we set no matter
286 # what but act as if we have the cannonical state
287 assert self.state == self.LOGGEDIN_STATE
288 oldCallback = self._callback
291 self._backend.set_callback_number,
296 self.error.emit(str(e))
298 self._callback = callback
299 if oldCallback != self._callback:
300 self.callbackNumberChanged.emit(self._callback)
302 def _login(self, username, password):
303 self._loggedInTime = self._LOGGINGIN_TIME
304 self.stateChange.emit(self.LOGGINGIN_STATE)
305 finalState = self.LOGGEDOUT_STATE
309 if not isLoggedIn and self._backend.is_quick_login_possible():
311 self._backend.is_authed,
316 _moduleLogger.info("Logged in through cookies")
318 # Force a clearing of the cookies
320 self._backend.logout,
328 (username, password),
332 _moduleLogger.info("Logged in through credentials")
335 self._loggedInTime = int(time.time())
336 oldUsername = self._username
337 self._username = username
338 finalState = self.LOGGEDIN_STATE
340 if oldUsername != self._username:
341 needOps = not self._load()
345 loginOps = self._loginOps[:]
348 del self._loginOps[:]
349 for asyncOp in loginOps:
352 self.error.emit(str(e))
354 self.stateChange.emit(finalState)
357 updateContacts = len(self._contacts) != 0
358 updateMessages = len(self._messages) != 0
359 updateHistory = len(self._history) != 0
361 oldCallback = self._callback
369 loadedFromCache = self._load_from_cache()
371 updateContacts = True
372 updateMessages = True
376 self.contactsUpdated.emit()
378 self.messagesUpdated.emit()
380 self.historyUpdated.emit()
381 if oldDnd != self._dnd:
382 self.dndStateChange.emit(self._dnd)
383 if oldCallback != self._callback:
384 self.callbackNumberChanged.emit(self._callback)
386 return loadedFromCache
388 def _load_from_cache(self):
389 if self._cachePath is None:
391 cachePath = os.path.join(self._cachePath, "%s.cache" % self._username)
394 with open(cachePath, "rb") as f:
395 dumpedData = pickle.load(f)
396 except (pickle.PickleError, IOError, EOFError, ValueError):
397 _moduleLogger.exception("Pickle fun loading")
400 _moduleLogger.exception("Weirdness loading")
405 contacts, messages, history, dnd, callback
408 if misc_utils.compare_versions(
409 self._OLDEST_COMPATIBLE_FORMAT_VERSION,
410 misc_utils.parse_version(version),
412 _moduleLogger.info("Loaded cache")
413 self._contacts = contacts
414 self._messages = messages
415 self._history = history
417 self._callback = callback
421 "Skipping cache due to version mismatch (%s-%s)" % (
427 def _save_to_cache(self):
428 _moduleLogger.info("Saving cache")
429 if self._cachePath is None:
431 cachePath = os.path.join(self._cachePath, "%s.cache" % self._username)
435 constants.__version__, constants.__build__,
436 self._contacts, self._messages, self._history, self._dnd, self._callback
438 with open(cachePath, "wb") as f:
439 pickle.dump(dataToDump, f, pickle.HIGHEST_PROTOCOL)
440 _moduleLogger.info("Cache saved")
441 except (pickle.PickleError, IOError):
442 _moduleLogger.exception("While saving")
444 def _clear_cache(self):
445 updateContacts = len(self._contacts) != 0
446 updateMessages = len(self._messages) != 0
447 updateHistory = len(self._history) != 0
449 oldCallback = self._callback
458 self.contactsUpdated.emit()
460 self.messagesUpdated.emit()
462 self.historyUpdated.emit()
463 if oldDnd != self._dnd:
464 self.dndStateChange.emit(self._dnd)
465 if oldCallback != self._callback:
466 self.callbackNumberChanged.emit(self._callback)
468 self._save_to_cache()
470 def _update_contacts(self):
472 self._contacts = yield (
473 self._backend.get_contacts,
478 self.error.emit(str(e))
480 self.contactsUpdated.emit()
482 def _update_messages(self):
484 self._messages = yield (
485 self._backend.get_messages,
490 self.error.emit(str(e))
492 self.messagesUpdated.emit()
494 def _update_history(self):
496 self._history = yield (
497 self._backend.get_recent,
502 self.error.emit(str(e))
504 self.historyUpdated.emit()
506 def _update_dnd(self):
510 self._backend.is_dnd,
515 self.error.emit(str(e))
517 if oldDnd != self._dnd:
518 self.dndStateChange(self._dnd)
520 def _perform_op_while_loggedin(self, op):
521 if self.state == self.LOGGEDIN_STATE:
524 self._push_login_op(op)
526 def _push_login_op(self, asyncOp):
527 assert self.state != self.LOGGEDIN_STATE
528 if asyncOp in self._loginOps:
529 _moduleLogger.info("Skipping queueing duplicate op: %r" % asyncOp)
531 self._loginOps.append(asyncOp)