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