Not using i_logged_in for all of its fanciness, so shrink it down
[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                 else:
186                         isLoggedIn = self._backend.is_authed()
187                         if not isLoggedIn:
188                                 _moduleLogger.error("Not logged in anymore")
189
190         def set_dnd(self, doNotDisturb):
191                 self._backend.set_dnd(doNotDisturb)
192                 self._cachedIsDnd = doNotDisturb
193
194         def is_dnd(self):
195                 # To throttle checking with the server, use a 30s cache
196                 newTime = time.time()
197                 if self._lastDndCheck + 30 < newTime:
198                         self._lastDndCheck = newTime
199                         self._cachedIsDnd = self._backend.is_dnd()
200                 return self._cachedIsDnd
201
202         @property
203         def backend(self):
204                 assert self.is_logged_in()
205                 return self._backend
206
207         @property
208         def pool(self):
209                 return self._asyncPool
210
211         @property
212         def addressbook(self):
213                 return self._addressbook
214
215         @property
216         def texts(self):
217                 return self._texts
218
219         @property
220         def voicemails(self):
221                 return self._voicemails
222
223         @property
224         def stateMachine(self):
225                 return self._masterStateMachine
226
227         @property
228         def addressbookStateMachine(self):
229                 return self._addressbookStateMachine
230
231         @property
232         def voicemailsStateMachine(self):
233                 return self._voicemailsStateMachine
234
235         @property
236         def textsStateMachine(self):
237                 return self._textsStateMachine