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

Proposed by Chad Miller
Status: Merged
Approved by: Eric Casteleijn
Approved revision: 51
Merged at revision: not available
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
Stuart Langridge (community) Approve
Eric Casteleijn (community) Approve
Review via email: mp+10740@code.launchpad.net

This proposal supersedes a proposal from 2009-08-26.

Commit message

Add local-db replication daemon, and fix a few typos.

Since couchdb auth is not yet supported, stop the pairing tool from giving the user that local replication is supported. Once couchdb is not a security risk and we can bind it to an external interface, then replication will work for everyone.

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

Buggy first try at replication.

Revision history for this message
Stuart Langridge (sil) wrote : Posted in a previous version of this proposal

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 : Posted in a previous version of this proposal

Also, Xget_replication_list ?

Revision history for this message
Eric Casteleijn (thisfred) wrote : Posted in a previous version of this proposal

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
Revision history for this message
Stuart Langridge (sil) wrote :

Code looks good. I can't actually test the functionality because I only have one machine here.

We should use xdg.BaseDirectory to find the cache folder rather than hardcoding ~/.cache, but that's a small point.

review: Approve
51. By Chad Miller

Use XDG module to find log directory.

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

Looks good.

Most of the long lines are still in there, but we can fix them at a less pressing date.

review: Approve
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.

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

I'm probably missing some gtk thing or an implicit import, but it looks like pick_or_listen is not defined in the following:

               fail_note = gtk.MessageDialog(
                    parent=pick_or_listen.window,
                    flags=gtk.DIALOG_DESTROY_WITH_PARENT,
                    buttons=gtk.BUTTONS_OK,
                    type=gtk.MESSAGE_ERROR,
                    message_format =_("Sorry, couchdb authentication is not yet enabled, so local pairing is not supported"))

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

also:

in desktopcouch-paired-replication-manager

import json is unused, and import logging.handler seems unnecessary.

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.

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

r51-56 look good to me!

review: Approve

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 13:59:05 +0000
@@ -0,0 +1,122 @@
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; want to replicate to it", uuid)
51 for db_name in couchdb_io.get_database_names_replicatable():
52 couchdb_io.replicate(db_name, db_name,
53 target_host=addr, target_port=port)
54
55
56 # TODO: get static addressed paired hosts and replicate to
57 # those too.
58
59 finally:
60 already_replicating = False
61 log.debug("finished replicating")
62
63
64def replicate_local_databases_to_paired_hosts():
65 if already_replicating:
66 log.warn("haven't finished replicating before next time to start.")
67 return False
68
69 r = ReplicatorThread()
70 r.start()
71
72def main(args):
73 log_directory = os.path.expanduser("~/.cache/ubuntuone/log")
74 try:
75 os.makedirs(log_directory)
76 except:
77 pass
78 rotating_log = logging.handlers.TimedRotatingFileHandler(
79 os.path.join(log_directory, "desktop-couch-replication.log"),
80 "midnight", 1, 14)
81 rotating_log.setLevel(logging.DEBUG)
82 formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s')
83 rotating_log.setFormatter(formatter)
84 logging.getLogger('').addHandler(rotating_log)
85 logging.getLogger('').setLevel(logging.DEBUG)
86
87 try:
88 log.info("Starting.")
89
90 unique_identifiers = couchdb_io.get_my_host_unique_id()
91 if unique_identifiers is None:
92 log.warn("No unique hostaccount id is set, so pairing not enabled.")
93 sys.exit(2)
94
95 port = desktopcouch.find_port()
96 beacons = [dbus_io.LocationAdvertisement(port, "desktopcouch " + i)
97 for i in unique_identifiers]
98 for b in beacons:
99 b.publish()
100
101 dbus_io.discover_services(None, None, True)
102
103 try:
104 dbus_io.maintain_discovered_servers()
105 t = task.LoopingCall(replicate_local_databases_to_paired_hosts)
106 t.start(600)
107
108 # TODO: port may change, so every so often, check it and
109 # perhaps refresh the beacons.
110
111 reactor.run()
112 finally:
113 for b in beacons:
114 b.unpublish()
115
116 finally:
117 log.info("Quitting.")
118
119if __name__ == "__main__":
120 import sys
121
122 main(sys.argv)
0123
=== 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 13:59:05 +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,118 @@
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 urllib2
19import json
20import logging20import logging
2121
22def replicate_to(port, src_name, dst_host, dst_port, dst_name):22from desktopcouch import find_port as desktopcouch_find_port
23 """A simple easiest-possible replication instruction for couchdb. It's23from desktopcouch.records import server
24 almost certainly wrong for us."""24
2525RECTYPE_BASE = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/"
26 dst_url = u"http://%(dst_host)s):%(dst_port)d/%(dst_name)s" % locals()26PAIRED_SERVER_RECORD_TYPE = RECTYPE_BASE + "paired_server"
27 doc_data = dict(source_database=src_name.encode("utf8"),27MY_ID_RECORD_TYPE = RECTYPE_BASE + "server_identity"
28 target_database=dst_url.encode("utf8"))28
2929def _get_db(name, create=True):
30 document = json.dumps(doc_data) # json doesn't mention Unicode.30 port = desktopcouch_find_port() # make sure d-c is running.
3131 return server.CouchDatabase(name, create=create)
32 req = urllib2.Request("http://localhost:%d/_replicate" % (port,), document)32
33 33def get_database_names_replicatable():
34 """Find a list of local databases, minus dbs that we do not want to
35 replicate (explicitly or implicitly)."""
36
37 port = int(desktopcouch_find_port())
38 couchdb_server = server.Server("http://localhost:%(port)d/" % locals())
39 all = set([db_name for db_name in couchdb_server])
40
41 excluded = set()
42 excluded.add("management")
43 excluded_msets = _get_management_data(PAIRED_SERVER_RECORD_TYPE,
44 "excluded_names")
45 for excluded_mset in excluded_msets:
46 excluded.update(excluded_mset)
47
48 return all - excluded
49
50def get_my_host_unique_id():
51 """Returns a list of ids we call ourselves. We complain in the log if it's
52 more than one, but it's really no error. If there are zero (id est, we've
53 never paired with anyone), then returns None."""
54
55 db = _get_db("management")
56 ids = _get_management_data(MY_ID_RECORD_TYPE, "self_identity")
57 ids = list(set(ids)) # uniqify
58 if len(ids) > 1:
59 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.")
60 return ids
61 if len(ids) == 1:
62 return ids
63 return None
64
65def get_local_paired_uuids():
66 results = _get_management_data(PAIRED_SERVER_RECORD_TYPE, "pairing_identifier")
67 return results
68
69def _get_management_data(record_type, key):
70 db = _get_db("management")
71 results = db.get_records(create_view=True)
72 values = list()
73 for record in results[record_type]:
74 if key in record.value: # EAFP, rather than LBYL? Nones default?
75 value = record.value[key]
76 if value is not None:
77 values.append(value)
78 else:
79 logging.debug("skipping record empty %s", key)
80 else:
81 logging.debug("skipping record with no %s", key)
82 logging.debug("found %d %s records", len(values), key)
83 return values
84
85def create_remote_database(dst_host, dst_port, dst_name):
86 dst_url = u"http://%(dst_host)s:%(dst_port)d/" % locals()
87 return server.CouchDatabase(dst_name, dst_url, create=True)
88
89def replicate(source_database, target_database, target_host=None,
90 target_port=None, source_host=None, source_port=None):
91 """This replication is instant and blocking, and does not persist. """
92
93 data = {}
94
95 if source_host:
96 if source_port:
97 source = "http://%(source_host)s/%(source_database)s" % locals()
98 else:
99 source = "http://%(source_host)s:%(source_port)d/%(source_database)s" % locals()
100 else:
101 source = source_database
102
103 if target_host:
104 if target_port:
105 target = "http://%(target_host)s/%(target_database)s" % locals()
106 else:
107 target = "http://%(target_host)s:%(target_port)d/%(target_database)s" % locals()
108 else:
109 target = target_database
110
111 record = dict(source=source, target=target)
34 try:112 try:
35 conn = urllib2.urlopen(req)113 if target_host:
36 logging.info("couchdb request resulted in %r", conn.read())114 # Remote databases must exist before replicating to them.
115 create_remote_database(target_host, target_port, target_database)
116
117 # TODO: Get admin username and password from keyring. Populate URL.
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
40131
=== 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 13:59:05 +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 desktopcouch.pair.couchdb_pairing 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,128 @@
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__(
113 stype=location_discovery_service_type, *args, **kwargs)
114
115class PairAdvertisement(Advertisement):
116 """An advertised couchdb pairing opportunity. See Advertisement class."""
117 def __init__(self, *args, **kwargs):
118 if "stype" in kwargs:
119 kwargs.pop(stype)
120 super(PairAdvertisement, self).__init__(
121 stype=invitations_discovery_service_type, *args, **kwargs)
122
123def avahitext_to_dict(avahitext):
124 text = {}
125 for l in avahitext:
126 try:
127 k, v = "".join(chr(i) for i in l).split("=", 1)
128 text[k] = v
129 except ValueError, e:
130 logging.error("k/v field could not be decoded. %s", e)
131 return text
132
133
134nearby_desktop_couch_instances = dict() # k=uuid, v=(addr, port)
135
136def cb_found_desktopcouch_server(uuid, host_address, port):
137 nearby_desktop_couch_instances[uuid] = (unicode(host_address), int(port))
138
139def cb_lost_desktopcouch_server(uuid):
140 try:
141 del nearby_desktop_couch_instances[uuid]
142 except KeyError:
143 pass
144
145def get_seen_paired_hosts():
146 paired_uuids = couchdb_io.get_local_paired_uuids()
147 return (
148 (uuid, addr, port)
149 for uuid, (addr, port)
150 in nearby_desktop_couch_instances.iteritems()
151 if uuid in paired_uuids)
152
153def maintain_discovered_servers(add_cb=cb_found_desktopcouch_server,
154 del_cb=cb_lost_desktopcouch_server):
155
156 def remove_item_handler(interface, protocol, name, stype, domain, flags):
157 """A service disappeared."""
158
159 def handle_error(*args):
160 """An error in resolving a new service."""
161 logging.error("zeroconf ItemNew error for services, %s", args)
162
163 def handle_resolved(*args):
164 """Successfully resolved a new service, which we decode and send
165 back to our calling environment with the callback function."""
166
167 name, host, port = args[2], args[5], args[8]
168 if name.startswith("desktopcouch "):
169 del_cb(name[13:], host, port)
170 else:
171 logging.error("no UUID in zeroconf message, %r", args)
172
173 del_cb(uuid)
174
175 server.ResolveService(interface, protocol, name, stype,
176 domain, avahi.PROTO_UNSPEC, dbus.UInt32(0),
177 reply_handler=handle_resolved, error_handler=handle_error)
178
179 def new_item_handler(interface, protocol, name, stype, domain, flags):
180 """A service appeared."""
181
182 def handle_error(*args):
183 """An error in resolving a new service."""
184 logging.error("zeroconf ItemNew error for services, %s", args)
185
186 def handle_resolved(*args):
187 """Successfully resolved a new service, which we decode and send
188 back to our calling environment with the callback function."""
189
190 name, host, port = args[2], args[5], args[8]
191 # FIXME strip off "desktopcouch "
192 if name.startswith("desktopcouch "):
193 add_cb(name[13:], host, port)
194 else:
195 logging.error("no UUID in zeroconf message, %r", name)
196 return True
197
198 server.ResolveService(interface, protocol, name, stype,
199 domain, avahi.PROTO_UNSPEC, dbus.UInt32(0),
200 reply_handler=handle_resolved, error_handler=handle_error)
201
202 bus, server = get_dbus_bus_server()
203 domain_name = get_local_hostname()[1]
204 browser = server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC,
205 location_discovery_service_type, domain_name, dbus.UInt32(0))
206 browser_name = bus.get_object(avahi.DBUS_NAME, browser)
207
208 sbrowser = dbus.Interface(browser_name,
209 avahi.DBUS_INTERFACE_SERVICE_BROWSER)
210 sbrowser.connect_to_signal("ItemNew", new_item_handler)
211 sbrowser.connect_to_signal("ItemRemove", remove_item_handler)
212 sbrowser.connect_to_signal("Failure",
213 lambda *a: logging.error("avahi error %r", a))
214
104215
105def discover_services(add_commport_name_cb, del_commport_name_cb,216def discover_services(add_commport_name_cb, del_commport_name_cb,
106 show_local=False):217 show_local=False):
@@ -125,13 +236,10 @@
125 def handle_resolved(*args):236 def handle_resolved(*args):
126 """Successfully resolved a new service, which we decode and send237 """Successfully resolved a new service, which we decode and send
127 back to our calling environment with the callback function."""238 back to our calling environment with the callback function."""
128 text = {}239 text = avahitext_to_dict(args[9])
129 for l in args[9]:240 name, host, port = args[2], args[5], args[8]
130 k, v = "".join(chr(i) for i in l).split("=", 1)241 add_commport_name_cb(name, text.get("description", "?"),
131 text[k] = v242 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))
135243
136 if not show_local and flags & avahi.LOOKUP_RESULT_LOCAL:244 if not show_local and flags & avahi.LOOKUP_RESULT_LOCAL:
137 return245 return
@@ -142,14 +250,15 @@
142250
143251
144 bus, server = get_dbus_bus_server()252 bus, server = get_dbus_bus_server()
145
146 domain_name = get_local_hostname()[1]253 domain_name = get_local_hostname()[1]
147254
148 browser = server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC,255 browser = server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC,
149 discovery_service_type, domain_name, dbus.UInt32(0))256 invitations_discovery_service_type, domain_name, dbus.UInt32(0))
150 browser_name = bus.get_object(avahi.DBUS_NAME, browser)257 browser_name = bus.get_object(avahi.DBUS_NAME, browser)
151258
152 sbrowser = dbus.Interface(browser_name,259 sbrowser = dbus.Interface(browser_name,
153 avahi.DBUS_INTERFACE_SERVICE_BROWSER)260 avahi.DBUS_INTERFACE_SERVICE_BROWSER)
154 sbrowser.connect_to_signal("ItemNew", new_item_handler)261 sbrowser.connect_to_signal("ItemNew", new_item_handler)
155 sbrowser.connect_to_signal("ItemRemove", remove_item_handler)262 sbrowser.connect_to_signal("ItemRemove", remove_item_handler)
263 sbrowser.connect_to_signal("Failure",
264 lambda *a: logging.error("avahi error %r", a))

Subscribers

People subscribed via source and target branches