## common/zeroconf/connection_zeroconf.py ## ## Contributors for this file: ## - Yann Leboulanger ## - Nikos Kouremenos ## - Dimitur Kirov ## - Travis Shirk ## - Stefan Bethge ## ## Copyright (C) 2003-2010 Yann Leboulanger ## Copyright (C) 2003-2004 Vincent Hanquez ## Copyright (C) 2006 Nikos Kouremenos ## Dimitur Kirov ## Travis Shirk ## Norman Rasmussen ## Stefan Bethge ## ## This file is part of Gajim. ## ## Gajim is free software; you can redistribute it and/or modify ## it under the terms of the GNU General Public License as published ## by the Free Software Foundation; version 3 only. ## ## Gajim is distributed in the hope that it will be useful, ## but WITHOUT ANY WARRANTY; without even the implied warranty of ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ## GNU General Public License for more details. ## ## You should have received a copy of the GNU General Public License ## along with Gajim. If not, see . ## import os import random random.seed() import signal if os.name != 'nt': signal.signal(signal.SIGPIPE, signal.SIG_DFL) import getpass import gobject from common.connection import CommonConnection from common import gajim from common import GnuPG from common.zeroconf import client_zeroconf from common.zeroconf import zeroconf from connection_handlers_zeroconf import * class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf): def __init__(self, name): ConnectionHandlersZeroconf.__init__(self) # system username self.username = None self.server_resource = '' # zeroconf has no resource, fake an empty one self.call_resolve_timeout = False # we don't need a password, but must be non-empty self.password = 'zeroconf' self.autoconnect = False CommonConnection.__init__(self, name) self.is_zeroconf = True def get_config_values_or_default(self): """ Get name, host, port from config, or create zeroconf account with default values """ if not gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name'): gajim.log.debug('Creating zeroconf account') gajim.config.add_per('accounts', gajim.ZEROCONF_ACC_NAME) gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'autoconnect', True) gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'no_log_for', '') gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'password', 'zeroconf') gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status', True) gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port', 5298) gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'is_zeroconf', True) gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'use_ft_proxies', False) #XXX make sure host is US-ASCII self.host = unicode(socket.gethostname()) gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'hostname', self.host) self.port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port') self.autoconnect = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'autoconnect') self.sync_with_global_status = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'sync_with_global_status') self.first = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_first_name') self.last = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_last_name') self.jabber_id = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_jabber_id') self.email = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_email') if not self.username: self.username = unicode(getpass.getuser()) gajim.config.set_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name', self.username) else: self.username = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'name') # END __init__ def check_jid(self, jid): return jid def _reconnect(self): # Do not try to reco while we are already trying self.time_to_reconnect = None gajim.log.debug('reconnect') self.disconnect() self.change_status(self.old_show, self.status) def disable_account(self): self.disconnect() def _on_resolve_timeout(self): if self.connected: self.connection.resolve_all() diffs = self.roster.getDiffs() for key in diffs: self.roster.setItem(key) self.dispatch('ROSTER_INFO', (key, self.roster.getName(key), 'both', 'no', self.roster.getGroups(key))) self.dispatch('NOTIFY', (key, self.roster.getStatus(key), self.roster.getMessage(key), 'local', 0, None, 0, None)) #XXX open chat windows don't get refreshed (full name), add that return self.call_resolve_timeout # callbacks called from zeroconf def _on_new_service(self, jid): self.roster.setItem(jid) self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid))) self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0, None)) def _on_remove_service(self, jid): self.roster.delItem(jid) # 'NOTIFY' (account, (jid, status, status message, resource, priority, # keyID, timestamp, contact_nickname)) self.dispatch('NOTIFY', (jid, 'offline', '', 'local', 0, None, 0, None)) def _disconnectedReconnCB(self): """ Called when we are disconnected. Comes from network manager for example we don't try to reconnect, network manager will tell us when we can """ if gajim.account_is_connected(self.name): # we cannot change our status to offline or connecting # after we auth to server self.old_show = STATUS_LIST[self.connected] self.connected = 0 self.dispatch('STATUS', 'offline') # random number to show we wait network manager to send us a reconenct self.time_to_reconnect = 5 self.on_purpose = False def _on_name_conflictCB(self, alt_name): self.disconnect() self.dispatch('STATUS', 'offline') self.dispatch('ZC_NAME_CONFLICT', alt_name) def _on_error(self, message): self.dispatch('ERROR', (_('Avahi error'), _('%s\nLink-local messaging might not work properly.') % message)) def connect(self, show='online', msg=''): self.get_config_values_or_default() if not self.connection: self.connection = client_zeroconf.ClientZeroconf(self) if not zeroconf.test_zeroconf(): self.dispatch('STATUS', 'offline') self.status = 'offline' self.dispatch('CONNECTION_LOST', (_('Could not connect to "%s"') % self.name, _('Please check if Avahi or Bonjour is installed.'))) self.disconnect() return result = self.connection.connect(show, msg) if not result: self.dispatch('STATUS', 'offline') self.status = 'offline' if result is False: self.dispatch('CONNECTION_LOST', (_('Could not start local service'), _('Unable to bind to port %d.' % self.port))) else: # result is None self.dispatch('CONNECTION_LOST', (_('Could not start local service'), _('Please check if avahi-daemon is running.'))) self.disconnect() return else: self.connection.announce() self.roster = self.connection.getRoster() self.dispatch('ROSTER', self.roster) # display contacts already detected and resolved for jid in self.roster.keys(): self.dispatch('ROSTER_INFO', (jid, self.roster.getName(jid), 'both', 'no', self.roster.getGroups(jid))) self.dispatch('NOTIFY', (jid, self.roster.getStatus(jid), self.roster.getMessage(jid), 'local', 0, None, 0, None)) self.connected = STATUS_LIST.index(show) # refresh all contacts data every five seconds self.call_resolve_timeout = True gobject.timeout_add_seconds(5, self._on_resolve_timeout) return True def disconnect(self, on_purpose=False): self.connected = 0 self.time_to_reconnect = None if self.connection: self.connection.disconnect() self.connection = None # stop calling the timeout self.call_resolve_timeout = False def reannounce(self): if self.connected: txt = {} txt['1st'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_first_name') txt['last'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_last_name') txt['jid'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_jabber_id') txt['email'] = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'zeroconf_email') self.connection.reannounce(txt) def update_details(self): if self.connection: port = gajim.config.get_per('accounts', gajim.ZEROCONF_ACC_NAME, 'custom_port') if port != self.port: self.port = port last_msg = self.connection.last_msg self.disconnect() if not self.connect(self.status, last_msg): return if self.status != 'invisible': self.connection.announce() else: self.reannounce() def connect_and_init(self, show, msg, sign_msg): # to check for errors from zeroconf check = True if not self.connect(show, msg): return if show != 'invisible': check = self.connection.announce() else: self.connected = STATUS_LIST.index(show) self.dispatch('SIGNED_IN', ()) # stay offline when zeroconf does something wrong if check: self.dispatch('STATUS', show) else: # show notification that avahi or system bus is down self.dispatch('STATUS', 'offline') self.status = 'offline' self.dispatch('CONNECTION_LOST', (_('Could not change status of account "%s"') % self.name, _('Please check if avahi-daemon is running.'))) def _change_to_invisible(self, msg): if self.connection.remove_announce(): self.dispatch('STATUS', 'invisible') else: # show notification that avahi or system bus is down self.dispatch('STATUS', 'offline') self.status = 'offline' self.dispatch('CONNECTION_LOST', (_('Could not change status of account "%s"') % self.name, _('Please check if avahi-daemon is running.'))) def _change_from_invisible(self): self.connection.announce() def _update_status(self, show, msg): if self.connection.set_show_msg(show, msg): self.dispatch('STATUS', show) else: # show notification that avahi or system bus is down self.dispatch('STATUS', 'offline') self.status = 'offline' self.dispatch('CONNECTION_LOST', (_('Could not change status of account "%s"') % self.name, _('Please check if avahi-daemon is running.'))) def send_message(self, jid, msg, keyID, type_='chat', subject='', chatstate=None, msg_id=None, composing_xep=None, resource=None, user_nick=None, xhtml=None, label=None, session=None, forward_from=None, form_node=None, original_message=None, delayed=None, callback=None, callback_args=[], now=True): def on_send_ok(msg_id): self.dispatch('MSGSENT', (jid, msg, keyID)) if callback: callback(msg_id, *callback_args) self.log_message(jid, msg, forward_from, session, original_message, subject, type_) def on_send_not_ok(reason): reason += ' ' + _('Your message could not be sent.') self.dispatch('MSGERROR', [jid, -1, reason, None, None, session]) def cb(jid, msg, keyID, forward_from, session, original_message, subject, type_, msg_iq): ret = self.connection.send(msg_iq, msg is not None, on_ok=on_send_ok, on_not_ok=on_send_not_ok) if ret == -1: # Contact Offline self.dispatch('MSGERROR', [jid, -1, _('Contact is offline. Your ' 'message could not be sent.'), None, None, session]) self._prepare_message(jid, msg, keyID, type_=type_, subject=subject, chatstate=chatstate, msg_id=msg_id, composing_xep=composing_xep, resource=resource, user_nick=user_nick, xhtml=xhtml, session=session, forward_from=forward_from, form_node=form_node, original_message=original_message, delayed=delayed, callback=cb) def send_stanza(self, stanza): # send a stanza untouched if not self.connection: return if not isinstance(stanza, common.xmpp.Node): stanza = common.xmpp.Protocol(node=stanza) self.connection.send(stanza) def _event_dispatcher(self, realm, event, data): CommonConnection._event_dispatcher(self, realm, event, data) if realm == '': if event == common.xmpp.transports_nb.DATA_ERROR: thread_id = data[1] frm = unicode(data[0]) session = self.get_or_create_session(frm, thread_id) self.dispatch('MSGERROR', [frm, -1, _('Connection to host could not be established: Timeout while ' 'sending data.'), None, None, session]) # END ConnectionZeroconf