*sigh* what I previously thought was a bug reducer turned out to create bugs
[theonering] / src / connection.py
1 import os
2 import weakref
3 import logging
4
5 import telepathy
6
7 import constants
8 import tp
9 import util.misc as util_misc
10 import gtk_toolbox
11
12 import gvoice
13 import handle
14
15 import requests
16 import contacts
17 import aliasing
18 import simple_presence
19 import presence
20 import capabilities
21
22 import autogv
23 import channel_manager
24
25
26 _moduleLogger = logging.getLogger("connection")
27
28
29 class TheOneRingOptions(object):
30
31         useGVContacts = True
32
33         assert gvoice.session.Session._DEFAULTS["contacts"][1] == "hours"
34         contactsPollPeriodInHours = gvoice.session.Session._DEFAULTS["contacts"][0]
35
36         assert gvoice.session.Session._DEFAULTS["voicemail"][1] == "minutes"
37         voicemailPollPeriodInMinutes = gvoice.session.Session._DEFAULTS["voicemail"][0]
38
39         assert gvoice.session.Session._DEFAULTS["texts"][1] == "minutes"
40         textsPollPeriodInMinutes = gvoice.session.Session._DEFAULTS["texts"][0]
41
42         def __init__(self, parameters = None):
43                 if parameters is None:
44                         return
45                 self.useGVContacts = parameters["use-gv-contacts"]
46                 self.contactsPollPeriodInHours = parameters['contacts-poll-period-in-hours']
47                 self.voicemailPollPeriodInMinutes = parameters['voicemail-poll-period-in-minutes']
48                 self.textsPollPeriodInMinutes = parameters['texts-poll-period-in-minutes']
49
50
51 class TheOneRingConnection(
52         tp.Connection,
53         requests.RequestsMixin,
54         contacts.ContactsMixin,
55         aliasing.AliasingMixin,
56         simple_presence.SimplePresenceMixin,
57         presence.PresenceMixin,
58         capabilities.CapabilitiesMixin,
59 ):
60
61         # overiding base class variable
62         _mandatory_parameters = {
63                 'account': 's',
64                 'password': 's',
65         }
66         # overiding base class variable
67         _optional_parameters = {
68                 'forward': 's',
69                 'use-gv-contacts': 'b',
70                 'contacts-poll-period-in-hours': 'i',
71                 'voicemail-poll-period-in-minutes': 'i',
72                 'texts-poll-period-in-minutes': 'i',
73         }
74         _parameter_defaults = {
75                 'forward': '',
76                 'use-gv-contacts': TheOneRingOptions.useGVContacts,
77                 'contacts-poll-period-in-hours': TheOneRingOptions.contactsPollPeriodInHours,
78                 'voicemail-poll-period-in-minutes': TheOneRingOptions.voicemailPollPeriodInMinutes,
79                 'texts-poll-period-in-minutes': TheOneRingOptions.textsPollPeriodInMinutes,
80         }
81         _secret_parameters = set((
82                 "password",
83         ))
84
85         @gtk_toolbox.log_exception(_moduleLogger)
86         def __init__(self, manager, parameters):
87                 self.check_parameters(parameters)
88                 account = unicode(parameters['account'])
89                 encodedAccount = parameters['account'].encode('utf-8')
90                 encodedPassword = parameters['password'].encode('utf-8')
91                 encodedCallback = util_misc.normalize_number(parameters['forward'].encode('utf-8'))
92                 if encodedCallback and not util_misc.is_valid_number(encodedCallback):
93                         raise telepathy.errors.InvalidArgument("Invalid forwarding number")
94
95                 # Connection init must come first
96                 self.__options = TheOneRingOptions(parameters)
97                 self.__session = gvoice.session.Session(
98                         cookiePath = None,
99                         defaults = {
100                                 "contacts": (self.__options.contactsPollPeriodInHours, "hours"),
101                                 "voicemail": (self.__options.voicemailPollPeriodInMinutes, "minutes"),
102                                 "texts": (self.__options.textsPollPeriodInMinutes, "minutes"),
103                         },
104                 )
105                 tp.Connection.__init__(
106                         self,
107                         constants._telepathy_protocol_name_,
108                         account,
109                         constants._telepathy_implementation_name_
110                 )
111                 requests.RequestsMixin.__init__(self)
112                 contacts.ContactsMixin.__init__(self)
113                 aliasing.AliasingMixin.__init__(self)
114                 simple_presence.SimplePresenceMixin.__init__(self)
115                 presence.PresenceMixin.__init__(self)
116                 capabilities.CapabilitiesMixin.__init__(self)
117
118                 self.__manager = weakref.proxy(manager)
119                 self.__credentials = (
120                         encodedAccount,
121                         encodedPassword,
122                 )
123                 self.__callbackNumberParameter = encodedCallback
124                 self.__channelManager = channel_manager.ChannelManager(self)
125
126                 self.__cachePath = os.sep.join((constants._data_path_, "cache", self.username))
127                 try:
128                         os.makedirs(self.__cachePath)
129                 except OSError, e:
130                         if e.errno != 17:
131                                 raise
132
133                 self.set_self_handle(handle.create_handle(self, 'connection'))
134                 self._plumbing = [
135                         autogv.NewGVConversations(weakref.ref(self)),
136                         autogv.RefreshVoicemail(weakref.ref(self)),
137                         autogv.AutoDisconnect(weakref.ref(self)),
138                 ]
139
140                 _moduleLogger.info("Connection to the account %s created" % account)
141
142         @property
143         def manager(self):
144                 return self.__manager
145
146         @property
147         def session(self):
148                 return self.__session
149
150         @property
151         def options(self):
152                 return self.__options
153
154         @property
155         def username(self):
156                 return self.__credentials[0]
157
158         @property
159         def callbackNumberParameter(self):
160                 return self.__callbackNumberParameter
161
162         def get_handle_by_name(self, handleType, handleName):
163                 requestedHandleName = handleName.encode('utf-8')
164                 if handleType == telepathy.HANDLE_TYPE_CONTACT:
165                         _moduleLogger.debug("get_handle_by_name Contact: %s" % requestedHandleName)
166                         h = handle.create_handle(self, 'contact', requestedHandleName)
167                 elif handleType == telepathy.HANDLE_TYPE_LIST:
168                         # Support only server side (immutable) lists
169                         _moduleLogger.debug("get_handle_by_name List: %s" % requestedHandleName)
170                         h = handle.create_handle(self, 'list', requestedHandleName)
171                 else:
172                         raise telepathy.errors.NotAvailable('Handle type unsupported %d' % handleType)
173                 return h
174
175         @property
176         def _channel_manager(self):
177                 return self.__channelManager
178
179         @gtk_toolbox.log_exception(_moduleLogger)
180         def Connect(self):
181                 """
182                 For org.freedesktop.telepathy.Connection
183                 """
184                 _moduleLogger.info("Connecting...")
185                 self.StatusChanged(
186                         telepathy.CONNECTION_STATUS_CONNECTING,
187                         telepathy.CONNECTION_STATUS_REASON_REQUESTED
188                 )
189                 try:
190                         self.__session.load(self.__cachePath)
191
192                         for plumber in self._plumbing:
193                                 plumber.start()
194                         self.session.login(*self.__credentials)
195                         if not self.__callbackNumberParameter:
196                                 callback = gvoice.backend.get_sane_callback(
197                                         self.session.backend
198                                 )
199                                 self.__callbackNumberParameter = util_misc.normalize_number(callback)
200                         self.session.backend.set_callback_number(self.__callbackNumberParameter)
201
202                         subscribeHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "subscribe")
203                         subscribeProps = self.generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, subscribeHandle, False)
204                         self.__channelManager.channel_for_props(subscribeProps, signal=True)
205                         publishHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "publish")
206                         publishProps = self.generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, publishHandle, False)
207                         self.__channelManager.channel_for_props(publishProps, signal=True)
208                 except gvoice.backend.NetworkError, e:
209                         _moduleLogger.exception("Connection Failed")
210                         self.StatusChanged(
211                                 telepathy.CONNECTION_STATUS_DISCONNECTED,
212                                 telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR
213                         )
214                         return
215                 except Exception, e:
216                         _moduleLogger.exception("Connection Failed")
217                         self.StatusChanged(
218                                 telepathy.CONNECTION_STATUS_DISCONNECTED,
219                                 telepathy.CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED
220                         )
221                         return
222
223                 _moduleLogger.info("Connected")
224                 self.StatusChanged(
225                         telepathy.CONNECTION_STATUS_CONNECTED,
226                         telepathy.CONNECTION_STATUS_REASON_REQUESTED
227                 )
228
229         @gtk_toolbox.log_exception(_moduleLogger)
230         def Disconnect(self):
231                 """
232                 For org.freedesktop.telepathy.Connection
233                 """
234                 self.StatusChanged(
235                         telepathy.CONNECTION_STATUS_DISCONNECTED,
236                         telepathy.CONNECTION_STATUS_REASON_REQUESTED
237                 )
238                 try:
239                         self.disconnect()
240                 except Exception:
241                         _moduleLogger.exception("Error durring disconnect")
242
243         @gtk_toolbox.log_exception(_moduleLogger)
244         def RequestChannel(self, type, handleType, handleId, suppressHandler):
245                 """
246                 For org.freedesktop.telepathy.Connection
247
248                 @param type DBus interface name for base channel type
249                 @param handleId represents a contact, list, etc according to handleType
250
251                 @returns DBus object path for the channel created or retrieved
252                 """
253                 self.check_connected()
254                 self.check_handle(handleType, handleId)
255
256                 h = self.get_handle_by_id(handleType, handleId) if handleId != 0 else None
257                 props = self.generate_props(type, h, suppressHandler)
258                 self._validate_handle(props)
259
260                 chan = self.__channelManager.channel_for_props(props, signal=True)
261                 path = chan._object_path
262                 _moduleLogger.info("RequestChannel Object Path (%s): %s" % (type.rsplit(".", 1)[-1], path))
263                 return path
264
265         def generate_props(self, channelType, handle, suppressHandler, initiatorHandle=None):
266                 targetHandle = 0 if handle is None else handle.get_id()
267                 targetHandleType = telepathy.HANDLE_TYPE_NONE if handle is None else handle.get_type()
268                 props = {
269                         telepathy.CHANNEL_INTERFACE + '.ChannelType': channelType,
270                         telepathy.CHANNEL_INTERFACE + '.TargetHandle': targetHandle,
271                         telepathy.CHANNEL_INTERFACE + '.TargetHandleType': targetHandleType,
272                         telepathy.CHANNEL_INTERFACE + '.Requested': suppressHandler
273                 }
274
275                 if initiatorHandle is not None:
276                         props[telepathy.CHANNEL_INTERFACE + '.InitiatorHandle'] = initiatorHandle.id
277
278                 return props
279
280         def disconnect(self):
281                 _moduleLogger.info("Disconnecting")
282                 for plumber in self._plumbing:
283                         plumber.stop()
284
285                 self.__channelManager.close()
286                 self.session.save(self.__cachePath)
287                 self.session.logout()
288                 self.session.close()
289
290                 self.manager.disconnected(self)
291                 _moduleLogger.info("Disconnected")