10 except (ImportError, OSError):
15 import util.coroutines as coroutines
16 import util.misc as util_misc
25 import simple_presence
29 import channel_manager
32 _moduleLogger = logging.getLogger("connection")
35 class TheOneRingOptions(object):
39 assert gvoice.session.Session._DEFAULTS["contacts"][1] == "hours"
40 contactsPollPeriodInHours = gvoice.session.Session._DEFAULTS["contacts"][0]
42 assert gvoice.session.Session._DEFAULTS["voicemail"][1] == "minutes"
43 voicemailPollPeriodInMinutes = gvoice.session.Session._DEFAULTS["voicemail"][0]
45 assert gvoice.session.Session._DEFAULTS["texts"][1] == "minutes"
46 textsPollPeriodInMinutes = gvoice.session.Session._DEFAULTS["texts"][0]
48 def __init__(self, parameters = None):
49 if parameters is None:
51 self.useGVContacts = parameters["use-gv-contacts"]
52 self.contactsPollPeriodInHours = parameters['contacts-poll-period-in-hours']
53 self.voicemailPollPeriodInMinutes = parameters['voicemail-poll-period-in-minutes']
54 self.textsPollPeriodInMinutes = parameters['texts-poll-period-in-minutes']
57 class TheOneRingConnection(
59 requests.RequestsMixin,
60 contacts.ContactsMixin,
61 aliasing.AliasingMixin,
62 simple_presence.SimplePresenceMixin,
63 presence.PresenceMixin,
64 capabilities.CapabilitiesMixin,
67 # overiding base class variable
68 _mandatory_parameters = {
72 # overiding base class variable
73 _optional_parameters = {
75 'use-gv-contacts': 'b',
76 'contacts-poll-period': 'i',
77 'voicemail-poll-period': 'i',
78 'texts-poll-period': 'i',
80 _parameter_defaults = {
82 'use-gv-contacts': TheOneRingOptions.useGVContacts,
83 'contacts-poll-period-in-hours': TheOneRingOptions.contactsPollPeriodInHours,
84 'voicemail-poll-period-in-minutes': TheOneRingOptions.voicemailPollPeriodInMinutes,
85 'texts-poll-period-in-minutes': TheOneRingOptions.textsPollPeriodInMinutes,
87 _secret_parameters = set((
91 @gtk_toolbox.log_exception(_moduleLogger)
92 def __init__(self, manager, parameters):
93 self.check_parameters(parameters)
94 account = unicode(parameters['account'])
95 encodedAccount = parameters['account'].encode('utf-8')
96 encodedPassword = parameters['password'].encode('utf-8')
97 encodedCallback = util_misc.normalize_number(parameters['forward'].encode('utf-8'))
98 if encodedCallback and not util_misc.is_valid_number(encodedCallback):
99 raise telepathy.errors.InvalidArgument("Invalid forwarding number")
101 # Connection init must come first
102 self.__session = gvoice.session.Session(None)
103 self.__options = TheOneRingOptions(parameters)
104 tp.Connection.__init__(
106 constants._telepathy_protocol_name_,
108 constants._telepathy_implementation_name_
110 requests.RequestsMixin.__init__(self)
111 contacts.ContactsMixin.__init__(self)
112 aliasing.AliasingMixin.__init__(self)
113 simple_presence.SimplePresenceMixin.__init__(self)
114 presence.PresenceMixin.__init__(self)
115 capabilities.CapabilitiesMixin.__init__(self)
117 self.__manager = weakref.proxy(manager)
118 self.__credentials = (
122 self.__callbackNumberParameter = encodedCallback
123 self.__channelManager = channel_manager.ChannelManager(self)
125 if conic is not None:
126 self.__connection = conic.Connection()
128 self.__connection = None
129 self.__cachePath = os.sep.join((constants._data_path_, "cache", self.username))
131 os.makedirs(self.__cachePath)
136 self.set_self_handle(handle.create_handle(self, 'connection'))
138 self.__callback = None
139 _moduleLogger.info("Connection to the account %s created" % account)
143 return self.__manager
147 return self.__session
151 return self.__options
155 return self.__credentials[0]
158 def callbackNumberParameter(self):
159 return self.__callbackNumberParameter
161 def get_handle_by_name(self, handleType, handleName):
162 requestedHandleName = handleName.encode('utf-8')
163 if handleType == telepathy.HANDLE_TYPE_CONTACT:
164 _moduleLogger.debug("get_handle_by_name Contact: %s" % requestedHandleName)
165 h = handle.create_handle(self, 'contact', requestedHandleName)
166 elif handleType == telepathy.HANDLE_TYPE_LIST:
167 # Support only server side (immutable) lists
168 _moduleLogger.debug("get_handle_by_name List: %s" % requestedHandleName)
169 h = handle.create_handle(self, 'list', requestedHandleName)
171 raise telepathy.errors.NotAvailable('Handle type unsupported %d' % handleType)
175 def _channel_manager(self):
176 return self.__channelManager
178 @gtk_toolbox.log_exception(_moduleLogger)
181 For org.freedesktop.telepathy.Connection
183 _moduleLogger.info("Connecting...")
185 telepathy.CONNECTION_STATUS_CONNECTING,
186 telepathy.CONNECTION_STATUS_REASON_REQUESTED
189 self.__session.load(self.__cachePath)
191 self.__callback = coroutines.func_sink(
192 coroutines.expand_positional(
193 self._on_conversations_updated
196 self.session.voicemails.updateSignalHandler.register_sink(
199 self.session.texts.updateSignalHandler.register_sink(
202 self.session.login(*self.__credentials)
203 if not self.__callbackNumberParameter:
204 callback = gvoice.backend.get_sane_callback(
207 self.__callbackNumberParameter = util_misc.normalize_number(callback)
208 self.session.backend.set_callback_number(self.__callbackNumberParameter)
210 subscribeHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "subscribe")
211 subscribeProps = self._generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, subscribeHandle, False)
212 self.__channelManager.channel_for_props(subscribeProps, signal=True)
213 publishHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "publish")
214 publishProps = self._generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, publishHandle, False)
215 self.__channelManager.channel_for_props(publishProps, signal=True)
216 except gvoice.backend.NetworkError, e:
217 _moduleLogger.exception("Connection Failed")
219 telepathy.CONNECTION_STATUS_DISCONNECTED,
220 telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR
224 _moduleLogger.exception("Connection Failed")
226 telepathy.CONNECTION_STATUS_DISCONNECTED,
227 telepathy.CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED
231 _moduleLogger.info("Connected")
233 telepathy.CONNECTION_STATUS_CONNECTED,
234 telepathy.CONNECTION_STATUS_REASON_REQUESTED
236 if self.__connection is not None:
237 self.__connectionEventId = self.__connection.connect("connection-event", self._on_connection_change)
239 @gtk_toolbox.log_exception(_moduleLogger)
240 def Disconnect(self):
242 For org.freedesktop.telepathy.Connection
247 _moduleLogger.exception("Error durring disconnect")
249 telepathy.CONNECTION_STATUS_DISCONNECTED,
250 telepathy.CONNECTION_STATUS_REASON_REQUESTED
253 @gtk_toolbox.log_exception(_moduleLogger)
254 def RequestChannel(self, type, handleType, handleId, suppressHandler):
256 For org.freedesktop.telepathy.Connection
258 @param type DBus interface name for base channel type
259 @param handleId represents a contact, list, etc according to handleType
261 @returns DBus object path for the channel created or retrieved
263 self.check_connected()
264 self.check_handle(handleType, handleId)
266 h = self.get_handle_by_id(handleType, handleId) if handleId != 0 else None
267 props = self._generate_props(type, h, suppressHandler)
268 self._validate_handle(props)
270 chan = self.__channelManager.channel_for_props(props, signal=True)
271 path = chan._object_path
272 _moduleLogger.info("RequestChannel Object Path (%s): %s" % (type.rsplit(".", 1)[-1], path))
275 def _generate_props(self, channelType, handle, suppressHandler, initiatorHandle=None):
276 targetHandle = 0 if handle is None else handle.get_id()
277 targetHandleType = telepathy.HANDLE_TYPE_NONE if handle is None else handle.get_type()
279 telepathy.CHANNEL_INTERFACE + '.ChannelType': channelType,
280 telepathy.CHANNEL_INTERFACE + '.TargetHandle': targetHandle,
281 telepathy.CHANNEL_INTERFACE + '.TargetHandleType': targetHandleType,
282 telepathy.CHANNEL_INTERFACE + '.Requested': suppressHandler
285 if initiatorHandle is not None:
286 props[telepathy.CHANNEL_INTERFACE + '.InitiatorHandle'] = initiatorHandle.id
290 def _disconnect(self):
291 _moduleLogger.info("Disconnecting")
292 self.session.voicemails.updateSignalHandler.unregister_sink(
295 self.session.texts.updateSignalHandler.unregister_sink(
298 self.__callback = None
300 self.__channelManager.close()
301 self.session.save(self.__cachePath)
302 self.session.logout()
305 self.manager.disconnected(self)
306 _moduleLogger.info("Disconnected")
308 @gtk_toolbox.log_exception(_moduleLogger)
309 def _on_conversations_updated(self, conv, conversationIds):
310 _moduleLogger.debug("Incoming messages from: %r" % (conversationIds, ))
311 for phoneNumber in conversationIds:
312 h = self.get_handle_by_name(telepathy.HANDLE_TYPE_CONTACT, phoneNumber)
313 # Just let the TextChannel decide whether it should be reported to the user or not
314 props = self._generate_props(telepathy.CHANNEL_TYPE_TEXT, h, False)
315 if self.__channelManager.channel_exists(props):
318 # Maemo 4.1's RTComm opens a window for a chat regardless if a
319 # message is received or not, so we need to do some filtering here
320 mergedConv = conv.get_conversation(phoneNumber)
323 for conversation in mergedConv.conversations
324 if not conversation.isRead and not conversation.isArchived
329 chan = self.__channelManager.channel_for_props(props, signal=True)
331 @gtk_toolbox.log_exception(_moduleLogger)
332 def _on_connection_change(self, connection, event):
336 if not self.session.is_logged_in():
337 _moduleLogger.info("Received connection change event when not logged in")
339 status = event.get_status()
340 error = event.get_error()
341 iap_id = event.get_iap_id()
342 bearer = event.get_bearer_type()
344 if status == conic.STATUS_DISCONNECTED:
345 _moduleLogger.info("Disconnecting due to loss of network connection")
347 telepathy.CONNECTION_STATUS_DISCONNECTED,
348 telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR
353 _moduleLogger.exception("Error durring disconnect")
355 _moduleLogger.info("Other status: %r" % (status, ))