Adding fancier version checks to allow to the cache to be preserved longer
[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
13 _moduleLogger = logging.getLogger(__name__)
14
15
16 class Session(object):
17
18         _DEFAULTS = {
19                 "contacts": (12, "hours"),
20                 "voicemail": (120, "minutes"),
21                 "texts": (10, "minutes"),
22         }
23
24         _MINIMUM_MESSAGE_PERIOD = state_machine.to_seconds(minutes=30)
25
26         def __init__(self, cookiePath = None, defaults = None):
27                 if defaults is None:
28                         defaults = self._DEFAULTS
29                 else:
30                         for key, (quant, unit) in defaults.iteritems():
31                                 if quant == 0:
32                                         defaults[key] = (self._DEFAULTS[key], unit)
33                                 elif quant < 0:
34                                         defaults[key] = (state_machine.UpdateStateMachine.INFINITE_PERIOD, unit)
35                 self._username = None
36                 self._password = None
37
38                 self._backend = backend.GVoiceBackend(cookiePath)
39
40                 if defaults["contacts"][0] == state_machine.UpdateStateMachine.INFINITE_PERIOD:
41                         contactsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
42                 else:
43                         contactsPeriodInSeconds = state_machine.to_seconds(
44                                 **{defaults["contacts"][1]: defaults["contacts"][0],}
45                         )
46                 self._addressbook = addressbook.Addressbook(self._backend)
47                 self._addressbookStateMachine = state_machine.UpdateStateMachine([self.addressbook], "Addressbook")
48                 self._addressbookStateMachine.set_state_strategy(
49                         state_machine.StateMachine.STATE_DND,
50                         state_machine.NopStateStrategy()
51                 )
52                 self._addressbookStateMachine.set_state_strategy(
53                         state_machine.StateMachine.STATE_IDLE,
54                         state_machine.NopStateStrategy()
55                 )
56                 self._addressbookStateMachine.set_state_strategy(
57                         state_machine.StateMachine.STATE_ACTIVE,
58                         state_machine.ConstantStateStrategy(contactsPeriodInSeconds)
59                 )
60
61                 if defaults["voicemail"][0] == state_machine.UpdateStateMachine.INFINITE_PERIOD:
62                         voicemailPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
63                         idleVoicemailPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
64                 else:
65                         voicemailPeriodInSeconds = state_machine.to_seconds(
66                                 **{defaults["voicemail"][1]: defaults["voicemail"][0],}
67                         )
68                         idleVoicemailPeriodInSeconds = max(voicemailPeriodInSeconds * 4, self._MINIMUM_MESSAGE_PERIOD)
69                 self._voicemails = conversations.Conversations(self._backend.get_voicemails)
70                 self._voicemailsStateMachine = state_machine.UpdateStateMachine([self.voicemails], "Voicemail")
71                 self._voicemailsStateMachine.set_state_strategy(
72                         state_machine.StateMachine.STATE_DND,
73                         state_machine.NopStateStrategy()
74                 )
75                 self._voicemailsStateMachine.set_state_strategy(
76                         state_machine.StateMachine.STATE_IDLE,
77                         state_machine.ConstantStateStrategy(idleVoicemailPeriodInSeconds)
78                 )
79                 self._voicemailsStateMachine.set_state_strategy(
80                         state_machine.StateMachine.STATE_ACTIVE,
81                         state_machine.NTimesStateStrategy(
82                                 3 * [state_machine.to_seconds(minutes=1)], voicemailPeriodInSeconds
83                         )
84                 )
85                 self._voicemails.updateSignalHandler.register_sink(
86                         self._voicemailsStateMachine.request_reset_timers
87                 )
88
89                 if defaults["texts"][0] == state_machine.UpdateStateMachine.INFINITE_PERIOD:
90                         initTextsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
91                         minTextsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
92                         textsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
93                         idleTextsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
94                 else:
95                         initTextsPeriodInSeconds = state_machine.to_seconds(seconds=20)
96                         minTextsPeriodInSeconds = state_machine.to_seconds(seconds=1)
97                         textsPeriodInSeconds = state_machine.to_seconds(
98                                 **{defaults["texts"][1]: defaults["texts"][0],}
99                         )
100                         idleTextsPeriodInSeconds = max(textsPeriodInSeconds * 4, self._MINIMUM_MESSAGE_PERIOD)
101                 self._texts = conversations.Conversations(self._backend.get_texts)
102                 self._textsStateMachine = state_machine.UpdateStateMachine([self.texts], "Texting")
103                 self._textsStateMachine.set_state_strategy(
104                         state_machine.StateMachine.STATE_DND,
105                         state_machine.NopStateStrategy()
106                 )
107                 self._textsStateMachine.set_state_strategy(
108                         state_machine.StateMachine.STATE_IDLE,
109                         state_machine.ConstantStateStrategy(idleTextsPeriodInSeconds)
110                 )
111                 self._textsStateMachine.set_state_strategy(
112                         state_machine.StateMachine.STATE_ACTIVE,
113                         state_machine.GeometricStateStrategy(
114                                 initTextsPeriodInSeconds,
115                                 minTextsPeriodInSeconds,
116                                 textsPeriodInSeconds,
117                         )
118                 )
119                 self._texts.updateSignalHandler.register_sink(
120                         self._textsStateMachine.request_reset_timers
121                 )
122
123                 self._masterStateMachine = state_machine.MasterStateMachine()
124                 self._masterStateMachine.append_machine(self._addressbookStateMachine)
125                 self._masterStateMachine.append_machine(self._voicemailsStateMachine)
126                 self._masterStateMachine.append_machine(self._textsStateMachine)
127
128                 self._lastDndCheck = 0
129                 self._cachedIsDnd = False
130
131         def load(self, path):
132                 self._texts.load(os.sep.join((path, "texts.cache")))
133                 self._voicemails.load(os.sep.join((path, "voicemails.cache")))
134
135         def save(self, path):
136                 self._texts.save(os.sep.join((path, "texts.cache")))
137                 self._voicemails.save(os.sep.join((path, "voicemails.cache")))
138
139         def close(self):
140                 self._voicemails.updateSignalHandler.unregister_sink(
141                         self._voicemailsStateMachine.request_reset_timers
142                 )
143                 self._texts.updateSignalHandler.unregister_sink(
144                         self._textsStateMachine.request_reset_timers
145                 )
146                 self._masterStateMachine.close()
147
148         def login(self, username, password):
149                 self._username = username
150                 self._password = password
151                 self._backend.login(self._username, self._password)
152
153                 self._masterStateMachine.start()
154
155         def logout(self):
156                 self._masterStateMachine.stop()
157                 self._backend.logout()
158
159                 self._username = None
160                 self._password = None
161
162         def is_logged_in(self):
163                 if self._username is None and self._password is None:
164                         _moduleLogger.info("Hasn't even attempted to login yet")
165                         return False
166                 elif self._backend.is_authed():
167                         return True
168                 else:
169                         try:
170                                 loggedIn = self._backend.login(self._username, self._password)
171                         except RuntimeError, e:
172                                 _moduleLogger.exception("Re-authenticating and erroring")
173                                 loggedIn = False
174                         if loggedIn:
175                                 return True
176                         else:
177                                 _moduleLogger.info("Login failed")
178                                 self.logout()
179                                 return False
180
181         def set_dnd(self, doNotDisturb):
182                 self._backend.set_dnd(doNotDisturb)
183                 self._cachedIsDnd = doNotDisturb
184
185         def is_dnd(self):
186                 # To throttle checking with the server, use a 30s cache
187                 newTime = time.time()
188                 if self._lastDndCheck + 30 < newTime:
189                         self._lastDndCheck = newTime
190                         self._cachedIsDnd = self._backend.is_dnd()
191                 return self._cachedIsDnd
192
193         @property
194         def backend(self):
195                 """
196                 Login enforcing backend
197                 """
198                 assert self.is_logged_in(), "User not logged in"
199                 return self._backend
200
201         @property
202         def addressbook(self):
203                 return self._addressbook
204
205         @property
206         def texts(self):
207                 return self._texts
208
209         @property
210         def voicemails(self):
211                 return self._voicemails
212
213         @property
214         def stateMachine(self):
215                 return self._masterStateMachine
216
217         @property
218         def addressbookStateMachine(self):
219                 return self._addressbookStateMachine
220
221         @property
222         def voicemailsStateMachine(self):
223                 return self._voicemailsStateMachine
224
225         @property
226         def textsStateMachine(self):
227                 return self._textsStateMachine