11 except (ImportError, OSError):
16 import util.coroutines as coroutines
17 import util.go_utils as gobject_utils
18 import util.misc as util_misc
27 import simple_presence
31 import channel_manager
34 _moduleLogger = logging.getLogger("connection")
37 class TheOneRingOptions(object):
41 assert gvoice.session.Session._DEFAULTS["contacts"][1] == "hours"
42 contactsPollPeriodInHours = gvoice.session.Session._DEFAULTS["contacts"][0]
44 assert gvoice.session.Session._DEFAULTS["voicemail"][1] == "minutes"
45 voicemailPollPeriodInMinutes = gvoice.session.Session._DEFAULTS["voicemail"][0]
47 assert gvoice.session.Session._DEFAULTS["texts"][1] == "minutes"
48 textsPollPeriodInMinutes = gvoice.session.Session._DEFAULTS["texts"][0]
50 def __init__(self, parameters = None):
51 if parameters is None:
53 self.useGVContacts = parameters["use-gv-contacts"]
54 self.contactsPollPeriodInHours = parameters['contacts-poll-period-in-hours']
55 self.voicemailPollPeriodInMinutes = parameters['voicemail-poll-period-in-minutes']
56 self.textsPollPeriodInMinutes = parameters['texts-poll-period-in-minutes']
59 class TheOneRingConnection(
61 requests.RequestsMixin,
62 contacts.ContactsMixin,
63 aliasing.AliasingMixin,
64 simple_presence.SimplePresenceMixin,
65 presence.PresenceMixin,
66 capabilities.CapabilitiesMixin,
69 # overiding base class variable
70 _mandatory_parameters = {
74 # overiding base class variable
75 _optional_parameters = {
77 'use-gv-contacts': 'b',
78 'contacts-poll-period-in-hours': 'i',
79 'voicemail-poll-period-in-minutes': 'i',
80 'texts-poll-period-in-minutes': 'i',
82 _parameter_defaults = {
84 'use-gv-contacts': TheOneRingOptions.useGVContacts,
85 'contacts-poll-period-in-hours': TheOneRingOptions.contactsPollPeriodInHours,
86 'voicemail-poll-period-in-minutes': TheOneRingOptions.voicemailPollPeriodInMinutes,
87 'texts-poll-period-in-minutes': TheOneRingOptions.textsPollPeriodInMinutes,
89 _secret_parameters = set((
93 @gtk_toolbox.log_exception(_moduleLogger)
94 def __init__(self, manager, parameters):
95 self.check_parameters(parameters)
96 account = unicode(parameters['account'])
97 encodedAccount = parameters['account'].encode('utf-8')
98 encodedPassword = parameters['password'].encode('utf-8')
99 encodedCallback = util_misc.normalize_number(parameters['forward'].encode('utf-8'))
100 if encodedCallback and not util_misc.is_valid_number(encodedCallback):
101 raise telepathy.errors.InvalidArgument("Invalid forwarding number")
103 # Connection init must come first
104 self.__options = TheOneRingOptions(parameters)
105 self.__session = gvoice.session.Session(
108 "contacts": (self.__options.contactsPollPeriodInHours, "hours"),
109 "voicemail": (self.__options.voicemailPollPeriodInMinutes, "minutes"),
110 "texts": (self.__options.textsPollPeriodInMinutes, "minutes"),
113 tp.Connection.__init__(
115 constants._telepathy_protocol_name_,
117 constants._telepathy_implementation_name_
119 requests.RequestsMixin.__init__(self)
120 contacts.ContactsMixin.__init__(self)
121 aliasing.AliasingMixin.__init__(self)
122 simple_presence.SimplePresenceMixin.__init__(self)
123 presence.PresenceMixin.__init__(self)
124 capabilities.CapabilitiesMixin.__init__(self)
126 self.__manager = weakref.proxy(manager)
127 self.__credentials = (
131 self.__callbackNumberParameter = encodedCallback
132 self.__channelManager = channel_manager.ChannelManager(self)
134 if conic is not None:
135 self.__connection = conic.Connection()
137 self.__connection = None
138 self.__cachePath = os.sep.join((constants._data_path_, "cache", self.username))
140 os.makedirs(self.__cachePath)
145 self.set_self_handle(handle.create_handle(self, 'connection'))
147 self.__callback = None
148 self.__connectionEventId = None
149 self.__delayedDisconnectEventId = None
150 _moduleLogger.info("Connection to the account %s created" % account)
154 return self.__manager
158 return self.__session
162 return self.__options
166 return self.__credentials[0]
169 def callbackNumberParameter(self):
170 return self.__callbackNumberParameter
172 def get_handle_by_name(self, handleType, handleName):
173 requestedHandleName = handleName.encode('utf-8')
174 if handleType == telepathy.HANDLE_TYPE_CONTACT:
175 _moduleLogger.debug("get_handle_by_name Contact: %s" % requestedHandleName)
176 h = handle.create_handle(self, 'contact', requestedHandleName)
177 elif handleType == telepathy.HANDLE_TYPE_LIST:
178 # Support only server side (immutable) lists
179 _moduleLogger.debug("get_handle_by_name List: %s" % requestedHandleName)
180 h = handle.create_handle(self, 'list', requestedHandleName)
182 raise telepathy.errors.NotAvailable('Handle type unsupported %d' % handleType)
186 def _channel_manager(self):
187 return self.__channelManager
189 @gtk_toolbox.log_exception(_moduleLogger)
192 For org.freedesktop.telepathy.Connection
194 _moduleLogger.info("Connecting...")
196 telepathy.CONNECTION_STATUS_CONNECTING,
197 telepathy.CONNECTION_STATUS_REASON_REQUESTED
200 self.__session.load(self.__cachePath)
202 self.__callback = coroutines.func_sink(
203 coroutines.expand_positional(
204 self._on_conversations_updated
207 self.session.voicemails.updateSignalHandler.register_sink(
210 self.session.texts.updateSignalHandler.register_sink(
213 self.session.login(*self.__credentials)
214 if not self.__callbackNumberParameter:
215 callback = gvoice.backend.get_sane_callback(
218 self.__callbackNumberParameter = util_misc.normalize_number(callback)
219 self.session.backend.set_callback_number(self.__callbackNumberParameter)
221 subscribeHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "subscribe")
222 subscribeProps = self._generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, subscribeHandle, False)
223 self.__channelManager.channel_for_props(subscribeProps, signal=True)
224 publishHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "publish")
225 publishProps = self._generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, publishHandle, False)
226 self.__channelManager.channel_for_props(publishProps, signal=True)
227 except gvoice.backend.NetworkError, e:
228 _moduleLogger.exception("Connection Failed")
230 telepathy.CONNECTION_STATUS_DISCONNECTED,
231 telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR
235 _moduleLogger.exception("Connection Failed")
237 telepathy.CONNECTION_STATUS_DISCONNECTED,
238 telepathy.CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED
242 _moduleLogger.info("Connected")
244 telepathy.CONNECTION_STATUS_CONNECTED,
245 telepathy.CONNECTION_STATUS_REASON_REQUESTED
247 if self.__connection is not None:
248 self.__connectionEventId = self.__connection.connect("connection-event", self._on_connection_change)
250 @gtk_toolbox.log_exception(_moduleLogger)
251 def Disconnect(self):
253 For org.freedesktop.telepathy.Connection
258 _moduleLogger.exception("Error durring disconnect")
260 telepathy.CONNECTION_STATUS_DISCONNECTED,
261 telepathy.CONNECTION_STATUS_REASON_REQUESTED
264 @gtk_toolbox.log_exception(_moduleLogger)
265 def RequestChannel(self, type, handleType, handleId, suppressHandler):
267 For org.freedesktop.telepathy.Connection
269 @param type DBus interface name for base channel type
270 @param handleId represents a contact, list, etc according to handleType
272 @returns DBus object path for the channel created or retrieved
274 self.check_connected()
275 self.check_handle(handleType, handleId)
277 h = self.get_handle_by_id(handleType, handleId) if handleId != 0 else None
278 props = self._generate_props(type, h, suppressHandler)
279 self._validate_handle(props)
281 chan = self.__channelManager.channel_for_props(props, signal=True)
282 path = chan._object_path
283 _moduleLogger.info("RequestChannel Object Path (%s): %s" % (type.rsplit(".", 1)[-1], path))
286 def _generate_props(self, channelType, handle, suppressHandler, initiatorHandle=None):
287 targetHandle = 0 if handle is None else handle.get_id()
288 targetHandleType = telepathy.HANDLE_TYPE_NONE if handle is None else handle.get_type()
290 telepathy.CHANNEL_INTERFACE + '.ChannelType': channelType,
291 telepathy.CHANNEL_INTERFACE + '.TargetHandle': targetHandle,
292 telepathy.CHANNEL_INTERFACE + '.TargetHandleType': targetHandleType,
293 telepathy.CHANNEL_INTERFACE + '.Requested': suppressHandler
296 if initiatorHandle is not None:
297 props[telepathy.CHANNEL_INTERFACE + '.InitiatorHandle'] = initiatorHandle.id
301 def _disconnect(self):
302 _moduleLogger.info("Disconnecting")
303 self.session.voicemails.updateSignalHandler.unregister_sink(
306 self.session.texts.updateSignalHandler.unregister_sink(
309 self.__callback = None
311 self.__channelManager.close()
312 self.session.save(self.__cachePath)
313 self.session.logout()
316 self.manager.disconnected(self)
318 self._cancel_delayed_disconnect()
319 self.__connection = None
320 _moduleLogger.info("Disconnected")
322 @gtk_toolbox.log_exception(_moduleLogger)
323 def _on_conversations_updated(self, conv, conversationIds):
324 _moduleLogger.debug("Incoming messages from: %r" % (conversationIds, ))
325 for phoneNumber in conversationIds:
326 h = self.get_handle_by_name(telepathy.HANDLE_TYPE_CONTACT, phoneNumber)
327 # Just let the TextChannel decide whether it should be reported to the user or not
328 props = self._generate_props(telepathy.CHANNEL_TYPE_TEXT, h, False)
329 if self.__channelManager.channel_exists(props):
332 # Maemo 4.1's RTComm opens a window for a chat regardless if a
333 # message is received or not, so we need to do some filtering here
334 mergedConv = conv.get_conversation(phoneNumber)
337 for conversation in mergedConv.conversations
338 if not conversation.isRead and not conversation.isArchived
343 chan = self.__channelManager.channel_for_props(props, signal=True)
345 @gtk_toolbox.log_exception(_moduleLogger)
346 def _on_connection_change(self, connection, event):
350 status = event.get_status()
351 error = event.get_error()
352 iap_id = event.get_iap_id()
353 bearer = event.get_bearer_type()
355 if status == conic.STATUS_DISCONNECTED:
356 _moduleLogger.info("Disconnected from network, starting countdown to logoff")
357 self.__delayedDisconnectEventId = gobject_utils.timeout_add_seconds(
358 5, self._on_delayed_disconnect
360 elif status == conic.STATUS_CONNECTED:
361 _moduleLogger.info("Connected to network")
362 self._cancel_delayed_disconnect()
364 _moduleLogger.info("Other status: %r" % (status, ))
366 def _cancel_delayed_disconnect(self):
367 if self.__delayedDisconnectEventId is None:
369 _moduleLogger.info("Cancelling auto-log off")
370 gobject.source_reove(self.__delayedDisconnectEventId)
371 self.__delayedDisconnectEventId = None
373 @gtk_toolbox.log_exception(_moduleLogger)
374 def _on_delayed_disconnect(self):
375 if not self.session.is_logged_in():
376 _moduleLogger.info("Received connection change event when not logged in")
381 _moduleLogger.exception("Error durring disconnect")
383 telepathy.CONNECTION_STATUS_DISCONNECTED,
384 telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR
386 self.__delayedDisconnectEventId = None