faec62d39f75239eb949b7e7c0a51dbceea06466
[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._username = username
154                 self._password = password
155                 self._asyncPool.start()
156                 self._asyncPool.add_task(
157                         self._backend.login,
158                         (self._username, self._password),
159                         {},
160                         self.__on_login_success(on_success),
161                         on_error
162                 )
163
164         def __on_login_success(self, user_success):
165
166                 @misc_utils.log_exception(_moduleLogger)
167                 def _actual_success(*args, **kwds):
168                         self._masterStateMachine.start()
169                         user_success(*args, **kwds)
170
171                 return _actual_success
172
173         def logout(self):
174                 self._asyncPool.stop()
175                 self._masterStateMachine.stop()
176                 self._backend.logout()
177
178                 self._username = None
179                 self._password = None
180
181         def is_logged_in(self):
182                 if self._username is None and self._password is None:
183                         _moduleLogger.info("Hasn't even attempted to login yet")
184                         return False
185                 elif self._backend.is_authed():
186                         return True
187                 else:
188                         try:
189                                 loggedIn = self._backend.login(self._username, self._password)
190                         except RuntimeError, e:
191                                 _moduleLogger.exception("Re-authenticating and erroring")
192                                 loggedIn = False
193                         if loggedIn:
194                                 return True
195                         else:
196                                 _moduleLogger.info("Login failed")
197                                 self.logout()
198                                 return False
199
200         def set_dnd(self, doNotDisturb):
201                 self._backend.set_dnd(doNotDisturb)
202                 self._cachedIsDnd = doNotDisturb
203
204         def is_dnd(self):
205                 # To throttle checking with the server, use a 30s cache
206                 newTime = time.time()
207                 if self._lastDndCheck + 30 < newTime:
208                         self._lastDndCheck = newTime
209                         self._cachedIsDnd = self._backend.is_dnd()
210                 return self._cachedIsDnd
211
212         @property
213         def backend(self):
214                 return self._backend
215
216         @property
217         def pool(self):
218                 return self._asyncPool
219
220         @property
221         def addressbook(self):
222                 return self._addressbook
223
224         @property
225         def texts(self):
226                 return self._texts
227
228         @property
229         def voicemails(self):
230                 return self._voicemails
231
232         @property
233         def stateMachine(self):
234                 return self._masterStateMachine
235
236         @property
237         def addressbookStateMachine(self):
238                 return self._addressbookStateMachine
239
240         @property
241         def voicemailsStateMachine(self):
242                 return self._voicemailsStateMachine
243
244         @property
245         def textsStateMachine(self):
246                 return self._textsStateMachine