Speeding up login through cookies
[theonering] / src / gvoice / session.py
1 #!/usr/bin/env python
2
3 import os
4 import time
5 import logging
6
7 import backend
8 import addressbook
9 import conversations
10 import state_machine
11
12 import util.go_utils as gobject_utils
13 import util.misc as misc_utils
14
15
16 _moduleLogger = logging.getLogger(__name__)
17
18
19 class Session(object):
20
21         _DEFAULTS = {
22                 "contacts": (12, "hours"),
23                 "voicemail": (120, "minutes"),
24                 "texts": (10, "minutes"),
25         }
26
27         _MINIMUM_MESSAGE_PERIOD = state_machine.to_seconds(minutes=30)
28
29         def __init__(self, cookiePath = None, defaults = None):
30                 if defaults is None:
31                         defaults = self._DEFAULTS
32                 else:
33                         for key, (quant, unit) in defaults.iteritems():
34                                 if quant == 0:
35                                         defaults[key] = (self._DEFAULTS[key], unit)
36                                 elif quant < 0:
37                                         defaults[key] = (state_machine.UpdateStateMachine.INFINITE_PERIOD, unit)
38                 self._username = None
39                 self._password = None
40
41                 self._asyncPool = gobject_utils.AsyncPool()
42                 self._backend = backend.GVoiceBackend(cookiePath)
43
44                 if defaults["contacts"][0] == state_machine.UpdateStateMachine.INFINITE_PERIOD:
45                         contactsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
46                 else:
47                         contactsPeriodInSeconds = state_machine.to_seconds(
48                                 **{defaults["contacts"][1]: defaults["contacts"][0],}
49                         )
50                 self._addressbook = addressbook.Addressbook(self._backend, self._asyncPool)
51                 self._addressbookStateMachine = state_machine.UpdateStateMachine([self.addressbook], "Addressbook")
52                 self._addressbookStateMachine.set_state_strategy(
53                         state_machine.StateMachine.STATE_DND,
54                         state_machine.NopStateStrategy()
55                 )
56                 self._addressbookStateMachine.set_state_strategy(
57                         state_machine.StateMachine.STATE_IDLE,
58                         state_machine.NopStateStrategy()
59                 )
60                 self._addressbookStateMachine.set_state_strategy(
61                         state_machine.StateMachine.STATE_ACTIVE,
62                         state_machine.ConstantStateStrategy(contactsPeriodInSeconds)
63                 )
64
65                 if defaults["voicemail"][0] == state_machine.UpdateStateMachine.INFINITE_PERIOD:
66                         voicemailPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
67                         idleVoicemailPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
68                 else:
69                         voicemailPeriodInSeconds = state_machine.to_seconds(
70                                 **{defaults["voicemail"][1]: defaults["voicemail"][0],}
71                         )
72                         idleVoicemailPeriodInSeconds = max(voicemailPeriodInSeconds * 4, self._MINIMUM_MESSAGE_PERIOD)
73                 self._voicemails = conversations.Conversations(self._backend.get_voicemails, self._asyncPool)
74                 self._voicemailsStateMachine = state_machine.UpdateStateMachine([self.voicemails], "Voicemail")
75                 self._voicemailsStateMachine.set_state_strategy(
76                         state_machine.StateMachine.STATE_DND,
77                         state_machine.NopStateStrategy()
78                 )
79                 self._voicemailsStateMachine.set_state_strategy(
80                         state_machine.StateMachine.STATE_IDLE,
81                         state_machine.ConstantStateStrategy(idleVoicemailPeriodInSeconds)
82                 )
83                 self._voicemailsStateMachine.set_state_strategy(
84                         state_machine.StateMachine.STATE_ACTIVE,
85                         state_machine.NTimesStateStrategy(
86                                 3 * [state_machine.to_seconds(minutes=1)], voicemailPeriodInSeconds
87                         )
88                 )
89                 self._voicemails.updateSignalHandler.register_sink(
90                         self._voicemailsStateMachine.request_reset_timers
91                 )
92
93                 if defaults["texts"][0] == state_machine.UpdateStateMachine.INFINITE_PERIOD:
94                         initTextsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
95                         minTextsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
96                         textsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
97                         idleTextsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
98                 else:
99                         initTextsPeriodInSeconds = state_machine.to_seconds(seconds=20)
100                         minTextsPeriodInSeconds = state_machine.to_seconds(seconds=1)
101                         textsPeriodInSeconds = state_machine.to_seconds(
102                                 **{defaults["texts"][1]: defaults["texts"][0],}
103                         )
104                         idleTextsPeriodInSeconds = max(textsPeriodInSeconds * 4, self._MINIMUM_MESSAGE_PERIOD)
105                 self._texts = conversations.Conversations(self._backend.get_texts, self._asyncPool)
106                 self._textsStateMachine = state_machine.UpdateStateMachine([self.texts], "Texting")
107                 self._textsStateMachine.set_state_strategy(
108                         state_machine.StateMachine.STATE_DND,
109                         state_machine.NopStateStrategy()
110                 )
111                 self._textsStateMachine.set_state_strategy(
112                         state_machine.StateMachine.STATE_IDLE,
113                         state_machine.ConstantStateStrategy(idleTextsPeriodInSeconds)
114                 )
115                 self._textsStateMachine.set_state_strategy(
116                         state_machine.StateMachine.STATE_ACTIVE,
117                         state_machine.GeometricStateStrategy(
118                                 initTextsPeriodInSeconds,
119                                 minTextsPeriodInSeconds,
120                                 textsPeriodInSeconds,
121                         )
122                 )
123                 self._texts.updateSignalHandler.register_sink(
124                         self._textsStateMachine.request_reset_timers
125                 )
126
127                 self._masterStateMachine = state_machine.MasterStateMachine()
128                 self._masterStateMachine.append_machine(self._addressbookStateMachine)
129                 self._masterStateMachine.append_machine(self._voicemailsStateMachine)
130                 self._masterStateMachine.append_machine(self._textsStateMachine)
131
132                 self._lastDndCheck = 0
133                 self._cachedIsDnd = False
134
135         def load(self, path):
136                 self._texts.load(os.sep.join((path, "texts.cache")))
137                 self._voicemails.load(os.sep.join((path, "voicemails.cache")))
138
139         def save(self, path):
140                 self._texts.save(os.sep.join((path, "texts.cache")))
141                 self._voicemails.save(os.sep.join((path, "voicemails.cache")))
142
143         def close(self):
144                 self._voicemails.updateSignalHandler.unregister_sink(
145                         self._voicemailsStateMachine.request_reset_timers
146                 )
147                 self._texts.updateSignalHandler.unregister_sink(
148                         self._textsStateMachine.request_reset_timers
149                 )
150                 self._masterStateMachine.close()
151
152         def login(self, username, password, on_success, on_error):
153                 self._asyncPool.start()
154
155                 le = gobject_utils.AsyncLinearExecution(self._asyncPool, self._login)
156                 le.start(username, password, on_success, on_error)
157
158         @misc_utils.log_exception(_moduleLogger)
159         def _login(self, username, password, on_success, on_error):
160                 self._username = username
161                 self._password = password
162
163                 isLoggedIn = False
164
165                 if not isLoggedIn and self._backend.is_quick_login_possible():
166                         try:
167                                 isLoggedIn = yield (
168                                         self._backend.is_authed,
169                                         (),
170                                         {},
171                                 )
172                         except Exception, e:
173                                 on_error(e)
174                                 return
175                         if isLoggedIn:
176                                 _moduleLogger.info("Logged in through cookies")
177
178                 if not isLoggedIn:
179                         try:
180                                 isLoggedIn = yield (
181                                         self._backend.login,
182                                         (self._username, self._password),
183                                         {},
184                                 )
185                         except Exception, e:
186                                 on_error(e)
187                                 return
188                         if isLoggedIn:
189                                 _moduleLogger.info("Logged in through credentials")
190
191                 self._masterStateMachine.start()
192                 on_success(isLoggedIn)
193
194         def shutdown(self):
195                 self._asyncPool.stop()
196                 self._masterStateMachine.stop()
197                 self._backend.shutdown()
198
199                 self._username = None
200                 self._password = None
201
202         def logout(self):
203                 self._asyncPool.stop()
204                 self._masterStateMachine.stop()
205                 self._backend.logout()
206
207                 self._username = None
208                 self._password = None
209
210         def is_logged_in(self):
211                 if self._username is None and self._password is None:
212                         _moduleLogger.info("Hasn't even attempted to login yet")
213                         return False
214                 else:
215                         isLoggedIn = self._backend.is_authed()
216                         if not isLoggedIn:
217                                 _moduleLogger.error("Not logged in anymore")
218                         return isLoggedIn
219
220         def set_dnd(self, doNotDisturb):
221                 self._backend.set_dnd(doNotDisturb)
222                 self._cachedIsDnd = doNotDisturb
223
224         def is_dnd(self):
225                 # To throttle checking with the server, use a 30s cache
226                 newTime = time.time()
227                 if self._lastDndCheck + 30 < newTime:
228                         self._lastDndCheck = newTime
229                         self._cachedIsDnd = self._backend.is_dnd()
230                 return self._cachedIsDnd
231
232         @property
233         def backend(self):
234                 assert self.is_logged_in()
235                 return self._backend
236
237         @property
238         def pool(self):
239                 return self._asyncPool
240
241         @property
242         def addressbook(self):
243                 return self._addressbook
244
245         @property
246         def texts(self):
247                 return self._texts
248
249         @property
250         def voicemails(self):
251                 return self._voicemails
252
253         @property
254         def stateMachine(self):
255                 return self._masterStateMachine
256
257         @property
258         def addressbookStateMachine(self):
259                 return self._addressbookStateMachine
260
261         @property
262         def voicemailsStateMachine(self):
263                 return self._voicemailsStateMachine
264
265         @property
266         def textsStateMachine(self):
267                 return self._textsStateMachine