Imitiating buttfly in being explicitly typed
[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                 try:
163                         isLoggedIn = yield (
164                                 self._backend.login,
165                                 (self._username, self._password),
166                                 {},
167                         )
168                 except Exception, e:
169                         on_error(e)
170                         return
171
172                 self._masterStateMachine.start()
173                 on_success(isLoggedIn)
174
175         def logout(self):
176                 self._asyncPool.stop()
177                 self._masterStateMachine.stop()
178                 self._backend.logout()
179
180                 self._username = None
181                 self._password = None
182
183         def is_logged_in(self):
184                 if self._username is None and self._password is None:
185                         _moduleLogger.info("Hasn't even attempted to login yet")
186                         return False
187                 else:
188                         isLoggedIn = self._backend.is_authed()
189                         if not isLoggedIn:
190                                 _moduleLogger.error("Not logged in anymore")
191                         return isLoggedIn
192
193         def set_dnd(self, doNotDisturb):
194                 self._backend.set_dnd(doNotDisturb)
195                 self._cachedIsDnd = doNotDisturb
196
197         def is_dnd(self):
198                 # To throttle checking with the server, use a 30s cache
199                 newTime = time.time()
200                 if self._lastDndCheck + 30 < newTime:
201                         self._lastDndCheck = newTime
202                         self._cachedIsDnd = self._backend.is_dnd()
203                 return self._cachedIsDnd
204
205         @property
206         def backend(self):
207                 assert self.is_logged_in()
208                 return self._backend
209
210         @property
211         def pool(self):
212                 return self._asyncPool
213
214         @property
215         def addressbook(self):
216                 return self._addressbook
217
218         @property
219         def texts(self):
220                 return self._texts
221
222         @property
223         def voicemails(self):
224                 return self._voicemails
225
226         @property
227         def stateMachine(self):
228                 return self._masterStateMachine
229
230         @property
231         def addressbookStateMachine(self):
232                 return self._addressbookStateMachine
233
234         @property
235         def voicemailsStateMachine(self):
236                 return self._voicemailsStateMachine
237
238         @property
239         def textsStateMachine(self):
240                 return self._textsStateMachine