9 import util.misc as misc_utils
20 import simple_presence
23 import channel_manager
26 _moduleLogger = logging.getLogger(__name__)
29 class TheOneRingOptions(object):
35 assert gvoice.session.Session._DEFAULTS["contacts"][1] == "hours"
36 contactsPollPeriodInHours = gvoice.session.Session._DEFAULTS["contacts"][0]
38 assert gvoice.session.Session._DEFAULTS["voicemail"][1] == "minutes"
39 voicemailPollPeriodInMinutes = gvoice.session.Session._DEFAULTS["voicemail"][0]
41 assert gvoice.session.Session._DEFAULTS["texts"][1] == "minutes"
42 textsPollPeriodInMinutes = gvoice.session.Session._DEFAULTS["texts"][0]
44 def __init__(self, parameters = None):
45 if parameters is None:
47 self.ignoreDND = parameters["ignore-dnd"]
48 self.useGVContacts = parameters["use-gv-contacts"]
49 self.contactsPollPeriodInHours = parameters['contacts-poll-period-in-hours']
50 self.voicemailPollPeriodInMinutes = parameters['voicemail-poll-period-in-minutes']
51 self.textsPollPeriodInMinutes = parameters['texts-poll-period-in-minutes']
54 class TheOneRingConnection(
56 aliasing.AliasingMixin,
58 capabilities.CapabilitiesMixin,
59 contacts.ContactsMixin,
60 requests.RequestsMixin,
61 simple_presence.TheOneRingPresence,
62 simple_presence.SimplePresenceMixin,
63 presence.PresenceMixin,
66 # overiding base class variable
67 _mandatory_parameters = {
71 # overiding base class variable
72 _optional_parameters = {
75 'use-gv-contacts': 'b',
76 'contacts-poll-period-in-hours': 'i',
77 'voicemail-poll-period-in-minutes': 'i',
78 'texts-poll-period-in-minutes': 'i',
80 _parameter_defaults = {
82 'ignore-dnd': TheOneRingOptions.ignoreDND,
83 'use-gv-contacts': TheOneRingOptions.useGVContacts,
84 'contacts-poll-period-in-hours': TheOneRingOptions.contactsPollPeriodInHours,
85 'voicemail-poll-period-in-minutes': TheOneRingOptions.voicemailPollPeriodInMinutes,
86 'texts-poll-period-in-minutes': TheOneRingOptions.textsPollPeriodInMinutes,
88 _secret_parameters = set((
92 @misc_utils.log_exception(_moduleLogger)
93 def __init__(self, manager, parameters):
96 self.check_parameters(parameters)
97 account = unicode(parameters['account'])
98 encodedAccount = parameters['account'].encode('utf-8')
99 encodedPassword = parameters['password'].encode('utf-8')
100 encodedCallback = misc_utils.normalize_number(parameters['forward'].encode('utf-8'))
101 if encodedCallback and not misc_utils.is_valid_number(encodedCallback):
102 raise telepathy.errors.InvalidArgument("Invalid forwarding number")
104 # Connection init must come first
105 self.__options = TheOneRingOptions(parameters)
106 self.__session = gvoice.session.Session(
107 cookiePath = os.path.join(constants._data_path_, "%s.cookies" % account),
109 "contacts": (self.__options.contactsPollPeriodInHours, "hours"),
110 "voicemail": (self.__options.voicemailPollPeriodInMinutes, "minutes"),
111 "texts": (self.__options.textsPollPeriodInMinutes, "minutes"),
114 tp.Connection.__init__(
116 constants._telepathy_protocol_name_,
118 constants._telepathy_implementation_name_
120 aliasing.AliasingMixin.__init__(self)
121 avatars.AvatarsMixin.__init__(self)
122 capabilities.CapabilitiesMixin.__init__(self)
123 contacts.ContactsMixin.__init__(self)
124 requests.RequestsMixin.__init__(self)
125 simple_presence.TheOneRingPresence.__init__(self, self.__options.ignoreDND)
126 simple_presence.SimplePresenceMixin.__init__(self, self)
127 presence.PresenceMixin.__init__(self, self)
129 self.__manager = weakref.proxy(manager)
130 self.__credentials = (
134 self.__callbackNumberParameter = encodedCallback
135 self.__channelManager = channel_manager.ChannelManager(self)
137 self.__cachePath = os.sep.join((constants._data_path_, "cache", self.username))
139 os.makedirs(self.__cachePath)
144 self.set_self_handle(handle.create_handle(self, 'connection'))
146 autogv.NewGVConversations(weakref.ref(self)),
147 autogv.RefreshVoicemail(weakref.ref(self)),
148 autogv.AutoDisconnect(weakref.ref(self)),
149 autogv.DelayEnableContactIntegration(constants._telepathy_implementation_name_),
152 _moduleLogger.info("Connection to the account %s created" % account)
153 self._timedDisconnect = autogv.TimedDisconnect(weakref.ref(self))
154 self._timedDisconnect.start()
158 return self.__manager
162 return self.__session
166 return self.__options
170 return self.__credentials[0]
173 def callbackNumberParameter(self):
174 return self.__callbackNumberParameter
176 def get_handle_by_name(self, handleType, handleName):
177 requestedHandleName = handleName.encode('utf-8')
179 # We need to return an existing or create a new handle. Unfortunately
180 # handle init's take care of normalizing the handle name. So we have
181 # to create a new handle regardless and burn some handle id's and burn
182 # some extra memory of creating objects we throw away if the handle
184 if handleType == telepathy.HANDLE_TYPE_CONTACT:
185 h = handle.create_handle(self, 'contact', requestedHandleName)
186 elif handleType == telepathy.HANDLE_TYPE_LIST:
187 # Support only server side (immutable) lists
188 h = handle.create_handle(self, 'list', requestedHandleName)
190 raise telepathy.errors.NotAvailable('Handle type unsupported %d' % handleType)
192 for candidate in self._handles.itervalues():
193 if candidate.get_name() == h.get_name():
195 _moduleLogger.debug("Re-used handle for %s, I hoped this helped" % handleName)
200 def log_to_user(self, component, message):
201 for logger in self._loggers:
202 logger.log_message(component, message)
204 def add_logger(self, logger):
205 self._loggers.append(logger)
207 def remove_logger(self, logger):
208 self._loggers.remove(logger)
211 def _channel_manager(self):
212 return self.__channelManager
214 @misc_utils.log_exception(_moduleLogger)
217 For org.freedesktop.telepathy.Connection
219 if self._status != telepathy.CONNECTION_STATUS_DISCONNECTED:
220 _moduleLogger.info("Attempting connect when not disconnected")
222 _moduleLogger.info("Connecting...")
224 telepathy.CONNECTION_STATUS_CONNECTING,
225 telepathy.CONNECTION_STATUS_REASON_REQUESTED
227 self._timedDisconnect.stop()
229 self.__credentials[0],
230 self.__credentials[1],
232 self._on_login_error,
235 @misc_utils.log_exception(_moduleLogger)
236 def _on_login(self, *args):
237 _moduleLogger.info("Connected, setting up...")
239 self.__session.load(self.__cachePath)
241 for plumber in self._plumbing:
243 if not self.__callbackNumberParameter:
244 callback = gvoice.backend.get_sane_callback(
247 self.__callbackNumberParameter = misc_utils.normalize_number(callback)
248 self.session.backend.set_callback_number(self.__callbackNumberParameter)
250 subscribeHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "subscribe")
251 subscribeProps = self.generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, subscribeHandle, False)
252 self.__channelManager.channel_for_props(subscribeProps, signal=True)
253 publishHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "publish")
254 publishProps = self.generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, publishHandle, False)
255 self.__channelManager.channel_for_props(publishProps, signal=True)
257 _moduleLogger.exception("Setup failed")
258 self.disconnect(telepathy.CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED)
261 _moduleLogger.info("Connected and set up")
263 telepathy.CONNECTION_STATUS_CONNECTED,
264 telepathy.CONNECTION_STATUS_REASON_REQUESTED
267 @misc_utils.log_exception(_moduleLogger)
268 def _on_login_error(self, error):
269 _moduleLogger.error(error)
270 if isinstance(error, StopIteration):
272 elif isinstance(error, gvoice.backend.NetworkError):
273 self.disconnect(telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR)
275 self.disconnect(telepathy.CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED)
277 @misc_utils.log_exception(_moduleLogger)
278 def Disconnect(self):
280 For org.freedesktop.telepathy.Connection
282 _moduleLogger.info("Kicking off disconnect")
283 self.disconnect(telepathy.CONNECTION_STATUS_REASON_REQUESTED)
285 @misc_utils.log_exception(_moduleLogger)
286 def RequestChannel(self, type, handleType, handleId, suppressHandler):
288 For org.freedesktop.telepathy.Connection
290 @param type DBus interface name for base channel type
291 @param handleId represents a contact, list, etc according to handleType
293 @returns DBus object path for the channel created or retrieved
295 self.check_connected()
296 self.check_handle(handleType, handleId)
298 h = self.get_handle_by_id(handleType, handleId) if handleId != 0 else None
299 props = self.generate_props(type, h, suppressHandler)
300 self._validate_handle(props)
302 chan = self.__channelManager.channel_for_props(props, signal=True)
303 path = chan._object_path
304 _moduleLogger.info("RequestChannel Object Path (%s): %s" % (type.rsplit(".", 1)[-1], path))
307 def generate_props(self, channelType, handleObj, suppressHandler, initiatorHandle=None):
308 targetHandle = 0 if handleObj is None else handleObj.get_id()
309 targetHandleType = telepathy.HANDLE_TYPE_NONE if handleObj is None else handleObj.get_type()
311 telepathy.CHANNEL_INTERFACE + '.ChannelType': channelType,
312 telepathy.CHANNEL_INTERFACE + '.TargetHandle': targetHandle,
313 telepathy.CHANNEL_INTERFACE + '.TargetHandleType': targetHandleType,
314 telepathy.CHANNEL_INTERFACE + '.Requested': suppressHandler
317 if initiatorHandle is not None:
318 props[telepathy.CHANNEL_INTERFACE + '.InitiatorHandle'] = initiatorHandle.id
322 def disconnect(self, reason):
323 _moduleLogger.info("Disconnecting")
325 self._timedDisconnect.stop()
327 # Not having the disconnect first can cause weird behavior with clients
328 # including not being able to reconnect or even crashing
330 telepathy.CONNECTION_STATUS_DISCONNECTED,
334 for plumber in self._plumbing:
337 self.__channelManager.close()
338 self.manager.disconnected(self)
340 self.session.save(self.__cachePath)
341 self.session.shutdown()
344 # In case one of the above items takes too long (which it should never
345 # do), we leave the starting of the shutdown-on-idle counter to the
347 self.manager.disconnect_completed()
349 _moduleLogger.info("Disconnected")