9 import util.misc as misc_utils
20 import simple_presence
23 import channel_manager
26 _moduleLogger = logging.getLogger(__name__)
29 class TheOneRingOptions(object):
33 assert gvoice.session.Session._DEFAULTS["contacts"][1] == "hours"
34 contactsPollPeriodInHours = gvoice.session.Session._DEFAULTS["contacts"][0]
36 assert gvoice.session.Session._DEFAULTS["voicemail"][1] == "minutes"
37 voicemailPollPeriodInMinutes = gvoice.session.Session._DEFAULTS["voicemail"][0]
39 assert gvoice.session.Session._DEFAULTS["texts"][1] == "minutes"
40 textsPollPeriodInMinutes = gvoice.session.Session._DEFAULTS["texts"][0]
42 def __init__(self, parameters = None):
43 if parameters is None:
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']
51 class TheOneRingConnection(
53 aliasing.AliasingMixin,
55 capabilities.CapabilitiesMixin,
56 contacts.ContactsMixin,
57 presence.PresenceMixin,
58 requests.RequestsMixin,
59 simple_presence.SimplePresenceMixin,
62 # overiding base class variable
63 _mandatory_parameters = {
67 # overiding base class variable
68 _optional_parameters = {
70 'use-gv-contacts': 'b',
71 'contacts-poll-period-in-hours': 'i',
72 'voicemail-poll-period-in-minutes': 'i',
73 'texts-poll-period-in-minutes': 'i',
75 _parameter_defaults = {
77 'use-gv-contacts': TheOneRingOptions.useGVContacts,
78 'contacts-poll-period-in-hours': TheOneRingOptions.contactsPollPeriodInHours,
79 'voicemail-poll-period-in-minutes': TheOneRingOptions.voicemailPollPeriodInMinutes,
80 'texts-poll-period-in-minutes': TheOneRingOptions.textsPollPeriodInMinutes,
82 _secret_parameters = set((
86 @misc_utils.log_exception(_moduleLogger)
87 def __init__(self, manager, parameters):
88 self.check_parameters(parameters)
89 account = unicode(parameters['account'])
90 encodedAccount = parameters['account'].encode('utf-8')
91 encodedPassword = parameters['password'].encode('utf-8')
92 encodedCallback = misc_utils.normalize_number(parameters['forward'].encode('utf-8'))
93 if encodedCallback and not misc_utils.is_valid_number(encodedCallback):
94 raise telepathy.errors.InvalidArgument("Invalid forwarding number")
96 # Connection init must come first
97 self.__options = TheOneRingOptions(parameters)
98 self.__session = gvoice.session.Session(
101 "contacts": (self.__options.contactsPollPeriodInHours, "hours"),
102 "voicemail": (self.__options.voicemailPollPeriodInMinutes, "minutes"),
103 "texts": (self.__options.textsPollPeriodInMinutes, "minutes"),
106 tp.Connection.__init__(
108 constants._telepathy_protocol_name_,
110 constants._telepathy_implementation_name_
112 aliasing.AliasingMixin.__init__(self)
113 avatars.AvatarsMixin.__init__(self)
114 capabilities.CapabilitiesMixin.__init__(self)
115 contacts.ContactsMixin.__init__(self)
116 presence.PresenceMixin.__init__(self)
117 requests.RequestsMixin.__init__(self)
118 simple_presence.SimplePresenceMixin.__init__(self)
120 self.__manager = weakref.proxy(manager)
121 self.__credentials = (
125 self.__callbackNumberParameter = encodedCallback
126 self.__channelManager = channel_manager.ChannelManager(self)
128 self.__cachePath = os.sep.join((constants._data_path_, "cache", self.username))
130 os.makedirs(self.__cachePath)
135 self.set_self_handle(handle.create_handle(self, 'connection'))
137 autogv.NewGVConversations(weakref.ref(self)),
138 autogv.RefreshVoicemail(weakref.ref(self)),
139 autogv.AutoDisconnect(weakref.ref(self)),
140 autogv.DelayEnableContactIntegration(constants._telepathy_implementation_name_),
143 _moduleLogger.info("Connection to the account %s created" % account)
144 self._timedDisconnect = autogv.TimedDisconnect(weakref.ref(self))
145 self._timedDisconnect.start()
149 return self.__manager
153 return self.__session
157 return self.__options
161 return self.__credentials[0]
164 def callbackNumberParameter(self):
165 return self.__callbackNumberParameter
167 def get_handle_by_name(self, handleType, handleName):
168 requestedHandleName = handleName.encode('utf-8')
170 # We need to return an existing or create a new handle. Unfortunately
171 # handle init's take care of normalizing the handle name. So we have
172 # to create a new handle regardless and burn some handle id's and burn
173 # some extra memory of creating objects we throw away if the handle
175 if handleType == telepathy.HANDLE_TYPE_CONTACT:
176 h = handle.create_handle(self, 'contact', requestedHandleName)
177 elif handleType == telepathy.HANDLE_TYPE_LIST:
178 # Support only server side (immutable) lists
179 h = handle.create_handle(self, 'list', requestedHandleName)
181 raise telepathy.errors.NotAvailable('Handle type unsupported %d' % handleType)
183 for candidate in self._handles.itervalues():
184 if candidate.get_name() == h.get_name():
186 _moduleLogger.debug("Re-used handle for %s, I hoped this helped" % handleName)
192 def _channel_manager(self):
193 return self.__channelManager
195 @misc_utils.log_exception(_moduleLogger)
198 For org.freedesktop.telepathy.Connection
200 if self._status != telepathy.CONNECTION_STATUS_DISCONNECTED:
201 _moduleLogger.info("Attempting connect when not disconnected")
203 _moduleLogger.info("Connecting...")
205 telepathy.CONNECTION_STATUS_CONNECTING,
206 telepathy.CONNECTION_STATUS_REASON_REQUESTED
208 self._timedDisconnect.stop()
210 self.__credentials[0],
211 self.__credentials[1],
213 self._on_login_error,
216 @misc_utils.log_exception(_moduleLogger)
217 def _on_login(self, *args):
218 _moduleLogger.info("Connected, setting up...")
220 self.__session.load(self.__cachePath)
222 for plumber in self._plumbing:
224 if not self.__callbackNumberParameter:
225 callback = gvoice.backend.get_sane_callback(
228 self.__callbackNumberParameter = misc_utils.normalize_number(callback)
229 self.session.backend.set_callback_number(self.__callbackNumberParameter)
231 subscribeHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "subscribe")
232 subscribeProps = self.generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, subscribeHandle, False)
233 self.__channelManager.channel_for_props(subscribeProps, signal=True)
234 publishHandle = self.get_handle_by_name(telepathy.HANDLE_TYPE_LIST, "publish")
235 publishProps = self.generate_props(telepathy.CHANNEL_TYPE_CONTACT_LIST, publishHandle, False)
236 self.__channelManager.channel_for_props(publishProps, signal=True)
238 _moduleLogger.exception("Setup failed")
239 self.disconnect(telepathy.CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED)
242 _moduleLogger.info("Connected and set up")
244 telepathy.CONNECTION_STATUS_CONNECTED,
245 telepathy.CONNECTION_STATUS_REASON_REQUESTED
248 @misc_utils.log_exception(_moduleLogger)
249 def _on_login_error(self, error):
250 _moduleLogger.error(error)
251 if isinstance(error, StopIteration):
253 elif isinstance(error, gvoice.backend.NetworkError):
254 self.disconnect(telepathy.CONNECTION_STATUS_REASON_NETWORK_ERROR)
256 self.disconnect(telepathy.CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED)
258 @misc_utils.log_exception(_moduleLogger)
259 def Disconnect(self):
261 For org.freedesktop.telepathy.Connection
263 _moduleLogger.info("Kicking off disconnect")
264 self.disconnect(telepathy.CONNECTION_STATUS_REASON_REQUESTED)
266 @misc_utils.log_exception(_moduleLogger)
267 def RequestChannel(self, type, handleType, handleId, suppressHandler):
269 For org.freedesktop.telepathy.Connection
271 @param type DBus interface name for base channel type
272 @param handleId represents a contact, list, etc according to handleType
274 @returns DBus object path for the channel created or retrieved
276 self.check_connected()
277 self.check_handle(handleType, handleId)
279 h = self.get_handle_by_id(handleType, handleId) if handleId != 0 else None
280 props = self.generate_props(type, h, suppressHandler)
281 self._validate_handle(props)
283 chan = self.__channelManager.channel_for_props(props, signal=True)
284 path = chan._object_path
285 _moduleLogger.info("RequestChannel Object Path (%s): %s" % (type.rsplit(".", 1)[-1], path))
288 def generate_props(self, channelType, handleObj, suppressHandler, initiatorHandle=None):
289 targetHandle = 0 if handleObj is None else handleObj.get_id()
290 targetHandleType = telepathy.HANDLE_TYPE_NONE if handleObj is None else handleObj.get_type()
292 telepathy.CHANNEL_INTERFACE + '.ChannelType': channelType,
293 telepathy.CHANNEL_INTERFACE + '.TargetHandle': targetHandle,
294 telepathy.CHANNEL_INTERFACE + '.TargetHandleType': targetHandleType,
295 telepathy.CHANNEL_INTERFACE + '.Requested': suppressHandler
298 if initiatorHandle is not None:
299 props[telepathy.CHANNEL_INTERFACE + '.InitiatorHandle'] = initiatorHandle.id
303 def disconnect(self, reason):
304 _moduleLogger.info("Disconnecting")
306 self._timedDisconnect.stop()
308 # Not having the disconnect first can cause weird behavior with clients
309 # including not being able to reconnect or even crashing
311 telepathy.CONNECTION_STATUS_DISCONNECTED,
315 for plumber in self._plumbing:
318 self.__channelManager.close()
319 self.manager.disconnected(self)
321 self.session.save(self.__cachePath)
322 self.session.logout()
325 # In case one of the above items takes too long (which it should never
326 # do), we leave the starting of the shutdown-on-idle counter to the
328 self.manager.disconnect_completed()
330 _moduleLogger.info("Disconnected")