Merge lp:~cmiller/desktopcouch/replication-daemon into lp:desktopcouch

Proposed by Chad Miller
Status: Superseded
Proposed branch: lp:~cmiller/desktopcouch/replication-daemon
Merge into: lp:desktopcouch
Diff against target: None lines
To merge this branch: bzr merge lp:~cmiller/desktopcouch/replication-daemon
Reviewer Review Type Date Requested Status
Eric Casteleijn (community) Needs Fixing
Review via email: mp+10732@code.launchpad.net

This proposal has been superseded by a proposal from 2009-08-26.

Commit message

Add a daemon that to replicate to our dynamic style of internettin', where
hosts may have the same name, change addresses often, and be offline for
periods. If we've paired with another host, then we know our unique ID
and the ID of the remote end. We advertise our ID via zeroconf, and look
for the remote end to appear on our local network. When it does, we send
all our non-excluded desktopcouch databases to it.

This daemon shoud probably be merged into the desktopcouch-start program
somewhere.

To post a comment you must log in.
Revision history for this message
Chad Miller (cmiller) wrote :

Buggy first try at replication.

Revision history for this message
Stuart Langridge (sil) wrote :

We decided to not use relative imports, I think.

I reckon that this code should actually be integrated into startup before it's approved?

Revision history for this message
Stuart Langridge (sil) wrote :

Also, Xget_replication_list ?

49. By Chad Miller

Added TODO notes and set the replication period to a sane rate.

Revision history for this message
Eric Casteleijn (thisfred) wrote :

Some long lines in bin/desktopcouch-pair

I'd put the recordtype as a constant at the top of the file:

PAIRED_SERVER_TYPE = \
    "http://www.freedesktop.org/wiki/Specifications/desktopcouch/paired_server"

couchdb_io has 4 unused imports:

import urllib2
import json
import tempfile
import os

and relative imports which should be abolutized.

also long lines which I would solve:

base_url = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/"
PAIRED_SERVER_RECORD_TYPE = base_url + "paired_server"
MY_ID_RECORD_TYPE = base_url + "server_identity"

(there are more. It pays to have an editor that shows them. If you're using emacs, I can send you a thingy that does that)

This is broken (calls a method on self outside a class, ViewDefinition is undefined)

def Xget_replication_list(db):
    map_js = """function(doc) { emit(doc.managed_by, doc) }"""
    view_name = "u1_replicators_by_manager"
    design_document = "ubuntuone_replication"
    view = ViewDefinition(design_document, view_name, map_js, None)
    view.sync(db)
    results = self.execute_view(view_name, design_document)
    return results

in dbus_io.py:

long lines, and a lot of *args, **kwargs magic (perhaps that cannot be helped)

review: Needs Fixing
50. By Chad Miller

Fixed most lint problems. Removed unused function.

51. By Chad Miller

Use XDG module to find log directory.

52. By Chad Miller

Since we're defined in desktopcouch module now, it's okay to use direct
functions instead of DBus proxies of those functions.

53. By Chad Miller

Explain about local pairing being disabled because of couchdb auth
not working.

54. By Chad Miller

Fix method name typo.

55. By Chad Miller

Also stop listening end, before user gets far.

56. By Chad Miller

Since a replicate() funciton is missing from python-couchdb , make it
internally.

Fix a few bugs with reversed logic, lack of a port number, and one
change-while-iterating bug.

Remove an unused import.

Log more useful information.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/desktopcouch-pair'
--- bin/desktopcouch-pair 2009-08-24 19:06:43 +0000
+++ bin/desktopcouch-pair 2009-08-26 12:30:49 +0000
@@ -329,10 +329,11 @@
329329
330 hostname, domainname = dbus_io.get_local_hostname()330 hostname, domainname = dbus_io.get_local_hostname()
331 username = getpass.getuser()331 username = getpass.getuser()
332 self.advertisement = dbus_io.Advertisement(port=listen_port,332 self.advertisement = dbus_io.PairAdvertisement(port=listen_port,
333 name="%s-%s-%d" % (hostname, username, listen_port),333 name="%s-%s-%d" % (hostname, username, listen_port),
334 text=dict(version=str(discovery_tool_version),334 text=dict(version=str(discovery_tool_version),
335 description=get_host_info()))335 description=get_host_info()))
336 self.advertisement.publish()
336 return hostname, username, listen_port337 return hostname, username, listen_port
337338
338 def __init__(self, couchdb_instance):339 def __init__(self, couchdb_instance):
@@ -725,4 +726,5 @@
725726
726if __name__ == "__main__":727if __name__ == "__main__":
727 import sys728 import sys
729 desktopcouch_port = dbus_io.get_desktopcouch_listening_port()
728 main(sys.argv)730 main(sys.argv)
729731
=== added file 'bin/desktopcouch-paired-replication-manager'
--- bin/desktopcouch-paired-replication-manager 1970-01-01 00:00:00 +0000
+++ bin/desktopcouch-paired-replication-manager 2009-08-26 12:30:49 +0000
@@ -0,0 +1,113 @@
1#!/bin/sh
2""""exec ${PYTHON:-python} -t $0 "$@";" """
3# vim: filetype=python expandtab smarttab
4
5# Copyright 2009 Canonical Ltd.
6#
7# This file is part of desktopcouch.
8#
9# desktopcouch is free software: you can redistribute it and/or modify
10# it under the terms of the GNU Lesser General Public License version 3
11# as published by the Free Software Foundation.
12#
13# desktopcouch is distributed in the hope that it will be useful,
14# but WITHOUT ANY WARRANTY; without even the implied warranty of
15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16# GNU Lesser General Public License for more details.
17#
18# You should have received a copy of the GNU Lesser General Public License
19# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
20#
21# Authors: Chad Miller <chad.miller@canonical.com>
22
23import os
24import json
25import threading
26import logging
27import logging.handlers
28log = logging.getLogger("main")
29
30from twisted.internet import gtk2reactor
31gtk2reactor.install()
32from twisted.internet import reactor, task
33
34import desktopcouch
35from desktopcouch.pair.couchdb_pairing import couchdb_io
36from desktopcouch.pair.couchdb_pairing import dbus_io
37
38already_replicating = False
39
40class ReplicatorThread(threading.Thread):
41 def __init__(self):
42 log.debug("starting up replication thread")
43 super(ReplicatorThread, self).__init__()
44
45 def run(self):
46 global already_replicating # Fuzzy, as not really critical,
47 already_replicating = True # just trying to be polite.
48 try:
49 for uuid, addr, port in dbus_io.get_seen_paired_hosts():
50 log.debug("host %s is seen, and I want to replicate to it", uuid)
51 for database_name in couchdb_io.get_database_names_replicatable():
52 couchdb_io.replicate(database_name, database_name,
53 target_host=addr, target_port=port)
54 finally:
55 already_replicating = False
56 log.debug("finished replicating")
57
58
59def replicate_local_databases_to_paired_hosts():
60 if already_replicating:
61 log.warn("haven't finished replicating before next time to start.")
62 return False
63
64 r = ReplicatorThread()
65 r.start()
66
67def main(args):
68 log_directory = os.path.expanduser("~/.cache/ubuntuone/log")
69 try:
70 os.makedirs(log_directory)
71 except:
72 pass
73 rotating_log = logging.handlers.TimedRotatingFileHandler(
74 os.path.join(log_directory, "desktop-couch-replication.log"),
75 "midnight", 1, 14)
76 rotating_log.setLevel(logging.DEBUG)
77 formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
78 rotating_log.setFormatter(formatter)
79 logging.getLogger('').addHandler(rotating_log)
80 logging.getLogger('').setLevel(logging.DEBUG)
81
82 try:
83 log.info("Starting.")
84
85 unique_identifiers = couchdb_io.get_my_host_unique_id()
86 if unique_identifiers is None:
87 log.warn("No unique hostaccount id is set, so pairing not enabled.")
88 sys.exit(2)
89
90 port = desktopcouch.find_port()
91 beacons = [dbus_io.LocationAdvertisement(port, "desktopcouch " + i)
92 for i in unique_identifiers]
93 for b in beacons:
94 b.publish()
95
96 dbus_io.discover_services(None, None, True)
97
98 try:
99 dbus_io.maintain_discovered_servers()
100 t = task.LoopingCall(replicate_local_databases_to_paired_hosts)
101 t.start(6)
102 reactor.run()
103 finally:
104 for b in beacons:
105 b.unpublish()
106
107 finally:
108 log.info("Quitting.")
109
110if __name__ == "__main__":
111 import sys
112
113 main(sys.argv)
0114
=== modified file 'desktopcouch/pair/couchdb_pairing/couchdb_io.py'
--- desktopcouch/pair/couchdb_pairing/couchdb_io.py 2009-07-08 17:48:11 +0000
+++ desktopcouch/pair/couchdb_pairing/couchdb_io.py 2009-08-26 12:30:49 +0000
@@ -2,7 +2,7 @@
2#2#
3# This file is part of desktopcouch.3# This file is part of desktopcouch.
4#4#
5# desktopcouch is free software: you can redistribute it and/or modify5# desktopcouch is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 36# it under the terms of the GNU Lesser General Public License version 3
7# as published by the Free Software Foundation.7# as published by the Free Software Foundation.
8#8#
@@ -13,27 +13,127 @@
13#13#
14# You should have received a copy of the GNU Lesser General Public License14# You should have received a copy of the GNU Lesser General Public License
15# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.15# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
16#
17# Authors: Chad Miller <chad.miller@canonical.com>
16"""Communicate with CouchDB."""18"""Communicate with CouchDB."""
1719
18import urllib220import urllib2
19import json21import json
20import logging22import logging
2123import tempfile
22def replicate_to(port, src_name, dst_host, dst_port, dst_name):24import os
23 """A simple easiest-possible replication instruction for couchdb. It's25
24 almost certainly wrong for us."""26from ... import find_port as desktopcouch_find_port
2527from ...records import server
26 dst_url = u"http://%(dst_host)s):%(dst_port)d/%(dst_name)s" % locals()28
27 doc_data = dict(source_database=src_name.encode("utf8"),29PAIRED_SERVER_RECORD_TYPE = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/paired_server"
28 target_database=dst_url.encode("utf8"))30MY_ID_RECORD_TYPE = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/server_identity"
2931
30 document = json.dumps(doc_data) # json doesn't mention Unicode.32def _get_db(name, create=True):
3133 port = desktopcouch_find_port() # make sure d-c is running.
32 req = urllib2.Request("http://localhost:%d/_replicate" % (port,), document)34 return server.CouchDatabase(name, create=create)
33 35
36def get_database_names_replicatable():
37 """Find a list of local databases, minus dbs that we do not want to
38 replicate (explicitly or implicitly)."""
39
40 port = int(desktopcouch_find_port())
41 couchdb_server = server.Server("http://localhost:%(port)d/" % locals())
42 all = set([db_name for db_name in couchdb_server])
43
44 excluded = set()
45 excluded.add("management")
46 excluded_msets = _get_management_data(PAIRED_SERVER_RECORD_TYPE, "excluded_names")
47 for excluded_mset in excluded_msets:
48 excluded.update(excluded_mset)
49
50 return all - excluded
51
52def get_my_host_unique_id():
53 """Returns a list of ids we call ourselves. We complain in the log if it's
54 more than one, but it's really no error. If there are zero (id est, we've
55 never paired with anyone), then returns None."""
56
57 db = _get_db("management")
58 ids = _get_management_data(MY_ID_RECORD_TYPE, "self_identity")
59 ids = list(set(ids)) # uniqify
60 if len(ids) > 1:
61 logging.error("DANGER! We have more than one record claiming to be this host's unique identifier. Which is right? We will try to use them all, but this smells really funny.")
62 return ids
63 if len(ids) == 1:
64 return ids
65 return None
66
67def get_local_paired_uuids():
68 results = _get_management_data(PAIRED_SERVER_RECORD_TYPE, "pairing_identifier")
69 return results
70
71def _get_management_data(record_type, key):
72 db = _get_db("management")
73 results = db.get_records(create_view=True)
74 values = list()
75 for record in results[record_type]:
76 if key in record.value: # EAFP, rather than LBYL? Nones default?
77 value = record.value[key]
78 if value is not None:
79 values.append(value)
80 else:
81 logging.debug("skipping record empty %s", key)
82 else:
83 logging.debug("skipping record with no %s", key)
84 logging.debug("found %d %s records", len(values), key)
85 return values
86
87def create_remote_database(dst_host, dst_port, dst_name):
88 dst_url = u"http://%(dst_host)s:%(dst_port)d/" % locals()
89 return server.CouchDatabase(dst_name, dst_url, create=True)
90
91def replicate(source_database, target_database, target_host=None,
92 target_port=None, source_host=None, source_port=None):
93 """This replication is instant and blocking, and does not persist. """
94
95 data = {}
96
97 if source_host:
98 if source_port:
99 source = "http://%(source_host)s/%(source_database)s" % locals()
100 else:
101 source = "http://%(source_host)s:%(source_port)d/%(source_database)s" % locals()
102 else:
103 source = source_database
104
105 if target_host:
106 if target_port:
107 target = "http://%(target_host)s/%(target_database)s" % locals()
108 else:
109 target = "http://%(target_host)s:%(target_port)d/%(target_database)s" % locals()
110 else:
111 target = target_database
112
113 record = dict(source=source, target=target)
34 try:114 try:
35 conn = urllib2.urlopen(req)115 if target_host:
36 logging.info("couchdb request resulted in %r", conn.read())116 # Remote databases must exist before replicating to them.
117 create_remote_database(target_host, target_port, target_database)
118
119 port = int(desktopcouch_find_port())
120 url = "http://localhost:%d/" % (port,)
121
122 import couchdb
123 server = couchdb.client.Server(url)
124 db = server["_replicate"]
125 db.create(record)
126
127 logging.info("successfully replicated %r", record)
37 except:128 except:
38 logging.exception("can't talk to couchdb.")129 logging.exception("can't talk to couchdb.")
39 raise130 raise
131
132def Xget_replication_list(db):
133 map_js = """function(doc) { emit(doc.managed_by, doc) }"""
134 view_name = "u1_replicators_by_manager"
135 design_document = "ubuntuone_replication"
136 view = ViewDefinition(design_document, view_name, map_js, None)
137 view.sync(db)
138 results = self.execute_view(view_name, design_document)
139 return results
40140
=== modified file 'desktopcouch/pair/couchdb_pairing/dbus_io.py'
--- desktopcouch/pair/couchdb_pairing/dbus_io.py 2009-07-20 14:34:08 +0000
+++ desktopcouch/pair/couchdb_pairing/dbus_io.py 2009-08-26 12:30:49 +0000
@@ -2,7 +2,7 @@
2#2#
3# This file is part of desktopcouch.3# This file is part of desktopcouch.
4#4#
5# desktopcouch is free software: you can redistribute it and/or modify5# desktopcouch is free software: you can redistribute it and/or modify
6# it under the terms of the GNU Lesser General Public License version 36# it under the terms of the GNU Lesser General Public License version 3
7# as published by the Free Software Foundation.7# as published by the Free Software Foundation.
8#8#
@@ -13,6 +13,8 @@
13#13#
14# You should have received a copy of the GNU Lesser General Public License14# You should have received a copy of the GNU Lesser General Public License
15# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.15# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
16#
17# Authors: Chad Miller <chad.miller@canonical.com>
16"""Communicate with DBUS and also the APIs it proxies, like Zeroconf."""18"""Communicate with DBUS and also the APIs it proxies, like Zeroconf."""
1719
18import logging20import logging
@@ -22,8 +24,11 @@
22import avahi24import avahi
23DBusGMainLoop(set_as_default=True)25DBusGMainLoop(set_as_default=True)
2426
25discovery_service_type = "_couchdb_pairing_invitations._tcp"27from . import couchdb_io
2628
29invitations_discovery_service_type = "_couchdb_pairing_invitations._tcp"
30location_discovery_service_type = "_couchdb_location._tcp"
31desktopcouch_dbus_interface = "org.desktopcouch.CouchDB"
2732
28def get_local_hostname():33def get_local_hostname():
29 """Get the name of this host, as Unicode host and domain parts."""34 """Get the name of this host, as Unicode host and domain parts."""
@@ -43,36 +48,34 @@
4348
44 return hostname49 return hostname
4550
46def get_dbus_bus_server():51def get_desktopcouch_listening_port():
52 bus, server = get_dbus_bus_server(desktopcouch_dbus_interface)
53 return server.desktopCouch.getPort()
54
55def get_dbus_bus_server(interface="root"):
47 """Common sequence of steps to get a Bus and Server object from DBUS."""56 """Common sequence of steps to get a Bus and Server object from DBUS."""
48 bus = dbus.SystemBus()57 bus = dbus.SystemBus()
49 root_name = bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER)58 root_name = bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER)
50 server = dbus.Interface(root_name, avahi.DBUS_INTERFACE_SERVER)59 server = dbus.Interface(root_name, avahi.DBUS_INTERFACE_SERVER)
51 return bus, server60 return bus, server
5261
53
54class Advertisement(object):62class Advertisement(object):
55 """Represents an advertised service that exists on this host."""63 """Represents an advertised service that exists on this host."""
5664 def __init__(self, port, name, stype="", domain="", host="", text={}):
57 def __init__(self, port, name="rlx!", stype=discovery_service_type,65 super(Advertisement, self).__init__()
58 domain="", host="", text="(unknown)"):
59 66
60 self.logging = logging.getLogger(self.__class__.__name__)67 self.logging = logging.getLogger(self.__class__.__name__)
61
62 super(Advertisement, self).__init__()
63 self.name = name68 self.name = name
64 self.stype = stype69 self.stype = stype
65 self.domain = domain70 self.domain = domain
66 self.host = host71 self.host = host
67 self.port = port72 self.port = int(port)
68 if hasattr(text, "keys"):73 if hasattr(text, "keys"):
69 self.text = avahi.dict_to_txt_array(text)74 self.text = avahi.dict_to_txt_array(text)
70 else:75 else:
71 self.text = text76 self.text = text
7277
73 self.group = None78 self.group = None
74
75 self.publish()
76 79
77 def publish(self):80 def publish(self):
78 """Start the advertisement."""81 """Start the advertisement."""
@@ -87,20 +90,125 @@
8790
88 g.Commit()91 g.Commit()
89 self.logging.info("starting advertising %s on port %d",92 self.logging.info("starting advertising %s on port %d",
90 discovery_service_type, self.port)93 self.stype, self.port)
91 self.group = g94 self.group = g
92 95
93 def unpublish(self):96 def unpublish(self):
94 """End the advertisement."""97 """End the advertisement."""
95 self.group.Reset()98 self.group.Reset()
96 self.logging.info("ending advertising %s on port %d",99 self.logging.info("ending advertising %s on port %d",
97 discovery_service_type, self.port)100 self.stype, self.port)
98 self.group = None101 self.group = None
99102
100 def die(self):103 def die(self):
101 """Quit."""104 """Quit."""
102 self.unpublish()105 self.unpublish()
103106
107class LocationAdvertisement(Advertisement):
108 """An advertised couchdb location. See Advertisement class."""
109 def __init__(self, *args, **kwargs):
110 if "stype" in kwargs:
111 kwargs.pop(stype)
112 super(LocationAdvertisement, self).__init__(stype=location_discovery_service_type, *args, **kwargs)
113
114class PairAdvertisement(Advertisement):
115 """An advertised couchdb pairing opportunity. See Advertisement class."""
116 def __init__(self, *args, **kwargs):
117 if "stype" in kwargs:
118 kwargs.pop(stype)
119 super(PairAdvertisement, self).__init__(stype=invitations_discovery_service_type, *args, **kwargs)
120
121def avahitext_to_dict(avahitext):
122 text = {}
123 for l in avahitext:
124 try:
125 k, v = "".join(chr(i) for i in l).split("=", 1)
126 text[k] = v
127 except ValueError, e:
128 logging.error("k/v field could not be decoded. %s", e)
129 return text
130
131
132nearby_desktop_couch_instances = dict() # k=uuid, v=(addr, port)
133
134def cb_found_desktopcouch_server(uuid, host_address, port):
135 nearby_desktop_couch_instances[uuid] = (unicode(host_address), int(port))
136
137def cb_lost_desktopcouch_server(uuid):
138 try:
139 del nearby_desktop_couch_instances[uuid]
140 except KeyError:
141 pass
142
143def get_seen_paired_hosts():
144 paired_uuids = couchdb_io.get_local_paired_uuids()
145 return (
146 (uuid, addr, port)
147 for uuid, (addr, port)
148 in nearby_desktop_couch_instances.iteritems()
149 if uuid in paired_uuids)
150
151def maintain_discovered_servers(add_cb=cb_found_desktopcouch_server,
152 del_cb=cb_lost_desktopcouch_server):
153
154 def remove_item_handler(interface, protocol, name, stype, domain, flags):
155 """A service disappeared."""
156
157 def handle_error(*args):
158 """An error in resolving a new service."""
159 logging.error("zeroconf ItemNew error for services, %s", args)
160
161 def handle_resolved(*args):
162 """Successfully resolved a new service, which we decode and send
163 back to our calling environment with the callback function."""
164
165 name, host, port = args[2], args[5], args[8]
166 if name.startswith("desktopcouch "):
167 del_cb(name[13:], host, port)
168 else:
169 logging.error("no UUID in zeroconf message, %r", args)
170
171 del_cb(uuid)
172
173 server.ResolveService(interface, protocol, name, stype,
174 domain, avahi.PROTO_UNSPEC, dbus.UInt32(0),
175 reply_handler=handle_resolved, error_handler=handle_error)
176
177 def new_item_handler(interface, protocol, name, stype, domain, flags):
178 """A service appeared."""
179
180 def handle_error(*args):
181 """An error in resolving a new service."""
182 logging.error("zeroconf ItemNew error for services, %s", args)
183
184 def handle_resolved(*args):
185 """Successfully resolved a new service, which we decode and send
186 back to our calling environment with the callback function."""
187
188 name, host, port = args[2], args[5], args[8]
189 # FIXME strip off "desktopcouch "
190 if name.startswith("desktopcouch "):
191 add_cb(name[13:], host, port)
192 else:
193 logging.error("no UUID in zeroconf message, %r", name)
194 return True
195
196 server.ResolveService(interface, protocol, name, stype,
197 domain, avahi.PROTO_UNSPEC, dbus.UInt32(0),
198 reply_handler=handle_resolved, error_handler=handle_error)
199
200 bus, server = get_dbus_bus_server()
201 domain_name = get_local_hostname()[1]
202 browser = server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC,
203 location_discovery_service_type, domain_name, dbus.UInt32(0))
204 browser_name = bus.get_object(avahi.DBUS_NAME, browser)
205
206 sbrowser = dbus.Interface(browser_name,
207 avahi.DBUS_INTERFACE_SERVICE_BROWSER)
208 sbrowser.connect_to_signal("ItemNew", new_item_handler)
209 sbrowser.connect_to_signal("ItemRemove", remove_item_handler)
210 sbrowser.connect_to_signal("Failure", lambda *a: logging.error("avahi error %r", a))
211
104212
105def discover_services(add_commport_name_cb, del_commport_name_cb,213def discover_services(add_commport_name_cb, del_commport_name_cb,
106 show_local=False):214 show_local=False):
@@ -125,13 +233,10 @@
125 def handle_resolved(*args):233 def handle_resolved(*args):
126 """Successfully resolved a new service, which we decode and send234 """Successfully resolved a new service, which we decode and send
127 back to our calling environment with the callback function."""235 back to our calling environment with the callback function."""
128 text = {}236 text = avahitext_to_dict(args[9])
129 for l in args[9]:237 name, host, port = args[2], args[5], args[8]
130 k, v = "".join(chr(i) for i in l).split("=", 1)238 add_commport_name_cb(name, text.get("description", "?"),
131 text[k] = v239 host, port, text.get("version", None))
132
133 add_commport_name_cb(args[2], text.get("description", "?"),
134 args[5], args[8], text.get("version", None))
135240
136 if not show_local and flags & avahi.LOOKUP_RESULT_LOCAL:241 if not show_local and flags & avahi.LOOKUP_RESULT_LOCAL:
137 return242 return
@@ -142,14 +247,14 @@
142247
143248
144 bus, server = get_dbus_bus_server()249 bus, server = get_dbus_bus_server()
145
146 domain_name = get_local_hostname()[1]250 domain_name = get_local_hostname()[1]
147251
148 browser = server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC,252 browser = server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC,
149 discovery_service_type, domain_name, dbus.UInt32(0))253 invitations_discovery_service_type, domain_name, dbus.UInt32(0))
150 browser_name = bus.get_object(avahi.DBUS_NAME, browser)254 browser_name = bus.get_object(avahi.DBUS_NAME, browser)
151255
152 sbrowser = dbus.Interface(browser_name,256 sbrowser = dbus.Interface(browser_name,
153 avahi.DBUS_INTERFACE_SERVICE_BROWSER)257 avahi.DBUS_INTERFACE_SERVICE_BROWSER)
154 sbrowser.connect_to_signal("ItemNew", new_item_handler)258 sbrowser.connect_to_signal("ItemNew", new_item_handler)
155 sbrowser.connect_to_signal("ItemRemove", remove_item_handler)259 sbrowser.connect_to_signal("ItemRemove", remove_item_handler)
260 sbrowser.connect_to_signal("Failure", lambda *a: logging.error("avahi error %r", a))

Subscribers

People subscribed via source and target branches