diff --git a/src/common/zeroconf/client_zeroconf.py b/src/common/zeroconf/client_zeroconf.py index 66d5a28d0..c3de88a38 100644 --- a/src/common/zeroconf/client_zeroconf.py +++ b/src/common/zeroconf/client_zeroconf.py @@ -523,13 +523,6 @@ class ClientZeroconf: self.listener = None self.number_of_awaiting_messages = {} - def test_avahi(self): - try: - import avahi - except ImportError: - return False - return True - def connect(self, show, msg): self.port = self.start_listener(self.caller.port) if not self.port: diff --git a/src/common/zeroconf/connection_zeroconf.py b/src/common/zeroconf/connection_zeroconf.py index ce89fedcd..b218eab41 100644 --- a/src/common/zeroconf/connection_zeroconf.py +++ b/src/common/zeroconf/connection_zeroconf.py @@ -41,6 +41,7 @@ import gobject from common import gajim from common import GnuPG from common.zeroconf import client_zeroconf +from common.zeroconf import zeroconf from connection_handlers_zeroconf import * USE_GPG = GnuPG.USE_GPG @@ -228,7 +229,7 @@ class ConnectionZeroconf(ConnectionHandlersZeroconf): self.get_config_values_or_default() if not self.connection: self.connection = client_zeroconf.ClientZeroconf(self) - if not self.connection.test_avahi(): + if not zeroconf.test_zeroconf(): self.dispatch('STATUS', 'offline') self.status = 'offline' self.dispatch('CONNECTION_LOST', diff --git a/src/common/zeroconf/zeroconf.py b/src/common/zeroconf/zeroconf.py index 6a65e622d..e34599da2 100755 --- a/src/common/zeroconf/zeroconf.py +++ b/src/common/zeroconf/zeroconf.py @@ -12,404 +12,29 @@ ## GNU General Public License for more details. ## -from common import gajim - -try: - import dbus.glib -except ImportError, e: - pass - - C_NAME, C_DOMAIN, C_INTERFACE, C_PROTOCOL, C_HOST, \ C_ADDRESS, C_PORT, C_BARE_NAME, C_TXT = range(9) -class Zeroconf: - def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, - disconnected_CB, error_CB, name, host, port): - self.avahi = None - self.domain = None # specific domain to browse - self.stype = '_presence._tcp' - self.port = port # listening port that gets announced - self.username = name - self.host = host - self.txt = {} # service data - - #XXX these CBs should be set to None when we destroy the object - # (go offline), because they create a circular reference - self.new_serviceCB = new_serviceCB - self.remove_serviceCB = remove_serviceCB - self.name_conflictCB = name_conflictCB - self.disconnected_CB = disconnected_CB - self.error_CB = error_CB - - self.service_browser = None - self.domain_browser = None - self.bus = None - self.server = None - self.contacts = {} # all current local contacts with data - self.entrygroup = None - self.connected = False - self.announced = False - self.invalid_self_contact = {} +def test_avahi(): + try: + import avahi + except ImportError: + return False + return True +def test_bonjour(): + try: + import pybonjour + except ImportError: + return False + return True - ## handlers for dbus callbacks - def entrygroup_commit_error_CB(self, err): - # left blank for possible later usage - pass - - def error_callback1(self, err): - gajim.log.debug('Error while resolving: ' + str(err)) - - def error_callback(self, err): - gajim.log.debug(str(err)) - # timeouts are non-critical - if str(err) != 'Timeout reached': - self.disconnect() - self.disconnected_CB() +def test_zeroconf(): + return test_avahi() or test_bonjour() - def new_service_callback(self, interface, protocol, name, stype, domain, flags): - gajim.log.debug('Found service %s in domain %s on %i.%i.' % (name, domain, interface, protocol)) - if not self.connected: - return - - # synchronous resolving - self.server.ResolveService( int(interface), int(protocol), name, stype, \ - domain, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), \ - reply_handler=self.service_resolved_callback, error_handler=self.error_callback1) - - def remove_service_callback(self, interface, protocol, name, stype, domain, flags): - gajim.log.debug('Service %s in domain %s on %i.%i disappeared.' % (name, domain, interface, protocol)) - if not self.connected: - return - if name != self.name: - for key in self.contacts.keys(): - if self.contacts[key][C_BARE_NAME] == name: - del self.contacts[key] - self.remove_serviceCB(key) - return - - def new_service_type(self, interface, protocol, stype, domain, flags): - # Are we already browsing this domain for this type? - if self.service_browser: - return - - object_path = self.server.ServiceBrowserNew(interface, protocol, \ - stype, domain, dbus.UInt32(0)) - - self.service_browser = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, \ - object_path) , self.avahi.DBUS_INTERFACE_SERVICE_BROWSER) - self.service_browser.connect_to_signal('ItemNew', self.new_service_callback) - self.service_browser.connect_to_signal('ItemRemove', self.remove_service_callback) - self.service_browser.connect_to_signal('Failure', self.error_callback) - - def new_domain_callback(self,interface, protocol, domain, flags): - if domain != "local": - self.browse_domain(interface, protocol, domain) - - def txt_array_to_dict(self, txt_array): - txt_dict = {} - for els in txt_array: - key, val = '', None - for c in els: - #FIXME: remove when outdated, this is for avahi < 0.6.14 - if c < 0 or c > 255: - c = '.' - else: - c = chr(c) - if val is None: - if c == '=': - val = '' - else: - key += c - else: - val += c - if val is None: # missing '=' - val = '' - txt_dict[key] = val.decode('utf-8') - return txt_dict - - def service_resolved_callback(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags): - gajim.log.debug('Service data for service %s in domain %s on %i.%i:' - % (name, domain, interface, protocol)) - gajim.log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address, port, - self.txt_array_to_dict(txt))) - if not self.connected: - return - bare_name = name - if name.find('@') == -1: - name = name + '@' + name - - # we don't want to see ourselves in the list - if name != self.name: - self.contacts[name] = (name, domain, interface, protocol, host, address, port, - bare_name, txt) - self.new_serviceCB(name) - else: - # remember data - # In case this is not our own record but of another - # gajim instance on the same machine, - # it will be used when we get a new name. - self.invalid_self_contact[name] = (name, domain, interface, protocol, host, address, port, bare_name, txt) - - - # different handler when resolving all contacts - def service_resolved_all_callback(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags): - if not self.connected: - return - bare_name = name - if name.find('@') == -1: - name = name + '@' + name - self.contacts[name] = (name, domain, interface, protocol, host, address, port, bare_name, txt) - - def service_added_callback(self): - gajim.log.debug('Service successfully added') - - def service_committed_callback(self): - gajim.log.debug('Service successfully committed') - - def service_updated_callback(self): - gajim.log.debug('Service successfully updated') - - def service_add_fail_callback(self, err): - gajim.log.debug('Error while adding service. %s' % str(err)) - if str(err) == 'Local name collision': - alternative_name = self.server.GetAlternativeServiceName(self.username) - self.name_conflictCB(alternative_name) - return - self.error_CB(_('Error while adding service. %s') % str(err)) - self.disconnect() - - def server_state_changed_callback(self, state, error): - if state == self.avahi.SERVER_RUNNING: - self.create_service() - elif state == self.avahi.SERVER_COLLISION: - self.entrygroup.Reset() - elif state == self.avahi.CLIENT_FAILURE: - # does it ever go here? - gajim.log.debug('CLIENT FAILURE') - - def entrygroup_state_changed_callback(self, state, error): - # the name is already present, so recreate - if state == self.avahi.ENTRY_GROUP_COLLISION: - self.service_add_fail_callback('Local name collision') - elif state == self.avahi.ENTRY_GROUP_FAILURE: - gajim.log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached(that' - ' should not happen)') - - # make zeroconf-valid names - def replace_show(self, show): - if show in ['chat', 'online', '']: - return 'avail' - elif show == 'xa': - return 'away' - return show - - def avahi_txt(self): - utf8_dict = {} - for key in self.txt: - val = self.txt[key] - if isinstance(val, unicode): - utf8_dict[key] = val.encode('utf-8') - else: - utf8_dict[key] = val - return self.avahi.dict_to_txt_array(utf8_dict) - - def create_service(self): - try: - if not self.entrygroup: - # create an EntryGroup for publishing - self.entrygroup = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, self.server.EntryGroupNew()), self.avahi.DBUS_INTERFACE_ENTRY_GROUP) - self.entrygroup.connect_to_signal('StateChanged', self.entrygroup_state_changed_callback) - - txt = {} - - #remove empty keys - for key,val in self.txt.iteritems(): - if val: - txt[key] = val - - txt['port.p2pj'] = self.port - txt['version'] = 1 - txt['txtvers'] = 1 - - # replace gajim's show messages with compatible ones - if self.txt.has_key('status'): - txt['status'] = self.replace_show(self.txt['status']) - else: - txt['status'] = 'avail' - - self.txt = txt - gajim.log.debug('Publishing service %s of type %s' % (self.name, self.stype)) - self.entrygroup.AddService(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', '', self.port, self.avahi_txt(), reply_handler=self.service_added_callback, error_handler=self.service_add_fail_callback) - - self.entrygroup.Commit(reply_handler=self.service_committed_callback, - error_handler=self.entrygroup_commit_error_CB) - - return True - - except dbus.DBusException, e: - gajim.log.debug(str(e)) - return False - - def announce(self): - if not self.connected: - return False - - state = self.server.GetState() - if state == self.avahi.SERVER_RUNNING: - self.create_service() - self.announced = True - return True - - def remove_announce(self): - if self.announced == False: - return False - try: - if self.entrygroup.GetState() != self.avahi.ENTRY_GROUP_FAILURE: - self.entrygroup.Reset() - self.entrygroup.Free() - # .Free() has mem leaks - obj = self.entrygroup._obj - obj._bus = None - self.entrygroup._obj = None - self.entrygroup = None - self.announced = False - - return True - else: - return False - except dbus.DBusException, e: - gajim.log.debug("Can't remove service. That should not happen") - - def browse_domain(self, interface, protocol, domain): - self.new_service_type(interface, protocol, self.stype, domain, '') - - def avahi_dbus_connect_cb(self, a, connect, disconnect): - if connect != "": - gajim.log.debug('Lost connection to avahi-daemon') - self.disconnect() - if self.disconnected_CB: - self.disconnected_CB() - else: - gajim.log.debug('We are connected to avahi-daemon') - - # connect to dbus - def connect_dbus(self): - try: - import dbus - except ImportError: - gajim.log.debug('Error: python-dbus needs to be installed. No zeroconf support.') - return False - if self.bus: - return True - try: - self.bus = dbus.SystemBus() - self.bus.add_signal_receiver(self.avahi_dbus_connect_cb, - "NameOwnerChanged", "org.freedesktop.DBus", - arg0="org.freedesktop.Avahi") - except Exception, e: - # System bus is not present - self.bus = None - gajim.log.debug(str(e)) - return False - else: - return True - - # connect to avahi - def connect_avahi(self): - if not self.connect_dbus(): - return False - try: - import avahi - self.avahi = avahi - except ImportError: - gajim.log.debug('Error: python-avahi needs to be installed. No zeroconf support.') - return False - - if self.server: - return True - try: - self.server = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, \ - self.avahi.DBUS_PATH_SERVER), self.avahi.DBUS_INTERFACE_SERVER) - self.server.connect_to_signal('StateChanged', - self.server_state_changed_callback) - except Exception, e: - # Avahi service is not present - self.server = None - gajim.log.debug(str(e)) - return False - else: - return True - - def connect(self): - self.name = self.username + '@' + self.host # service name - if not self.connect_avahi(): - return False - - self.connected = True - # start browsing - if self.domain is None: - # Explicitly browse .local - self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, "local") - - # Browse for other browsable domains - self.domain_browser = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, \ - self.server.DomainBrowserNew(self.avahi.IF_UNSPEC, \ - self.avahi.PROTO_UNSPEC, '', self.avahi.DOMAIN_BROWSER_BROWSE,\ - dbus.UInt32(0))), self.avahi.DBUS_INTERFACE_DOMAIN_BROWSER) - self.domain_browser.connect_to_signal('ItemNew', self.new_domain_callback) - self.domain_browser.connect_to_signal('Failure', self.error_callback) - else: - self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, self.domain) - - return True - - def disconnect(self): - if self.connected: - self.connected = False - if self.service_browser: - self.service_browser.Free() - self.service_browser._obj._bus = None - self.service_browser._obj = None - if self.domain_browser: - self.domain_browser.Free() - self.domain_browser._obj._bus = None - self.domain_browser._obj = None - self.remove_announce() - self.server._obj._bus = None - self.server._obj = None - self.server = None - self.service_browser = None - self.domain_browser = None - - # refresh txt data of all contacts manually (no callback available) - def resolve_all(self): - if not self.connected: - return - for val in self.contacts.values(): - self.server.ResolveService(int(val[C_INTERFACE]), int(val[C_PROTOCOL]), val[C_BARE_NAME], \ - self.stype, val[C_DOMAIN], self.avahi.PROTO_UNSPEC, dbus.UInt32(0),\ - reply_handler=self.service_resolved_all_callback, error_handler=self.error_callback) - - def get_contacts(self): - return self.contacts - - def get_contact(self, jid): - if not jid in self.contacts: - return None - return self.contacts[jid] - - def update_txt(self, show = None): - if show: - self.txt['status'] = self.replace_show(show) - - txt = self.avahi_txt() - if self.connected and self.entrygroup: - self.entrygroup.UpdateServiceTxt(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype,'', txt, reply_handler=self.service_updated_callback, error_handler=self.error_callback) - return True - else: - return False - - -# END Zeroconf +if test_avahi(): + from common.zeroconf import zeroconf_avahi + Zeroconf = zeroconf_avahi.Zeroconf +elif test_bonjour(): + from common.zeroconf import zeroconf_bonjour + Zeroconf = zeroconf_bonjour.Zeroconf diff --git a/src/common/zeroconf/zeroconf_avahi.py b/src/common/zeroconf/zeroconf_avahi.py new file mode 100755 index 000000000..6ad768826 --- /dev/null +++ b/src/common/zeroconf/zeroconf_avahi.py @@ -0,0 +1,415 @@ +## common/zeroconf/zeroconf.py +## +## Copyright (C) 2006 Stefan Bethge +## +## This program 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 2 only. +## +## This program 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. +## + +from common import gajim + +try: + import dbus.glib +except ImportError, e: + pass + +from common.zeroconf.zeroconf import C_BARE_NAME, C_INTERFACE, C_PROTOCOL, C_DOMAIN + +class Zeroconf: + def __init__(self, new_serviceCB, remove_serviceCB, name_conflictCB, + disconnected_CB, error_CB, name, host, port): + self.avahi = None + self.domain = None # specific domain to browse + self.stype = '_presence._tcp' + self.port = port # listening port that gets announced + self.username = name + self.host = host + self.txt = {} # service data + + #XXX these CBs should be set to None when we destroy the object + # (go offline), because they create a circular reference + self.new_serviceCB = new_serviceCB + self.remove_serviceCB = remove_serviceCB + self.name_conflictCB = name_conflictCB + self.disconnected_CB = disconnected_CB + self.error_CB = error_CB + + self.service_browser = None + self.domain_browser = None + self.bus = None + self.server = None + self.contacts = {} # all current local contacts with data + self.entrygroup = None + self.connected = False + self.announced = False + self.invalid_self_contact = {} + + + ## handlers for dbus callbacks + def entrygroup_commit_error_CB(self, err): + # left blank for possible later usage + pass + + def error_callback1(self, err): + gajim.log.debug('Error while resolving: ' + str(err)) + + def error_callback(self, err): + gajim.log.debug(str(err)) + # timeouts are non-critical + if str(err) != 'Timeout reached': + self.disconnect() + self.disconnected_CB() + + def new_service_callback(self, interface, protocol, name, stype, domain, flags): + gajim.log.debug('Found service %s in domain %s on %i.%i.' % (name, domain, interface, protocol)) + if not self.connected: + return + + # synchronous resolving + self.server.ResolveService( int(interface), int(protocol), name, stype, \ + domain, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), \ + reply_handler=self.service_resolved_callback, error_handler=self.error_callback1) + + def remove_service_callback(self, interface, protocol, name, stype, domain, flags): + gajim.log.debug('Service %s in domain %s on %i.%i disappeared.' % (name, domain, interface, protocol)) + if not self.connected: + return + if name != self.name: + for key in self.contacts.keys(): + if self.contacts[key][C_BARE_NAME] == name: + del self.contacts[key] + self.remove_serviceCB(key) + return + + def new_service_type(self, interface, protocol, stype, domain, flags): + # Are we already browsing this domain for this type? + if self.service_browser: + return + + object_path = self.server.ServiceBrowserNew(interface, protocol, \ + stype, domain, dbus.UInt32(0)) + + self.service_browser = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, \ + object_path) , self.avahi.DBUS_INTERFACE_SERVICE_BROWSER) + self.service_browser.connect_to_signal('ItemNew', self.new_service_callback) + self.service_browser.connect_to_signal('ItemRemove', self.remove_service_callback) + self.service_browser.connect_to_signal('Failure', self.error_callback) + + def new_domain_callback(self,interface, protocol, domain, flags): + if domain != "local": + self.browse_domain(interface, protocol, domain) + + def txt_array_to_dict(self, txt_array): + txt_dict = {} + for els in txt_array: + key, val = '', None + for c in els: + #FIXME: remove when outdated, this is for avahi < 0.6.14 + if c < 0 or c > 255: + c = '.' + else: + c = chr(c) + if val is None: + if c == '=': + val = '' + else: + key += c + else: + val += c + if val is None: # missing '=' + val = '' + txt_dict[key] = val.decode('utf-8') + return txt_dict + + def service_resolved_callback(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags): + gajim.log.debug('Service data for service %s in domain %s on %i.%i:' + % (name, domain, interface, protocol)) + gajim.log.debug('Host %s (%s), port %i, TXT data: %s' % (host, address, port, + self.txt_array_to_dict(txt))) + if not self.connected: + return + bare_name = name + if name.find('@') == -1: + name = name + '@' + name + + # we don't want to see ourselves in the list + if name != self.name: + self.contacts[name] = (name, domain, interface, protocol, host, address, port, + bare_name, txt) + self.new_serviceCB(name) + else: + # remember data + # In case this is not our own record but of another + # gajim instance on the same machine, + # it will be used when we get a new name. + self.invalid_self_contact[name] = (name, domain, interface, protocol, host, address, port, bare_name, txt) + + + # different handler when resolving all contacts + def service_resolved_all_callback(self, interface, protocol, name, stype, domain, host, aprotocol, address, port, txt, flags): + if not self.connected: + return + bare_name = name + if name.find('@') == -1: + name = name + '@' + name + self.contacts[name] = (name, domain, interface, protocol, host, address, port, bare_name, txt) + + def service_added_callback(self): + gajim.log.debug('Service successfully added') + + def service_committed_callback(self): + gajim.log.debug('Service successfully committed') + + def service_updated_callback(self): + gajim.log.debug('Service successfully updated') + + def service_add_fail_callback(self, err): + gajim.log.debug('Error while adding service. %s' % str(err)) + if str(err) == 'Local name collision': + alternative_name = self.server.GetAlternativeServiceName(self.username) + self.name_conflictCB(alternative_name) + return + self.error_CB(_('Error while adding service. %s') % str(err)) + self.disconnect() + + def server_state_changed_callback(self, state, error): + if state == self.avahi.SERVER_RUNNING: + self.create_service() + elif state == self.avahi.SERVER_COLLISION: + self.entrygroup.Reset() + elif state == self.avahi.CLIENT_FAILURE: + # does it ever go here? + gajim.log.debug('CLIENT FAILURE') + + def entrygroup_state_changed_callback(self, state, error): + # the name is already present, so recreate + if state == self.avahi.ENTRY_GROUP_COLLISION: + self.service_add_fail_callback('Local name collision') + elif state == self.avahi.ENTRY_GROUP_FAILURE: + gajim.log.debug('zeroconf.py: ENTRY_GROUP_FAILURE reached(that' + ' should not happen)') + + # make zeroconf-valid names + def replace_show(self, show): + if show in ['chat', 'online', '']: + return 'avail' + elif show == 'xa': + return 'away' + return show + + def avahi_txt(self): + utf8_dict = {} + for key in self.txt: + val = self.txt[key] + if isinstance(val, unicode): + utf8_dict[key] = val.encode('utf-8') + else: + utf8_dict[key] = val + return self.avahi.dict_to_txt_array(utf8_dict) + + def create_service(self): + try: + if not self.entrygroup: + # create an EntryGroup for publishing + self.entrygroup = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, self.server.EntryGroupNew()), self.avahi.DBUS_INTERFACE_ENTRY_GROUP) + self.entrygroup.connect_to_signal('StateChanged', self.entrygroup_state_changed_callback) + + txt = {} + + #remove empty keys + for key,val in self.txt.iteritems(): + if val: + txt[key] = val + + txt['port.p2pj'] = self.port + txt['version'] = 1 + txt['txtvers'] = 1 + + # replace gajim's show messages with compatible ones + if self.txt.has_key('status'): + txt['status'] = self.replace_show(self.txt['status']) + else: + txt['status'] = 'avail' + + self.txt = txt + gajim.log.debug('Publishing service %s of type %s' % (self.name, self.stype)) + self.entrygroup.AddService(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype, '', '', self.port, self.avahi_txt(), reply_handler=self.service_added_callback, error_handler=self.service_add_fail_callback) + + self.entrygroup.Commit(reply_handler=self.service_committed_callback, + error_handler=self.entrygroup_commit_error_CB) + + return True + + except dbus.DBusException, e: + gajim.log.debug(str(e)) + return False + + def announce(self): + if not self.connected: + return False + + state = self.server.GetState() + if state == self.avahi.SERVER_RUNNING: + self.create_service() + self.announced = True + return True + + def remove_announce(self): + if self.announced == False: + return False + try: + if self.entrygroup.GetState() != self.avahi.ENTRY_GROUP_FAILURE: + self.entrygroup.Reset() + self.entrygroup.Free() + # .Free() has mem leaks + obj = self.entrygroup._obj + obj._bus = None + self.entrygroup._obj = None + self.entrygroup = None + self.announced = False + + return True + else: + return False + except dbus.DBusException, e: + gajim.log.debug("Can't remove service. That should not happen") + + def browse_domain(self, interface, protocol, domain): + self.new_service_type(interface, protocol, self.stype, domain, '') + + def avahi_dbus_connect_cb(self, a, connect, disconnect): + if connect != "": + gajim.log.debug('Lost connection to avahi-daemon') + self.disconnect() + if self.disconnected_CB: + self.disconnected_CB() + else: + gajim.log.debug('We are connected to avahi-daemon') + + # connect to dbus + def connect_dbus(self): + try: + import dbus + except ImportError: + gajim.log.debug('Error: python-dbus needs to be installed. No zeroconf support.') + return False + if self.bus: + return True + try: + self.bus = dbus.SystemBus() + self.bus.add_signal_receiver(self.avahi_dbus_connect_cb, + "NameOwnerChanged", "org.freedesktop.DBus", + arg0="org.freedesktop.Avahi") + except Exception, e: + # System bus is not present + self.bus = None + gajim.log.debug(str(e)) + return False + else: + return True + + # connect to avahi + def connect_avahi(self): + if not self.connect_dbus(): + return False + try: + import avahi + self.avahi = avahi + except ImportError: + gajim.log.debug('Error: python-avahi needs to be installed. No zeroconf support.') + return False + + if self.server: + return True + try: + self.server = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, \ + self.avahi.DBUS_PATH_SERVER), self.avahi.DBUS_INTERFACE_SERVER) + self.server.connect_to_signal('StateChanged', + self.server_state_changed_callback) + except Exception, e: + # Avahi service is not present + self.server = None + gajim.log.debug(str(e)) + return False + else: + return True + + def connect(self): + self.name = self.username + '@' + self.host # service name + if not self.connect_avahi(): + return False + + self.connected = True + # start browsing + if self.domain is None: + # Explicitly browse .local + self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, "local") + + # Browse for other browsable domains + self.domain_browser = dbus.Interface(self.bus.get_object(self.avahi.DBUS_NAME, \ + self.server.DomainBrowserNew(self.avahi.IF_UNSPEC, \ + self.avahi.PROTO_UNSPEC, '', self.avahi.DOMAIN_BROWSER_BROWSE,\ + dbus.UInt32(0))), self.avahi.DBUS_INTERFACE_DOMAIN_BROWSER) + self.domain_browser.connect_to_signal('ItemNew', self.new_domain_callback) + self.domain_browser.connect_to_signal('Failure', self.error_callback) + else: + self.browse_domain(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, self.domain) + + return True + + def disconnect(self): + if self.connected: + self.connected = False + if self.service_browser: + self.service_browser.Free() + self.service_browser._obj._bus = None + self.service_browser._obj = None + if self.domain_browser: + self.domain_browser.Free() + self.domain_browser._obj._bus = None + self.domain_browser._obj = None + self.remove_announce() + self.server._obj._bus = None + self.server._obj = None + self.server = None + self.service_browser = None + self.domain_browser = None + + # refresh txt data of all contacts manually (no callback available) + def resolve_all(self): + if not self.connected: + return + for val in self.contacts.values(): + self.server.ResolveService(int(val[C_INTERFACE]), int(val[C_PROTOCOL]), + val[C_BARE_NAME], self.stype, val[C_DOMAIN], + self.avahi.PROTO_UNSPEC, dbus.UInt32(0), + reply_handler=self.service_resolved_all_callback, + error_handler=self.error_callback) + + def get_contacts(self): + return self.contacts + + def get_contact(self, jid): + if not jid in self.contacts: + return None + return self.contacts[jid] + + def update_txt(self, show = None): + if show: + self.txt['status'] = self.replace_show(show) + + txt = self.avahi_txt() + if self.connected and self.entrygroup: + self.entrygroup.UpdateServiceTxt(self.avahi.IF_UNSPEC, self.avahi.PROTO_UNSPEC, dbus.UInt32(0), self.name, self.stype,'', txt, reply_handler=self.service_updated_callback, error_handler=self.error_callback) + return True + else: + return False + + +# END Zeroconf diff --git a/src/common/zeroconf/zeroconf_bonjour.py b/src/common/zeroconf/zeroconf_bonjour.py index d35660ac7..3b7207004 100644 --- a/src/common/zeroconf/zeroconf_bonjour.py +++ b/src/common/zeroconf/zeroconf_bonjour.py @@ -16,6 +16,7 @@ from common import gajim import sys import select from string import split +from common.zeroconf.zeroconf import C_BARE_NAME, C_DOMAIN try: import pybonjour @@ -23,9 +24,6 @@ except ImportError, e: pass -C_NAME, C_DOMAIN, C_INTERFACE, C_PROTOCOL, C_HOST, \ -C_ADDRESS, C_PORT, C_BARE_NAME, C_TXT = range(9) - resolve_timeout = 1 class Zeroconf: @@ -297,7 +295,10 @@ class Zeroconf: self.browse_loop() for val in self.contacts.values(): - resolve_sdRef = pybonjour.DNSServiceResolve(0, pybonjour.kDNSServiceInterfaceIndexAny, val[C_BARE_NAME], self.stype+'.', val[C_DOMAIN]+'.', self.service_resolved_all_callback) + resolve_sdRef = pybonjour.DNSServiceResolve(0, + pybonjour.kDNSServiceInterfaceIndexAny, val[C_BARE_NAME], + self.stype + '.', val[C_DOMAIN] + '.', + self.service_resolved_all_callback) try: ready = select.select([resolve_sdRef], [], [], resolve_timeout) diff --git a/src/features_window.py b/src/features_window.py index f0c2ec5d6..40a4af8f5 100644 --- a/src/features_window.py +++ b/src/features_window.py @@ -44,7 +44,7 @@ class FeaturesWindow: _('Bonjour / Zeroconf'): (self.zeroconf_available, _('Serverless chatting with autodetected clients in a local network.'), _('Requires python-avahai.'), - _('Feature not available under Windows.')), + _('Requires pybonjour (http://o2s.csail.mit.edu/o2s-wiki/pybonjour).')), _('gajim-remote'): (self.dbus_available, _('A script to controle gajim via commandline.'), _('Requires python-dbus.'), @@ -141,12 +141,13 @@ class FeaturesWindow: return True def zeroconf_available(self): - if os.name == 'nt': - return False try: import avahi except: - return False + try: + import pybonjour + except: + return False return True def dbus_available(self): @@ -272,4 +273,4 @@ class FeaturesWindow: pass if exitcode == 0: return True - return False \ No newline at end of file + return False