Attempting to fix a bug in code I stole from python-telepathy in which they deviated...
[theonering] / src / connection.py
1
2 """
3 @todo Add params for different state machines update times
4 @todo Add option to use screen name as callback
5 @todo Get a callback for missed calls to force an update of the voicemail state machine
6 @todo Get a callback on an incoming call and if its from GV, auto-pickup
7 @todo Observe when connected/disconnected to disconnect CM
8 """
9
10
11 import weakref
12 import logging
13
14 import telepathy
15
16 try:
17         import conic as _conic
18         conic = _conic
19 except (ImportError, OSError):
20         conic = None
21
22 import constants
23 import util.coroutines as coroutines
24 import gtk_toolbox
25
26 import gvoice
27 import handle
28
29 import requests
30 import contacts
31 import aliasing
32 import simple_presence
33 import presence
34 import capabilities
35
36 import channel_manager
37
38
39 _moduleLogger = logging.getLogger("connection")
40
41
42 class TheOneRingConnection(
43         telepathy.server.Connection,
44         requests.RequestsMixin,
45         contacts.ContactsMixin,
46         aliasing.AliasingMixin,
47         simple_presence.SimplePresenceMixin,
48         presence.PresenceMixin,
49         capabilities.CapabilitiesMixin,
50 ):
51
52         # Overriding a base class variable
53         # Should the forwarding number be handled by the alias or by an option?
54         _mandatory_parameters = {
55                 'account' : 's',
56                 'password' : 's',
57                 'forward' : 's',
58         }
59         # Overriding a base class variable
60         _optional_parameters = {
61         }
62         _parameter_defaults = {
63         }
64
65         @gtk_toolbox.log_exception(_moduleLogger)
66         def __init__(self, manager, parameters):
67                 self.check_parameters(parameters)
68                 account = unicode(parameters['account'])
69                 encodedAccount = parameters['account'].encode('utf-8')
70                 encodedPassword = parameters['password'].encode('utf-8')
71                 encodedCallback = parameters['forward'].encode('utf-8')
72                 if not encodedCallback:
73                         raise telepathy.errors.InvalidArgument("User must specify what number GV forwards calls to")
74
75                 # Connection init must come first
76                 telepathy.server.Connection.__init__(
77                         self,
78                         constants._telepathy_protocol_name_,
79                         account,
80                         constants._telepathy_implementation_name_
81                 )
82                 requests.RequestsMixin.__init__(self)
83                 contacts.ContactsMixin.__init__(self)
84                 aliasing.AliasingMixin.__init__(self)
85                 simple_presence.SimplePresenceMixin.__init__(self)
86                 presence.PresenceMixin.__init__(self)
87                 capabilities.CapabilitiesMixin.__init__(self)
88
89                 self.__manager = weakref.proxy(manager)
90                 self.__credentials = (
91                         encodedAccount,
92                         encodedPassword,
93                 )
94                 self.__callbackNumber = encodedCallback
95                 self.__channelManager = channel_manager.ChannelManager(self)
96
97                 self.__session = gvoice.session.Session(None)
98                 if conic is not None:
99                         self.__connection = conic.Connection()
100                         self.__connectionEventId = None
101                 else:
102                         self.__connection = None
103                         self.__connectionEventId = None
104
105                 self.set_self_handle(handle.create_handle(self, 'connection'))
106
107                 self.__callback = None
108                 _moduleLogger.info("Connection to the account %s created" % account)
109
110         @property
111         def manager(self):
112                 return self.__manager
113
114         @property
115         def session(self):
116                 return self.__session
117
118         @property
119         def username(self):
120                 return self.__credentials[0]
121
122         @property
123         def userAliasType(self):
124                 return self.USER_ALIAS_ACCOUNT
125
126         def handle(self, handleType, handleId):
127                 self.check_handle(handleType, handleId)
128                 return self._handles[handleType, handleId]
129
130         def handle_by_name(self, handleType, handleName):
131                 requestedHandleName = handleName.encode('utf-8')
132                 if handleType == telepathy.HANDLE_TYPE_CONTACT:
133                         _moduleLogger.info("RequestHandles Contact: %s" % requestedHandleName)
134                         requestedContactId, requestedContactNumber = handle.ContactHandle.from_handle_name(
135                                 requestedHandleName
136                         )
137                         h = handle.create_handle(self, 'contact', requestedContactId, requestedContactNumber)
138                 elif handleType == telepathy.HANDLE_TYPE_LIST:
139                         # Support only server side (immutable) lists
140                         _moduleLogger.info("RequestHandles List: %s" % requestedHandleName)
141                         h = handle.create_handle(self, 'list', requestedHandleName)
142                 else:
143                         raise telepathy.errors.NotAvailable('Handle type unsupported %d' % handleType)
144                 return h
145
146         @property
147         def _channel_manager(self):
148                 return self.__channelManager
149
150         @gtk_toolbox.log_exception(_moduleLogger)
151         def Connect(self):
152                 """
153                 For org.freedesktop.telepathy.Connection
154                 """
155                 _moduleLogger.info("Connecting...")
156                 self.StatusChanged(
157                         telepathy.CONNECTION_STATUS_CONNECTING,
158                         telepathy.CONNECTION_STATUS_REASON_REQUESTED
159                 )
160                 try:
161                         cookieFilePath = None
162                         self.__session = gvoice.session.Session(cookieFilePath)
163
164                         self.__callback = coroutines.func_sink(
165                                 coroutines.expand_positional(
166                                         self._on_conversations_updated
167                                 )
168                         )
169                         self.session.voicemails.updateSignalHandler.register_sink(
170                                 self.__callback
171                         )
172                         self.session.texts.updateSignalHandler.register_sink(
173                                 self.__callback
174                         )
175                         self.session.login(*self.__credentials)
176                         self.session.backend.set_callback_number(self.__callbackNumber)
177                 except gvoice.backend.NetworkError, e:
178                         _moduleLogger.exception("Connection Failed")
179                         self.StatusChanged(
180                                 telepathy.CONNECTION_STATUS_DISCONNECTED,
181                                 telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR
182                         )
183                         return
184                 except Exception, e:
185                         _moduleLogger.exception("Connection Failed")
186                         self.StatusChanged(
187                                 telepathy.CONNECTION_STATUS_DISCONNECTED,
188                                 telepathy.CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED
189                         )
190                         return
191
192                 _moduleLogger.info("Connected")
193                 self.StatusChanged(
194                         telepathy.CONNECTION_STATUS_CONNECTED,
195                         telepathy.CONNECTION_STATUS_REASON_REQUESTED
196                 )
197                 if self.__connection is not None:
198                         self.__connectionEventId = self.__connection.connect("connection-event", self._on_connection_change)
199
200         @gtk_toolbox.log_exception(_moduleLogger)
201         def Disconnect(self):
202                 """
203                 For org.freedesktop.telepathy.Connection
204                 """
205                 self.StatusChanged(
206                         telepathy.CONNECTION_STATUS_DISCONNECTED,
207                         telepathy.CONNECTION_STATUS_REASON_REQUESTED
208                 )
209                 try:
210                         self._disconnect()
211                 except Exception:
212                         _moduleLogger.exception("Error durring disconnect")
213
214         @gtk_toolbox.log_exception(_moduleLogger)
215         def RequestChannel(self, type, handleType, handleId, suppressHandler):
216                 """
217                 For org.freedesktop.telepathy.Connection
218
219                 @param type DBus interface name for base channel type
220                 @param handleId represents a contact, list, etc according to handleType
221
222                 @returns DBus object path for the channel created or retrieved
223                 """
224                 self.check_connected()
225                 self.check_handle(handleType, handleId)
226
227                 h = self.handle(handleType, handleId) if handleId != 0 else None
228                 props = self._generate_props(type, h, suppressHandler)
229                 if hasattr(self, "_validate_handle"):
230                         # HACK Newer python-telepathy
231                         self._validate_handle(props)
232
233                 chan = self.__channelManager.channel_for_props(props, signal=True)
234                 path = chan._object_path
235                 _moduleLogger.info("RequestChannel Object Path: %s" % path)
236                 return path
237
238         @gtk_toolbox.log_exception(_moduleLogger)
239         def RequestHandles(self, handleType, names, sender):
240                 """
241                 For org.freedesktop.telepathy.Connection
242                 Overiding telepathy.server.Connecton to allow custom handles
243                 """
244                 self.check_connected()
245                 self.check_handle_type(handleType)
246
247                 handles = []
248                 for name in names:
249                         h = self.handle_by_name(handleType, name)
250                         handles.append(h)
251                         self.add_client_handle(h, sender)
252                 return handles
253
254         def _generate_props(self, channelType, handle, suppressHandler, initiatorHandle=None):
255                 targetHandle = 0 if handle is None else handle.get_id()
256                 targetHandleType = telepathy.HANDLE_TYPE_NONE if handle is None else handle.get_type()
257                 props = {
258                         telepathy.CHANNEL_INTERFACE + '.ChannelType': channelType,
259                         telepathy.CHANNEL_INTERFACE + '.TargetHandle': targetHandle,
260                         telepathy.CHANNEL_INTERFACE + '.TargetHandleType': targetHandleType,
261                         telepathy.CHANNEL_INTERFACE + '.Requested': suppressHandler
262                 }
263
264                 if initiatorHandle is not None:
265                         props[telepathy.CHANNEL_INTERFACE + '.InitiatorHandle'] = initiatorHandle.id
266
267                 return props
268
269         def _disconnect(self):
270                 _moduleLogger.info("Disconnecting")
271                 self.session.voicemails.updateSignalHandler.unregister_sink(
272                         self.__callback
273                 )
274                 self.session.texts.updateSignalHandler.unregister_sink(
275                         self.__callback
276                 )
277                 self.__callback = None
278
279                 self.__channelManager.close()
280                 self.session.logout()
281                 self.session.close()
282                 self.__session = None
283                 if self.__connection is not None:
284                         self.__connection.disconnect(self.__connectionEventId)
285                         self.__connectionEventId = None
286
287                 self.manager.disconnected(self)
288                 _moduleLogger.info("Disconnected")
289
290         @gtk_toolbox.log_exception(_moduleLogger)
291         def _on_conversations_updated(self, conv, conversationIds):
292                 _moduleLogger.debug("Incoming messages from: %r" % (conversationIds, ))
293                 for contactId, phoneNumber in conversationIds:
294                         h = handle.create_handle(self, 'contact', contactId, phoneNumber)
295                         # Just let the TextChannel decide whether it should be reported to the user or not
296                         props = self._generate_props(telepathy.CHANNEL_TYPE_TEXT, h, False)
297                         channel = self.__channelManager.channel_for_props(props, signal=True)
298
299         @gtk_toolbox.log_exception(_moduleLogger)
300         def _on_connection_change(self, connection, event):
301                 """
302                 @note Maemo specific
303                 """
304                 status = event.get_status()
305                 error = event.get_error()
306                 iap_id = event.get_iap_id()
307                 bearer = event.get_bearer_type()
308
309                 if status == conic.STATUS_DISCONNECTED:
310                         _moduleLogger.info("Disconnecting due to loss of network connection")
311                         self.StatusChanged(
312                                 telepathy.CONNECTION_STATUS_DISCONNECTED,
313                                 telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR
314                         )
315                         try:
316                                 self._disconnect()
317                         except Exception:
318                                 _moduleLogger.exception("Error durring disconnect")