Fixing lots of minor bugs found by lint
[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.go_utils as gobject_utils
10 import util.misc as misc_utils
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         @misc_utils.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 = misc_utils.normalize_number(parameters['forward'].encode('utf-8'))
92                 if encodedCallback and not misc_utils.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                 self._delayedConnect = gobject_utils.Async(self._delayed_connect)
140
141                 _moduleLogger.info("Connection to the account %s created" % account)
142
143         @property
144         def manager(self):
145                 return self.__manager
146
147         @property
148         def session(self):
149                 return self.__session
150
151         @property
152         def options(self):
153                 return self.__options
154
155         @property
156         def username(self):
157                 return self.__credentials[0]
158
159         @property
160         def callbackNumberParameter(self):
161                 return self.__callbackNumberParameter
162
163         def get_handle_by_name(self, handleType, handleName):
164                 requestedHandleName = handleName.encode('utf-8')
165                 if handleType == telepathy.HANDLE_TYPE_CONTACT:
166                         _moduleLogger.debug("get_handle_by_name Contact: %s" % requestedHandleName)
167                         h = handle.create_handle(self, 'contact', requestedHandleName)
168                 elif handleType == telepathy.HANDLE_TYPE_LIST:
169                         # Support only server side (immutable) lists
170                         _moduleLogger.debug("get_handle_by_name List: %s" % requestedHandleName)
171                         h = handle.create_handle(self, 'list', requestedHandleName)
172                 else:
173                         raise telepathy.errors.NotAvailable('Handle type unsupported %d' % handleType)
174                 return h
175
176         @property
177         def _channel_manager(self):
178                 return self.__channelManager
179
180         @misc_utils.log_exception(_moduleLogger)
181         def Connect(self):
182                 """
183                 For org.freedesktop.telepathy.Connection
184                 """
185                 if self._status != telepathy.CONNECTION_STATUS_DISCONNECTED:
186                         _moduleLogger.info("Attempting connect when not disconnected")
187                         return
188                 _moduleLogger.info("Kicking off connect")
189                 self._delayedConnect.start()
190
191         @misc_utils.log_exception(_moduleLogger)
192         def _delayed_connect(self):
193                 _moduleLogger.info("Connecting...")
194                 self.StatusChanged(
195                         telepathy.CONNECTION_STATUS_CONNECTING,
196                         telepathy.CONNECTION_STATUS_REASON_REQUESTED
197                 )
198                 try:
199                         self.__session.load(self.__cachePath)
200
201                         for plumber in self._plumbing:
202                                 plumber.start()
203                         self.session.login(*self.__credentials)
204                         if not self.__callbackNumberParameter:
205                                 callback = gvoice.backend.get_sane_callback(
206                                         self.session.backend
207                                 )
208                                 self.__callbackNumberParameter = misc_utils.normalize_number(callback)
209                         self.session.backend.set_callback_number(self.__callbackNumberParameter)
210
211                         subscribeHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "subscribe")
212                         subscribeProps = self.generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, subscribeHandle, False)
213                         self.__channelManager.channel_for_props(subscribeProps, signal=True)
214                         publishHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "publish")
215                         publishProps = self.generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, publishHandle, False)
216                         self.__channelManager.channel_for_props(publishProps, signal=True)
217                 except gvoice.backend.NetworkError, e:
218                         _moduleLogger.exception("Connection Failed")
219                         self.disconnect(telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR)
220                         return
221                 except Exception, e:
222                         _moduleLogger.exception("Connection Failed")
223                         self.disconnect(telepathy.CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED)
224                         return
225
226                 _moduleLogger.info("Connected")
227                 self.StatusChanged(
228                         telepathy.CONNECTION_STATUS_CONNECTED,
229                         telepathy.CONNECTION_STATUS_REASON_REQUESTED
230                 )
231
232         @misc_utils.log_exception(_moduleLogger)
233         def Disconnect(self):
234                 """
235                 For org.freedesktop.telepathy.Connection
236                 """
237                 _moduleLogger.info("Kicking off disconnect")
238                 self._delayed_disconnect()
239
240         @misc_utils.log_exception(_moduleLogger)
241         def RequestChannel(self, type, handleType, handleId, suppressHandler):
242                 """
243                 For org.freedesktop.telepathy.Connection
244
245                 @param type DBus interface name for base channel type
246                 @param handleId represents a contact, list, etc according to handleType
247
248                 @returns DBus object path for the channel created or retrieved
249                 """
250                 self.check_connected()
251                 self.check_handle(handleType, handleId)
252
253                 h = self.get_handle_by_id(handleType, handleId) if handleId != 0 else None
254                 props = self.generate_props(type, h, suppressHandler)
255                 self._validate_handle(props)
256
257                 chan = self.__channelManager.channel_for_props(props, signal=True)
258                 path = chan._object_path
259                 _moduleLogger.info("RequestChannel Object Path (%s): %s" % (type.rsplit(".", 1)[-1], path))
260                 return path
261
262         def generate_props(self, channelType, handleObj, suppressHandler, initiatorHandle=None):
263                 targetHandle = 0 if handleObj is None else handleObj.get_id()
264                 targetHandleType = telepathy.HANDLE_TYPE_NONE if handleObj is None else handleObj.get_type()
265                 props = {
266                         telepathy.CHANNEL_INTERFACE + '.ChannelType': channelType,
267                         telepathy.CHANNEL_INTERFACE + '.TargetHandle': targetHandle,
268                         telepathy.CHANNEL_INTERFACE + '.TargetHandleType': targetHandleType,
269                         telepathy.CHANNEL_INTERFACE + '.Requested': suppressHandler
270                 }
271
272                 if initiatorHandle is not None:
273                         props[telepathy.CHANNEL_INTERFACE + '.InitiatorHandle'] = initiatorHandle.id
274
275                 return props
276
277         @gobject_utils.async
278         def _delayed_disconnect(self):
279                 self.disconnect(telepathy.CONNECTION_STATUS_REASON_REQUESTED)
280                 return False
281
282         def disconnect(self, reason):
283                 _moduleLogger.info("Disconnecting")
284
285                 self._delayedConnect.cancel()
286
287                 # Not having the disconnect first can cause weird behavior with clients
288                 # including not being able to reconnect or even crashing
289                 self.StatusChanged(
290                         telepathy.CONNECTION_STATUS_DISCONNECTED,
291                         reason,
292                 )
293
294                 for plumber in self._plumbing:
295                         plumber.stop()
296
297                 self.__channelManager.close()
298                 self.session.save(self.__cachePath)
299                 self.session.logout()
300                 self.session.close()
301
302                 self.manager.disconnected(self)
303                 _moduleLogger.info("Disconnected")