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