__metaclass__ = type import stat import pprint import avahi import dbus import dbus.mainloop.glib import gobject from bzrlib import bzrdir, errors, urlutils from bzrlib.transport import get_transport, smart class AdvertisedBranch: def __init__(self, server, path, nick): self.server = server if path.startswith('.'): path = path[1:] if path == '': path = '/' self.path = path self.nick = nick self.group = None def __repr__(self): return 'AdvertisedBranch(%r, %r, %r)' % ( self.server, self.path, self.nick) def removeService(self): if self.group is not None: self.group.Reset() def addService(self): if self.group is None: self.group = dbus.Interface( self.server.bus.get_object( avahi.DBUS_NAME, self.server.avahi_server.EntryGroupNew()), avahi.DBUS_INTERFACE_ENTRY_GROUP) self.group.connect_to_signal('StateChanged', self.stateChanged) assert self.group.IsEmpty() self.group.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, 0, self.nick, '_bzr._tcp', self.server.domain, self.server.host, dbus.UInt16(self.server.smart_server.port), avahi.string_array_to_txt_array(['path=%s' % self.path])) self.group.Commit() def stateChanged(self, state, status=None): if state == avahi.ENTRY_GROUP_ESTABLISHED: print 'Service established' elif state == avahi.ENTRY_GROUP_COLLISION: self.nick = self.server.avahi_server.GetAlternativeServiceName( self.nick) print 'WARNING: Service name collision, changing name to %s' % self.nick self.removeService() self.addService() def close(self): if self.group is not None: self.group.Free() self.group = None class ZeroConfServer: def __init__(self, base, host='', port=0, domain=''): self.base = base self.host = host self.port = port self.domain = '' self.server = None self.branches = [] def run(self): self.initializeMainLoop() self.findBranches() self.setUpSmartServer() self.advertiseBranches() self.main.run() def close(self): for branch in self.branches: branch.close() def initializeMainLoop(self): """Initialise the glib main loop, and connect to system bus.""" gobject.threads_init() dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) self.main = gobject.MainLoop() def findBranches(self): """Find branches under the base URL, and add them to the branch list""" trans = get_transport(self.base) dirs_to_check = ['.'] while len(dirs_to_check) > 0: filename = dirs_to_check.pop(0) if stat.S_ISDIR(trans.stat(filename).st_mode): try: bdir = bzrdir.BzrDir.open(trans.abspath(filename)) branch = bdir.open_branch() except errors.NotBranchError: branch = None if branch is not None: print 'Found branch "%s" (nick=%s)' % (filename, branch.nick) self.branches.append(AdvertisedBranch(self,filename, branch.nick)) dirs_to_check.extend([urlutils.join(filename, name) for name in trans.list_dir(filename) if name != '.bzr']) def setUpSmartServer(self): transport = get_transport('readonly+' + self.base) self.smart_server = smart.SmartTCPServer(transport, self.host, self.port) self.port = self.smart_server.port gobject.io_add_watch(self.smart_server._server_socket.fileno(), gobject.IO_IN | gobject.IO_ERR | gobject.IO_HUP | gobject.IO_NVAL, self.acceptConnection) def acceptConnection(self, fp, condition): print 'acceptConnection:', (fp, condition) if condition & gobject.IO_IN: self.smart_server.accept_and_serve() return True def advertiseBranches(self): self.bus = dbus.SystemBus() self.avahi_server = dbus.Interface( self.bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER), avahi.DBUS_INTERFACE_SERVER) self.avahi_server.connect_to_signal('StateChanged', self.avahiStateChanged) self.avahiStateChanged(self.avahi_server.GetState()) def avahiStateChanged(self, state, status=None): if state == avahi.SERVER_COLLISION: for branch in self.branches: branch.removeService() elif state == avahi.SERVER_RUNNING: for branch in self.branches: branch.addService() if __name__ == '__main__': server = ZeroConfServer(urlutils.normalize_url('.')) try: server.run() finally: server.close()