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