Removing contact caching from backend, moving it to session
[gc-dialer] / src / session.py
1 import os
2 import time
3 import logging
4
5 from PyQt4 import QtCore
6
7 from util import qore_utils
8 from util import concurrent
9
10 from backends import gv_backend
11
12
13 _moduleLogger = logging.getLogger(__name__)
14
15
16 class Draft(QtCore.QObject):
17
18         sendingMessage = QtCore.pyqtSignal()
19         sentMessage = QtCore.pyqtSignal()
20         calling = QtCore.pyqtSignal()
21         called = QtCore.pyqtSignal()
22         cancelling = QtCore.pyqtSignal()
23         cancelled = QtCore.pyqtSignal()
24         error = QtCore.pyqtSignal(str)
25
26         recipientsChanged = QtCore.pyqtSignal()
27
28         def __init__(self, pool):
29                 QtCore.QObject.__init__(self)
30                 self._contacts = {}
31                 self._pool = pool
32
33         def send(self, text):
34                 assert 0 < len(self._contacts)
35                 le = concurrent.AsyncLinearExecution(self._pool, self._send)
36                 le.start(text)
37
38         def call(self):
39                 assert len(self._contacts) == 1
40                 le = concurrent.AsyncLinearExecution(self._pool, self._call)
41                 le.start()
42
43         def cancel(self):
44                 le = concurrent.AsyncLinearExecution(self._pool, self._cancel)
45                 le.start()
46
47         def add_contact(self, contactId, title, description, numbersWithDescriptions):
48                 assert contactId not in self._contacts
49                 contactDetails = title, description, numbersWithDescriptions
50                 self._contacts[contactId] = contactDetails
51                 self.recipientsChanged.emit()
52
53         def remove_contact(self, contactId):
54                 assert contactId in self._contacts
55                 del self._contacts[contactId]
56                 self.recipientsChanged.emit()
57
58         def get_contacts(self):
59                 return self._contacts
60
61         def clear(self):
62                 self._contacts = {}
63                 self.recipientsChanged.emit()
64
65         def _send(self, text):
66                 self.sendingMessage.emit()
67                 try:
68                         self.error.emit("Not Implemented")
69                         self.sentMessage.emit()
70                         self.clear()
71                 except Exception, e:
72                         self.error.emit(str(e))
73
74         def _call(self):
75                 self.calling.emit()
76                 try:
77                         self.error.emit("Not Implemented")
78                         self.called.emit()
79                         self.clear()
80                 except Exception, e:
81                         self.error.emit(str(e))
82
83         def _cancel(self):
84                 self.cancelling.emit()
85                 try:
86                         yield (
87                                 self._backend.cancel,
88                                 (),
89                                 {},
90                         )
91                         self.cancelled.emit()
92                 except Exception, e:
93                         self.error.emit(str(e))
94
95
96 class Session(QtCore.QObject):
97
98         stateChange = QtCore.pyqtSignal(str)
99         loggedOut = QtCore.pyqtSignal()
100         loggedIn = QtCore.pyqtSignal()
101         callbackNumberChanged = QtCore.pyqtSignal(str)
102
103         contactsUpdated = QtCore.pyqtSignal()
104         messagesUpdated = QtCore.pyqtSignal()
105         historyUpdated = QtCore.pyqtSignal()
106         dndStateChange = QtCore.pyqtSignal(bool)
107
108         error = QtCore.pyqtSignal(str)
109
110         LOGGEDOUT_STATE = "logged out"
111         LOGGINGIN_STATE = "logging in"
112         LOGGEDIN_STATE = "logged in"
113
114         _LOGGEDOUT_TIME = -1
115         _LOGGINGIN_TIME = 0
116
117         def __init__(self, cachePath = None):
118                 QtCore.QObject.__init__(self)
119                 self._pool = qore_utils.AsyncPool()
120                 self._backend = None
121                 self._loggedInTime = self._LOGGEDOUT_TIME
122                 self._loginOps = []
123                 self._cachePath = cachePath
124                 self._username = None
125                 self._draft = Draft(self._pool)
126
127                 self._contacts = {}
128                 self._messages = []
129                 self._history = []
130                 self._dnd = False
131                 self._callback = ""
132
133         @property
134         def state(self):
135                 return {
136                         self._LOGGEDOUT_TIME: self.LOGGEDOUT_STATE,
137                         self._LOGGINGIN_TIME: self.LOGGINGIN_STATE,
138                 }.get(self._loggedInTime, self.LOGGEDIN_STATE)
139
140         @property
141         def draft(self):
142                 return self._draft
143
144         def login(self, username, password):
145                 assert self.state == self.LOGGEDOUT_STATE
146                 assert username != ""
147                 if self._cachePath is not None:
148                         cookiePath = os.path.join(self._cachePath, "%s.cookies" % username)
149                 else:
150                         cookiePath = None
151
152                 if self._username != username or self._backend is None:
153                         self._backend = gv_backend.GVDialer(cookiePath)
154
155                 self._pool.start()
156                 le = concurrent.AsyncLinearExecution(self._pool, self._login)
157                 le.start(username, password)
158
159         def logout(self):
160                 assert self.state != self.LOGGEDOUT_STATE
161                 self._pool.stop()
162                 self._loggedInTime = self._LOGGEDOUT_TIME
163                 self._backend.persist()
164                 self._save_to_cache()
165
166         def clear(self):
167                 assert self.state == self.LOGGEDOUT_STATE
168                 self._backend.logout()
169                 self._backend = None
170                 self._clear_cache()
171                 self._draft.clear()
172
173         def logout_and_clear(self):
174                 assert self.state != self.LOGGEDOUT_STATE
175                 self._pool.stop()
176                 self._loggedInTime = self._LOGGEDOUT_TIME
177                 self.clear()
178
179         def update_contacts(self):
180                 le = concurrent.AsyncLinearExecution(self._pool, self._update_contacts)
181                 self._perform_op_while_loggedin(le)
182
183         def get_contacts(self):
184                 return self._contacts
185
186         def update_messages(self):
187                 le = concurrent.AsyncLinearExecution(self._pool, self._update_messages)
188                 self._perform_op_while_loggedin(le)
189
190         def get_messages(self):
191                 return self._messages
192
193         def update_history(self):
194                 le = concurrent.AsyncLinearExecution(self._pool, self._update_history)
195                 self._perform_op_while_loggedin(le)
196
197         def get_history(self):
198                 return self._history
199
200         def update_dnd(self):
201                 le = concurrent.AsyncLinearExecution(self._pool, self._update_dnd)
202                 self._perform_op_while_loggedin(le)
203
204         def set_dnd(self, dnd):
205                 # I'm paranoid about our state geting out of sync so we set no matter
206                 # what but act as if we have the cannonical state
207                 assert self.state == self.LOGGEDIN_STATE
208                 oldDnd = self._dnd
209                 try:
210                         yield (
211                                 self._backend.set_dnd,
212                                 (dnd),
213                                 {},
214                         )
215                 except Exception, e:
216                         self.error.emit(str(e))
217                         return
218                 self._dnd = dnd
219                 if oldDnd != self._dnd:
220                         self.dndStateChange.emit(self._dnd)
221
222         def get_dnd(self):
223                 return self._dnd
224
225         def get_callback_numbers(self):
226                 # @todo Remove evilness
227                 return self._backend.get_callback_numbers()
228
229         def get_callback_number(self):
230                 return self._callback
231
232         def set_callback_number(self, callback):
233                 # I'm paranoid about our state geting out of sync so we set no matter
234                 # what but act as if we have the cannonical state
235                 assert self.state == self.LOGGEDIN_STATE
236                 oldCallback = self._callback
237                 try:
238                         yield (
239                                 self._backend.set_callback_number,
240                                 (callback),
241                                 {},
242                         )
243                 except Exception, e:
244                         self.error.emit(str(e))
245                         return
246                 self._callback = callback
247                 if oldCallback != self._callback:
248                         self.callbackNumberChanged.emit(self._callback)
249
250         def _login(self, username, password):
251                 self._loggedInTime = self._LOGGINGIN_TIME
252                 self.stateChange.emit(self.LOGGINGIN_STATE)
253                 finalState = self.LOGGEDOUT_STATE
254                 try:
255                         isLoggedIn = False
256
257                         if not isLoggedIn and self._backend.is_quick_login_possible():
258                                 isLoggedIn = yield (
259                                         self._backend.is_authed,
260                                         (),
261                                         {},
262                                 )
263                                 if isLoggedIn:
264                                         _moduleLogger.info("Logged in through cookies")
265                                 else:
266                                         # Force a clearing of the cookies
267                                         yield (
268                                                 self._backend.logout,
269                                                 (),
270                                                 {},
271                                         )
272
273                         if not isLoggedIn:
274                                 isLoggedIn = yield (
275                                         self._backend.login,
276                                         (username, password),
277                                         {},
278                                 )
279                                 if isLoggedIn:
280                                         _moduleLogger.info("Logged in through credentials")
281
282                         if isLoggedIn:
283                                 self._loggedInTime = int(time.time())
284                                 oldUsername = self._username
285                                 self._username = username
286                                 finalState = self.LOGGEDIN_STATE
287                                 self.loggedIn.emit()
288                                 if oldUsername != self._username:
289                                         self._load_from_cache()
290                                 loginOps = self._loginOps[:]
291                                 del self._loginOps[:]
292                                 for asyncOp in loginOps:
293                                         asyncOp.start()
294                 except Exception, e:
295                         self.error.emit(str(e))
296                 finally:
297                         self.stateChange.emit(finalState)
298
299         def _load_from_cache(self):
300                 updateContacts = len(self._contacts) != 0
301                 updateMessages = len(self._messages) != 0
302                 updateHistory = len(self._history) != 0
303                 oldDnd = self._dnd
304                 oldCallback = self._callback
305
306                 self._contacts = {}
307                 self._messages = []
308                 self._history = []
309                 self._dnd = False
310                 self._callback = ""
311
312                 if updateContacts:
313                         self.contactsUpdated.emit()
314                 if updateMessages:
315                         self.messagesUpdated.emit()
316                 if updateHistory:
317                         self.historyUpdated.emit()
318                 if oldDnd != self._dnd:
319                         self.dndStateChange.emit(self._dnd)
320                 if oldCallback != self._callback:
321                         self.callbackNumberChanged.emit(self._callback)
322
323         def _save_to_cache(self):
324                 # @todo
325                 pass
326
327         def _clear_cache(self):
328                 updateContacts = len(self._contacts) != 0
329                 updateMessages = len(self._messages) != 0
330                 updateHistory = len(self._history) != 0
331                 oldDnd = self._dnd
332                 oldCallback = self._callback
333
334                 self._contacts = {}
335                 self._messages = []
336                 self._history = []
337                 self._dnd = False
338                 self._callback = ""
339
340                 if updateContacts:
341                         self.contactsUpdated.emit()
342                 if updateMessages:
343                         self.messagesUpdated.emit()
344                 if updateHistory:
345                         self.historyUpdated.emit()
346                 if oldDnd != self._dnd:
347                         self.dndStateChange.emit(self._dnd)
348                 if oldCallback != self._callback:
349                         self.callbackNumberChanged.emit(self._callback)
350
351                 self._save_to_cache()
352
353         def _update_contacts(self):
354                 try:
355                         self._contacts = yield (
356                                 self._backend.get_contacts,
357                                 (),
358                                 {},
359                         )
360                 except Exception, e:
361                         self.error.emit(str(e))
362                         return
363                 self.contactsUpdated.emit()
364
365         def _update_messages(self):
366                 try:
367                         self._messages = yield (
368                                 self._backend.get_messages,
369                                 (),
370                                 {},
371                         )
372                 except Exception, e:
373                         self.error.emit(str(e))
374                         return
375                 self.messagesUpdated.emit()
376
377         def _update_history(self):
378                 try:
379                         self._history = yield (
380                                 self._backend.get_recent,
381                                 (),
382                                 {},
383                         )
384                 except Exception, e:
385                         self.error.emit(str(e))
386                         return
387                 self.historyUpdated.emit()
388
389         def _update_dnd(self):
390                 oldDnd = self._dnd
391                 try:
392                         self._dnd = yield (
393                                 self._backend.is_dnd,
394                                 (),
395                                 {},
396                         )
397                 except Exception, e:
398                         self.error.emit(str(e))
399                         return
400                 if oldDnd != self._dnd:
401                         self.dndStateChange(self._dnd)
402
403         def _perform_op_while_loggedin(self, op):
404                 if self.state == self.LOGGEDIN_STATE:
405                         op.start()
406                 else:
407                         self._push_login_op(op)
408
409         def _push_login_op(self, asyncOp):
410                 assert self.state != self.LOGGEDIN_STATE
411                 if asyncOp in self._loginOps:
412                         _moduleLogger.info("Skipping queueing duplicate op: %r" % asyncOp)
413                         return
414                 self._loginOps.append(asyncOp)