597c7e397825589e5d86392e8a9dc195960fef07
[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
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
40                 self._asyncPool = gobject_utils.AsyncPool()
41                 self._backend = backend.GVoiceBackend(cookiePath)
42
43                 if defaults["contacts"][0] == state_machine.UpdateStateMachine.INFINITE_PERIOD:
44                         contactsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
45                 else:
46                         contactsPeriodInSeconds = state_machine.to_seconds(
47                                 **{defaults["contacts"][1]: defaults["contacts"][0],}
48                         )
49                 self._addressbook = addressbook.Addressbook(self._backend, self._asyncPool)
50                 self._addressbookStateMachine = state_machine.UpdateStateMachine([self.addressbook], "Addressbook")
51                 self._addressbookStateMachine.set_state_strategy(
52                         state_machine.StateMachine.STATE_DND,
53                         state_machine.NopStateStrategy()
54                 )
55                 self._addressbookStateMachine.set_state_strategy(
56                         state_machine.StateMachine.STATE_IDLE,
57                         state_machine.NopStateStrategy()
58                 )
59                 self._addressbookStateMachine.set_state_strategy(
60                         state_machine.StateMachine.STATE_ACTIVE,
61                         state_machine.ConstantStateStrategy(contactsPeriodInSeconds)
62                 )
63
64                 if defaults["voicemail"][0] == state_machine.UpdateStateMachine.INFINITE_PERIOD:
65                         voicemailPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
66                         idleVoicemailPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
67                 else:
68                         voicemailPeriodInSeconds = state_machine.to_seconds(
69                                 **{defaults["voicemail"][1]: defaults["voicemail"][0],}
70                         )
71                         idleVoicemailPeriodInSeconds = max(voicemailPeriodInSeconds * 4, self._MINIMUM_MESSAGE_PERIOD)
72                 self._voicemails = conversations.Conversations(self._backend.get_voicemails, self._asyncPool)
73                 self._voicemailsStateMachine = state_machine.UpdateStateMachine([self.voicemails], "Voicemail")
74                 self._voicemailsStateMachine.set_state_strategy(
75                         state_machine.StateMachine.STATE_DND,
76                         state_machine.NopStateStrategy()
77                 )
78                 self._voicemailsStateMachine.set_state_strategy(
79                         state_machine.StateMachine.STATE_IDLE,
80                         state_machine.ConstantStateStrategy(idleVoicemailPeriodInSeconds)
81                 )
82                 self._voicemailsStateMachine.set_state_strategy(
83                         state_machine.StateMachine.STATE_ACTIVE,
84                         state_machine.NTimesStateStrategy(
85                                 3 * [state_machine.to_seconds(minutes=1)], voicemailPeriodInSeconds
86                         )
87                 )
88                 self._voicemails.updateSignalHandler.register_sink(
89                         self._voicemailsStateMachine.request_reset_timers
90                 )
91
92                 if defaults["texts"][0] == state_machine.UpdateStateMachine.INFINITE_PERIOD:
93                         initTextsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
94                         minTextsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
95                         textsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
96                         idleTextsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
97                 else:
98                         initTextsPeriodInSeconds = state_machine.to_seconds(seconds=20)
99                         minTextsPeriodInSeconds = state_machine.to_seconds(seconds=1)
100                         textsPeriodInSeconds = state_machine.to_seconds(
101                                 **{defaults["texts"][1]: defaults["texts"][0],}
102                         )
103                         idleTextsPeriodInSeconds = max(textsPeriodInSeconds * 4, self._MINIMUM_MESSAGE_PERIOD)
104                 self._texts = conversations.Conversations(self._backend.get_texts, self._asyncPool)
105                 self._textsStateMachine = state_machine.UpdateStateMachine([self.texts], "Texting")
106                 self._textsStateMachine.set_state_strategy(
107                         state_machine.StateMachine.STATE_DND,
108                         state_machine.NopStateStrategy()
109                 )
110                 self._textsStateMachine.set_state_strategy(
111                         state_machine.StateMachine.STATE_IDLE,
112                         state_machine.ConstantStateStrategy(idleTextsPeriodInSeconds)
113                 )
114                 self._textsStateMachine.set_state_strategy(
115                         state_machine.StateMachine.STATE_ACTIVE,
116                         state_machine.GeometricStateStrategy(
117                                 initTextsPeriodInSeconds,
118                                 minTextsPeriodInSeconds,
119                                 textsPeriodInSeconds,
120                         )
121                 )
122                 self._texts.updateSignalHandler.register_sink(
123                         self._textsStateMachine.request_reset_timers
124                 )
125
126                 self._masterStateMachine = state_machine.MasterStateMachine()
127                 self._masterStateMachine.append_machine(self._addressbookStateMachine)
128                 self._masterStateMachine.append_machine(self._voicemailsStateMachine)
129                 self._masterStateMachine.append_machine(self._textsStateMachine)
130
131                 self._lastDndCheck = 0
132                 self._cachedIsDnd = False
133
134         def load(self, path):
135                 self._texts.load(os.sep.join((path, "texts.cache")))
136                 self._voicemails.load(os.sep.join((path, "voicemails.cache")))
137
138         def save(self, path):
139                 self._texts.save(os.sep.join((path, "texts.cache")))
140                 self._voicemails.save(os.sep.join((path, "voicemails.cache")))
141
142         def close(self):
143                 self._voicemails.updateSignalHandler.unregister_sink(
144                         self._voicemailsStateMachine.request_reset_timers
145                 )
146                 self._texts.updateSignalHandler.unregister_sink(
147                         self._textsStateMachine.request_reset_timers
148                 )
149                 self._masterStateMachine.close()
150
151         def login(self, username, password, on_success, on_error):
152                 self._username = username
153                 self._password = password
154                 self._asyncPool.start()
155                 self._asyncPool.add_task(
156                         self._backend.login,
157                         (self._username, self._password),
158                         {},
159                         self.__on_login_success(on_success),
160                         on_error
161                 )
162
163         def __on_login_success(self, user_success):
164
165                 def _actual_success(self, *args, **kwds):
166                         self._masterStateMachine.start()
167                         user_success(*args, **kwds)
168
169                 return _actual_success
170
171         def logout(self):
172                 self._asyncPool.stop()
173                 self._masterStateMachine.stop()
174                 self._backend.logout()
175
176                 self._username = None
177                 self._password = None
178
179         def is_logged_in(self):
180                 if self._username is None and self._password is None:
181                         _moduleLogger.info("Hasn't even attempted to login yet")
182                         return False
183                 elif self._backend.is_authed():
184                         return True
185                 else:
186                         try:
187                                 loggedIn = self._backend.login(self._username, self._password)
188                         except RuntimeError, e:
189                                 _moduleLogger.exception("Re-authenticating and erroring")
190                                 loggedIn = False
191                         if loggedIn:
192                                 return True
193                         else:
194                                 _moduleLogger.info("Login failed")
195                                 self.logout()
196                                 return False
197
198         def set_dnd(self, doNotDisturb):
199                 self._backend.set_dnd(doNotDisturb)
200                 self._cachedIsDnd = doNotDisturb
201
202         def is_dnd(self):
203                 # To throttle checking with the server, use a 30s cache
204                 newTime = time.time()
205                 if self._lastDndCheck + 30 < newTime:
206                         self._lastDndCheck = newTime
207                         self._cachedIsDnd = self._backend.is_dnd()
208                 return self._cachedIsDnd
209
210         @property
211         def backend(self):
212                 return self._backend
213
214         @property
215         def pool(self):
216                 return self._asyncPool
217
218         @property
219         def addressbook(self):
220                 return self._addressbook
221
222         @property
223         def texts(self):
224                 return self._texts
225
226         @property
227         def voicemails(self):
228                 return self._voicemails
229
230         @property
231         def stateMachine(self):
232                 return self._masterStateMachine
233
234         @property
235         def addressbookStateMachine(self):
236                 return self._addressbookStateMachine
237
238         @property
239         def voicemailsStateMachine(self):
240                 return self._voicemailsStateMachine
241
242         @property
243         def textsStateMachine(self):
244                 return self._textsStateMachine