45f80b95689ef3fdb239f01ebae8c230c26c660a
[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("gvoice.session")
14
15
16 class Session(object):
17
18         _DEFAULTS = {
19                 "contacts": (3, "hours"),
20                 "voicemail": (30, "minutes"),
21                 "texts": (5, "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                 else:
64                         voicemailPeriodInSeconds = state_machine.to_seconds(
65                                 **{defaults["voicemail"][1]: defaults["voicemail"][0],}
66                         )
67                 self._voicemails = conversations.Conversations(self._backend.get_voicemails)
68                 self._voicemailsStateMachine = state_machine.UpdateStateMachine([self.voicemails], "Voicemail")
69                 self._voicemailsStateMachine.set_state_strategy(
70                         state_machine.StateMachine.STATE_DND,
71                         state_machine.NopStateStrategy()
72                 )
73                 self._voicemailsStateMachine.set_state_strategy(
74                         state_machine.StateMachine.STATE_IDLE,
75                         state_machine.ConstantStateStrategy(
76                                 max(voicemailPeriodInSeconds * 4, self._MINIMUM_MESSAGE_PERIOD)
77                         )
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                         textsPeriodInSeconds = state_machine.UpdateStateMachine.INFINITE_PERIOD
91                 else:
92                         textsPeriodInSeconds = state_machine.to_seconds(
93                                 **{defaults["texts"][1]: defaults["texts"][0],}
94                         )
95                 self._texts = conversations.Conversations(self._backend.get_texts)
96                 self._textsStateMachine = state_machine.UpdateStateMachine([self.texts], "Texting")
97                 self._textsStateMachine.set_state_strategy(
98                         state_machine.StateMachine.STATE_DND,
99                         state_machine.NopStateStrategy()
100                 )
101                 self._textsStateMachine.set_state_strategy(
102                         state_machine.StateMachine.STATE_IDLE,
103                         state_machine.ConstantStateStrategy(
104                                 max(textsPeriodInSeconds * 4, self._MINIMUM_MESSAGE_PERIOD)
105                         )
106                 )
107                 self._textsStateMachine.set_state_strategy(
108                         state_machine.StateMachine.STATE_ACTIVE,
109                         state_machine.GeometricStateStrategy(
110                                 state_machine.to_seconds(seconds=20),
111                                 state_machine.to_seconds(seconds=1),
112                                 textsPeriodInSeconds,
113                         )
114                 )
115                 self._texts.updateSignalHandler.register_sink(
116                         self._textsStateMachine.request_reset_timers
117                 )
118
119                 self._masterStateMachine = state_machine.MasterStateMachine()
120                 self._masterStateMachine.append_machine(self._addressbookStateMachine)
121                 self._masterStateMachine.append_machine(self._voicemailsStateMachine)
122                 self._masterStateMachine.append_machine(self._textsStateMachine)
123
124                 self._lastDndCheck = 0
125                 self._cachedIsDnd = False
126
127         def load(self, path):
128                 self._texts.load(os.sep.join((path, "texts.cache")))
129                 self._voicemails.load(os.sep.join((path, "voicemails.cache")))
130
131         def save(self, path):
132                 self._texts.save(os.sep.join((path, "texts.cache")))
133                 self._voicemails.save(os.sep.join((path, "voicemails.cache")))
134
135         def close(self):
136                 self._voicemails.updateSignalHandler.unregister_sink(
137                         self._voicemailsStateMachine.request_reset_timers
138                 )
139                 self._texts.updateSignalHandler.unregister_sink(
140                         self._textsStateMachine.request_reset_timers
141                 )
142                 self._masterStateMachine.close()
143
144         def login(self, username, password):
145                 self._username = username
146                 self._password = password
147                 self._backend.login(self._username, self._password)
148
149                 self._masterStateMachine.start()
150
151         def logout(self):
152                 self._masterStateMachine.stop()
153                 self._backend.logout()
154
155                 self._username = None
156                 self._password = None
157
158         def is_logged_in(self):
159                 if self._username is None and self._password is None:
160                         _moduleLogger.info("Hasn't even attempted to login yet")
161                         return False
162                 elif self._backend.is_authed():
163                         return True
164                 else:
165                         try:
166                                 loggedIn = self._backend.login(self._username, self._password)
167                         except RuntimeError, e:
168                                 _moduleLogger.exception("Re-authenticating and erroring")
169                                 loggedIn = False
170                         if loggedIn:
171                                 return True
172                         else:
173                                 _moduleLogger.info("Login failed")
174                                 self.logout()
175                                 return False
176
177         def set_dnd(self, doNotDisturb):
178                 self._backend.set_dnd(doNotDisturb)
179                 self._cachedIsDnd = doNotDisturb
180
181         def is_dnd(self):
182                 # To throttle checking with the server, use a 30s cache
183                 newTime = time.time()
184                 if self._lastDndCheck + 30 < newTime:
185                         self._lasDndCheck = newTime
186                         self._cachedIsDnd = self._backend.is_dnd()
187                 return self._cachedIsDnd
188
189         @property
190         def backend(self):
191                 """
192                 Login enforcing backend
193                 """
194                 assert self.is_logged_in(), "User not logged in"
195                 return self._backend
196
197         @property
198         def addressbook(self):
199                 return self._addressbook
200
201         @property
202         def texts(self):
203                 return self._texts
204
205         @property
206         def voicemails(self):
207                 return self._voicemails
208
209         @property
210         def stateMachine(self):
211                 return self._masterStateMachine
212
213         @property
214         def addressbookStateMachine(self):
215                 return self._addressbookStateMachine
216
217         @property
218         def voicemailsStateMachine(self):
219                 return self._voicemailsStateMachine
220
221         @property
222         def textsStateMachine(self):
223                 return self._textsStateMachine