Merge lp:~cmiller/desktopcouch/replication-daemon into lp:desktopcouch
- replication-daemon
- Merge into trunk
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 |
Related bugs: |
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.
Description of the change
Chad Miller (cmiller) wrote : Posted in a previous version of this proposal | # |
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?
Stuart Langridge (sil) wrote : Posted in a previous version of this proposal | # |
Also, Xget_replicatio
Eric Casteleijn (thisfred) wrote : Posted in a previous version of this proposal | # |
Some long lines in bin/desktopcouc
I'd put the recordtype as a constant at the top of the file:
PAIRED_SERVER_TYPE = \
"http://
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://
PAIRED_
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_replicatio
map_js = """function(doc) { emit(doc.
view_name = "u1_replicators
design_document = "ubuntuone_
view = ViewDefinition(
view.sync(db)
results = self.execute_
return results
in dbus_io.py:
long lines, and a lot of *args, **kwargs magic (perhaps that cannot be helped)
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.
- 51. By Chad Miller
-
Use XDG module to find log directory.
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.
- 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.
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:
Eric Casteleijn (thisfred) wrote : | # |
also:
in desktopcouch-
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.
Stuart Langridge (sil) wrote : | # |
r51-56 look good to me!
Preview Diff
1 | === modified file 'bin/desktopcouch-pair' |
2 | --- bin/desktopcouch-pair 2009-08-24 19:06:43 +0000 |
3 | +++ bin/desktopcouch-pair 2009-08-26 12:30:49 +0000 |
4 | @@ -329,10 +329,11 @@ |
5 | |
6 | hostname, domainname = dbus_io.get_local_hostname() |
7 | username = getpass.getuser() |
8 | - self.advertisement = dbus_io.Advertisement(port=listen_port, |
9 | + self.advertisement = dbus_io.PairAdvertisement(port=listen_port, |
10 | name="%s-%s-%d" % (hostname, username, listen_port), |
11 | text=dict(version=str(discovery_tool_version), |
12 | description=get_host_info())) |
13 | + self.advertisement.publish() |
14 | return hostname, username, listen_port |
15 | |
16 | def __init__(self, couchdb_instance): |
17 | @@ -725,4 +726,5 @@ |
18 | |
19 | if __name__ == "__main__": |
20 | import sys |
21 | + desktopcouch_port = dbus_io.get_desktopcouch_listening_port() |
22 | main(sys.argv) |
23 | |
24 | === added file 'bin/desktopcouch-paired-replication-manager' |
25 | --- bin/desktopcouch-paired-replication-manager 1970-01-01 00:00:00 +0000 |
26 | +++ bin/desktopcouch-paired-replication-manager 2009-08-26 13:59:05 +0000 |
27 | @@ -0,0 +1,122 @@ |
28 | +#!/bin/sh |
29 | +""""exec ${PYTHON:-python} -t $0 "$@";" """ |
30 | +# vim: filetype=python expandtab smarttab |
31 | + |
32 | +# Copyright 2009 Canonical Ltd. |
33 | +# |
34 | +# This file is part of desktopcouch. |
35 | +# |
36 | +# desktopcouch is free software: you can redistribute it and/or modify |
37 | +# it under the terms of the GNU Lesser General Public License version 3 |
38 | +# as published by the Free Software Foundation. |
39 | +# |
40 | +# desktopcouch is distributed in the hope that it will be useful, |
41 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
42 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
43 | +# GNU Lesser General Public License for more details. |
44 | +# |
45 | +# You should have received a copy of the GNU Lesser General Public License |
46 | +# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>. |
47 | +# |
48 | +# Authors: Chad Miller <chad.miller@canonical.com> |
49 | + |
50 | +import os |
51 | +import json |
52 | +import threading |
53 | +import logging |
54 | +import logging.handlers |
55 | +log = logging.getLogger("main") |
56 | + |
57 | +from twisted.internet import gtk2reactor |
58 | +gtk2reactor.install() |
59 | +from twisted.internet import reactor, task |
60 | + |
61 | +import desktopcouch |
62 | +from desktopcouch.pair.couchdb_pairing import couchdb_io |
63 | +from desktopcouch.pair.couchdb_pairing import dbus_io |
64 | + |
65 | +already_replicating = False |
66 | + |
67 | +class ReplicatorThread(threading.Thread): |
68 | + def __init__(self): |
69 | + log.debug("starting up replication thread") |
70 | + super(ReplicatorThread, self).__init__() |
71 | + |
72 | + def run(self): |
73 | + global already_replicating # Fuzzy, as not really critical, |
74 | + already_replicating = True # just trying to be polite. |
75 | + try: |
76 | + for uuid, addr, port in dbus_io.get_seen_paired_hosts(): |
77 | + log.debug("host %s is seen; want to replicate to it", uuid) |
78 | + for db_name in couchdb_io.get_database_names_replicatable(): |
79 | + couchdb_io.replicate(db_name, db_name, |
80 | + target_host=addr, target_port=port) |
81 | + |
82 | + |
83 | + # TODO: get static addressed paired hosts and replicate to |
84 | + # those too. |
85 | + |
86 | + finally: |
87 | + already_replicating = False |
88 | + log.debug("finished replicating") |
89 | + |
90 | + |
91 | +def replicate_local_databases_to_paired_hosts(): |
92 | + if already_replicating: |
93 | + log.warn("haven't finished replicating before next time to start.") |
94 | + return False |
95 | + |
96 | + r = ReplicatorThread() |
97 | + r.start() |
98 | + |
99 | +def main(args): |
100 | + log_directory = os.path.expanduser("~/.cache/ubuntuone/log") |
101 | + try: |
102 | + os.makedirs(log_directory) |
103 | + except: |
104 | + pass |
105 | + rotating_log = logging.handlers.TimedRotatingFileHandler( |
106 | + os.path.join(log_directory, "desktop-couch-replication.log"), |
107 | + "midnight", 1, 14) |
108 | + rotating_log.setLevel(logging.DEBUG) |
109 | + formatter = logging.Formatter('%(asctime)s %(levelname)-8s %(message)s') |
110 | + rotating_log.setFormatter(formatter) |
111 | + logging.getLogger('').addHandler(rotating_log) |
112 | + logging.getLogger('').setLevel(logging.DEBUG) |
113 | + |
114 | + try: |
115 | + log.info("Starting.") |
116 | + |
117 | + unique_identifiers = couchdb_io.get_my_host_unique_id() |
118 | + if unique_identifiers is None: |
119 | + log.warn("No unique hostaccount id is set, so pairing not enabled.") |
120 | + sys.exit(2) |
121 | + |
122 | + port = desktopcouch.find_port() |
123 | + beacons = [dbus_io.LocationAdvertisement(port, "desktopcouch " + i) |
124 | + for i in unique_identifiers] |
125 | + for b in beacons: |
126 | + b.publish() |
127 | + |
128 | + dbus_io.discover_services(None, None, True) |
129 | + |
130 | + try: |
131 | + dbus_io.maintain_discovered_servers() |
132 | + t = task.LoopingCall(replicate_local_databases_to_paired_hosts) |
133 | + t.start(600) |
134 | + |
135 | + # TODO: port may change, so every so often, check it and |
136 | + # perhaps refresh the beacons. |
137 | + |
138 | + reactor.run() |
139 | + finally: |
140 | + for b in beacons: |
141 | + b.unpublish() |
142 | + |
143 | + finally: |
144 | + log.info("Quitting.") |
145 | + |
146 | +if __name__ == "__main__": |
147 | + import sys |
148 | + |
149 | + main(sys.argv) |
150 | |
151 | === modified file 'desktopcouch/pair/couchdb_pairing/couchdb_io.py' |
152 | --- desktopcouch/pair/couchdb_pairing/couchdb_io.py 2009-07-08 17:48:11 +0000 |
153 | +++ desktopcouch/pair/couchdb_pairing/couchdb_io.py 2009-08-26 13:59:05 +0000 |
154 | @@ -2,7 +2,7 @@ |
155 | # |
156 | # This file is part of desktopcouch. |
157 | # |
158 | -# desktopcouch is free software: you can redistribute it and/or modify |
159 | +# desktopcouch is free software: you can redistribute it and/or modify |
160 | # it under the terms of the GNU Lesser General Public License version 3 |
161 | # as published by the Free Software Foundation. |
162 | # |
163 | @@ -13,27 +13,118 @@ |
164 | # |
165 | # You should have received a copy of the GNU Lesser General Public License |
166 | # along with desktopcouch. If not, see <http://www.gnu.org/licenses/>. |
167 | +# |
168 | +# Authors: Chad Miller <chad.miller@canonical.com> |
169 | """Communicate with CouchDB.""" |
170 | |
171 | -import urllib2 |
172 | -import json |
173 | import logging |
174 | |
175 | -def replicate_to(port, src_name, dst_host, dst_port, dst_name): |
176 | - """A simple easiest-possible replication instruction for couchdb. It's |
177 | - almost certainly wrong for us.""" |
178 | - |
179 | - dst_url = u"http://%(dst_host)s):%(dst_port)d/%(dst_name)s" % locals() |
180 | - doc_data = dict(source_database=src_name.encode("utf8"), |
181 | - target_database=dst_url.encode("utf8")) |
182 | - |
183 | - document = json.dumps(doc_data) # json doesn't mention Unicode. |
184 | - |
185 | - req = urllib2.Request("http://localhost:%d/_replicate" % (port,), document) |
186 | - |
187 | +from desktopcouch import find_port as desktopcouch_find_port |
188 | +from desktopcouch.records import server |
189 | + |
190 | +RECTYPE_BASE = "http://www.freedesktop.org/wiki/Specifications/desktopcouch/" |
191 | +PAIRED_SERVER_RECORD_TYPE = RECTYPE_BASE + "paired_server" |
192 | +MY_ID_RECORD_TYPE = RECTYPE_BASE + "server_identity" |
193 | + |
194 | +def _get_db(name, create=True): |
195 | + port = desktopcouch_find_port() # make sure d-c is running. |
196 | + return server.CouchDatabase(name, create=create) |
197 | + |
198 | +def get_database_names_replicatable(): |
199 | + """Find a list of local databases, minus dbs that we do not want to |
200 | + replicate (explicitly or implicitly).""" |
201 | + |
202 | + port = int(desktopcouch_find_port()) |
203 | + couchdb_server = server.Server("http://localhost:%(port)d/" % locals()) |
204 | + all = set([db_name for db_name in couchdb_server]) |
205 | + |
206 | + excluded = set() |
207 | + excluded.add("management") |
208 | + excluded_msets = _get_management_data(PAIRED_SERVER_RECORD_TYPE, |
209 | + "excluded_names") |
210 | + for excluded_mset in excluded_msets: |
211 | + excluded.update(excluded_mset) |
212 | + |
213 | + return all - excluded |
214 | + |
215 | +def get_my_host_unique_id(): |
216 | + """Returns a list of ids we call ourselves. We complain in the log if it's |
217 | + more than one, but it's really no error. If there are zero (id est, we've |
218 | + never paired with anyone), then returns None.""" |
219 | + |
220 | + db = _get_db("management") |
221 | + ids = _get_management_data(MY_ID_RECORD_TYPE, "self_identity") |
222 | + ids = list(set(ids)) # uniqify |
223 | + if len(ids) > 1: |
224 | + 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.") |
225 | + return ids |
226 | + if len(ids) == 1: |
227 | + return ids |
228 | + return None |
229 | + |
230 | +def get_local_paired_uuids(): |
231 | + results = _get_management_data(PAIRED_SERVER_RECORD_TYPE, "pairing_identifier") |
232 | + return results |
233 | + |
234 | +def _get_management_data(record_type, key): |
235 | + db = _get_db("management") |
236 | + results = db.get_records(create_view=True) |
237 | + values = list() |
238 | + for record in results[record_type]: |
239 | + if key in record.value: # EAFP, rather than LBYL? Nones default? |
240 | + value = record.value[key] |
241 | + if value is not None: |
242 | + values.append(value) |
243 | + else: |
244 | + logging.debug("skipping record empty %s", key) |
245 | + else: |
246 | + logging.debug("skipping record with no %s", key) |
247 | + logging.debug("found %d %s records", len(values), key) |
248 | + return values |
249 | + |
250 | +def create_remote_database(dst_host, dst_port, dst_name): |
251 | + dst_url = u"http://%(dst_host)s:%(dst_port)d/" % locals() |
252 | + return server.CouchDatabase(dst_name, dst_url, create=True) |
253 | + |
254 | +def replicate(source_database, target_database, target_host=None, |
255 | + target_port=None, source_host=None, source_port=None): |
256 | + """This replication is instant and blocking, and does not persist. """ |
257 | + |
258 | + data = {} |
259 | + |
260 | + if source_host: |
261 | + if source_port: |
262 | + source = "http://%(source_host)s/%(source_database)s" % locals() |
263 | + else: |
264 | + source = "http://%(source_host)s:%(source_port)d/%(source_database)s" % locals() |
265 | + else: |
266 | + source = source_database |
267 | + |
268 | + if target_host: |
269 | + if target_port: |
270 | + target = "http://%(target_host)s/%(target_database)s" % locals() |
271 | + else: |
272 | + target = "http://%(target_host)s:%(target_port)d/%(target_database)s" % locals() |
273 | + else: |
274 | + target = target_database |
275 | + |
276 | + record = dict(source=source, target=target) |
277 | try: |
278 | - conn = urllib2.urlopen(req) |
279 | - logging.info("couchdb request resulted in %r", conn.read()) |
280 | + if target_host: |
281 | + # Remote databases must exist before replicating to them. |
282 | + create_remote_database(target_host, target_port, target_database) |
283 | + |
284 | + # TODO: Get admin username and password from keyring. Populate URL. |
285 | + |
286 | + port = int(desktopcouch_find_port()) |
287 | + url = "http://localhost:%d/" % (port,) |
288 | + |
289 | + import couchdb |
290 | + server = couchdb.client.Server(url) |
291 | + db = server["_replicate"] |
292 | + db.create(record) |
293 | + |
294 | + logging.info("successfully replicated %r", record) |
295 | except: |
296 | logging.exception("can't talk to couchdb.") |
297 | raise |
298 | |
299 | === modified file 'desktopcouch/pair/couchdb_pairing/dbus_io.py' |
300 | --- desktopcouch/pair/couchdb_pairing/dbus_io.py 2009-07-20 14:34:08 +0000 |
301 | +++ desktopcouch/pair/couchdb_pairing/dbus_io.py 2009-08-26 13:59:05 +0000 |
302 | @@ -2,7 +2,7 @@ |
303 | # |
304 | # This file is part of desktopcouch. |
305 | # |
306 | -# desktopcouch is free software: you can redistribute it and/or modify |
307 | +# desktopcouch is free software: you can redistribute it and/or modify |
308 | # it under the terms of the GNU Lesser General Public License version 3 |
309 | # as published by the Free Software Foundation. |
310 | # |
311 | @@ -13,6 +13,8 @@ |
312 | # |
313 | # You should have received a copy of the GNU Lesser General Public License |
314 | # along with desktopcouch. If not, see <http://www.gnu.org/licenses/>. |
315 | +# |
316 | +# Authors: Chad Miller <chad.miller@canonical.com> |
317 | """Communicate with DBUS and also the APIs it proxies, like Zeroconf.""" |
318 | |
319 | import logging |
320 | @@ -22,8 +24,11 @@ |
321 | import avahi |
322 | DBusGMainLoop(set_as_default=True) |
323 | |
324 | -discovery_service_type = "_couchdb_pairing_invitations._tcp" |
325 | +from desktopcouch.pair.couchdb_pairing import couchdb_io |
326 | |
327 | +invitations_discovery_service_type = "_couchdb_pairing_invitations._tcp" |
328 | +location_discovery_service_type = "_couchdb_location._tcp" |
329 | +desktopcouch_dbus_interface = "org.desktopcouch.CouchDB" |
330 | |
331 | def get_local_hostname(): |
332 | """Get the name of this host, as Unicode host and domain parts.""" |
333 | @@ -43,36 +48,34 @@ |
334 | |
335 | return hostname |
336 | |
337 | -def get_dbus_bus_server(): |
338 | +def get_desktopcouch_listening_port(): |
339 | + bus, server = get_dbus_bus_server(desktopcouch_dbus_interface) |
340 | + return server.desktopCouch.getPort() |
341 | + |
342 | +def get_dbus_bus_server(interface="root"): |
343 | """Common sequence of steps to get a Bus and Server object from DBUS.""" |
344 | bus = dbus.SystemBus() |
345 | root_name = bus.get_object(avahi.DBUS_NAME, avahi.DBUS_PATH_SERVER) |
346 | server = dbus.Interface(root_name, avahi.DBUS_INTERFACE_SERVER) |
347 | return bus, server |
348 | |
349 | - |
350 | class Advertisement(object): |
351 | """Represents an advertised service that exists on this host.""" |
352 | - |
353 | - def __init__(self, port, name="rlx!", stype=discovery_service_type, |
354 | - domain="", host="", text="(unknown)"): |
355 | + def __init__(self, port, name, stype="", domain="", host="", text={}): |
356 | + super(Advertisement, self).__init__() |
357 | |
358 | self.logging = logging.getLogger(self.__class__.__name__) |
359 | - |
360 | - super(Advertisement, self).__init__() |
361 | self.name = name |
362 | self.stype = stype |
363 | self.domain = domain |
364 | self.host = host |
365 | - self.port = port |
366 | + self.port = int(port) |
367 | if hasattr(text, "keys"): |
368 | self.text = avahi.dict_to_txt_array(text) |
369 | else: |
370 | self.text = text |
371 | |
372 | self.group = None |
373 | - |
374 | - self.publish() |
375 | |
376 | def publish(self): |
377 | """Start the advertisement.""" |
378 | @@ -87,20 +90,128 @@ |
379 | |
380 | g.Commit() |
381 | self.logging.info("starting advertising %s on port %d", |
382 | - discovery_service_type, self.port) |
383 | + self.stype, self.port) |
384 | self.group = g |
385 | |
386 | def unpublish(self): |
387 | """End the advertisement.""" |
388 | self.group.Reset() |
389 | self.logging.info("ending advertising %s on port %d", |
390 | - discovery_service_type, self.port) |
391 | + self.stype, self.port) |
392 | self.group = None |
393 | |
394 | def die(self): |
395 | """Quit.""" |
396 | self.unpublish() |
397 | |
398 | +class LocationAdvertisement(Advertisement): |
399 | + """An advertised couchdb location. See Advertisement class.""" |
400 | + def __init__(self, *args, **kwargs): |
401 | + if "stype" in kwargs: |
402 | + kwargs.pop(stype) |
403 | + super(LocationAdvertisement, self).__init__( |
404 | + stype=location_discovery_service_type, *args, **kwargs) |
405 | + |
406 | +class PairAdvertisement(Advertisement): |
407 | + """An advertised couchdb pairing opportunity. See Advertisement class.""" |
408 | + def __init__(self, *args, **kwargs): |
409 | + if "stype" in kwargs: |
410 | + kwargs.pop(stype) |
411 | + super(PairAdvertisement, self).__init__( |
412 | + stype=invitations_discovery_service_type, *args, **kwargs) |
413 | + |
414 | +def avahitext_to_dict(avahitext): |
415 | + text = {} |
416 | + for l in avahitext: |
417 | + try: |
418 | + k, v = "".join(chr(i) for i in l).split("=", 1) |
419 | + text[k] = v |
420 | + except ValueError, e: |
421 | + logging.error("k/v field could not be decoded. %s", e) |
422 | + return text |
423 | + |
424 | + |
425 | +nearby_desktop_couch_instances = dict() # k=uuid, v=(addr, port) |
426 | + |
427 | +def cb_found_desktopcouch_server(uuid, host_address, port): |
428 | + nearby_desktop_couch_instances[uuid] = (unicode(host_address), int(port)) |
429 | + |
430 | +def cb_lost_desktopcouch_server(uuid): |
431 | + try: |
432 | + del nearby_desktop_couch_instances[uuid] |
433 | + except KeyError: |
434 | + pass |
435 | + |
436 | +def get_seen_paired_hosts(): |
437 | + paired_uuids = couchdb_io.get_local_paired_uuids() |
438 | + return ( |
439 | + (uuid, addr, port) |
440 | + for uuid, (addr, port) |
441 | + in nearby_desktop_couch_instances.iteritems() |
442 | + if uuid in paired_uuids) |
443 | + |
444 | +def maintain_discovered_servers(add_cb=cb_found_desktopcouch_server, |
445 | + del_cb=cb_lost_desktopcouch_server): |
446 | + |
447 | + def remove_item_handler(interface, protocol, name, stype, domain, flags): |
448 | + """A service disappeared.""" |
449 | + |
450 | + def handle_error(*args): |
451 | + """An error in resolving a new service.""" |
452 | + logging.error("zeroconf ItemNew error for services, %s", args) |
453 | + |
454 | + def handle_resolved(*args): |
455 | + """Successfully resolved a new service, which we decode and send |
456 | + back to our calling environment with the callback function.""" |
457 | + |
458 | + name, host, port = args[2], args[5], args[8] |
459 | + if name.startswith("desktopcouch "): |
460 | + del_cb(name[13:], host, port) |
461 | + else: |
462 | + logging.error("no UUID in zeroconf message, %r", args) |
463 | + |
464 | + del_cb(uuid) |
465 | + |
466 | + server.ResolveService(interface, protocol, name, stype, |
467 | + domain, avahi.PROTO_UNSPEC, dbus.UInt32(0), |
468 | + reply_handler=handle_resolved, error_handler=handle_error) |
469 | + |
470 | + def new_item_handler(interface, protocol, name, stype, domain, flags): |
471 | + """A service appeared.""" |
472 | + |
473 | + def handle_error(*args): |
474 | + """An error in resolving a new service.""" |
475 | + logging.error("zeroconf ItemNew error for services, %s", args) |
476 | + |
477 | + def handle_resolved(*args): |
478 | + """Successfully resolved a new service, which we decode and send |
479 | + back to our calling environment with the callback function.""" |
480 | + |
481 | + name, host, port = args[2], args[5], args[8] |
482 | + # FIXME strip off "desktopcouch " |
483 | + if name.startswith("desktopcouch "): |
484 | + add_cb(name[13:], host, port) |
485 | + else: |
486 | + logging.error("no UUID in zeroconf message, %r", name) |
487 | + return True |
488 | + |
489 | + server.ResolveService(interface, protocol, name, stype, |
490 | + domain, avahi.PROTO_UNSPEC, dbus.UInt32(0), |
491 | + reply_handler=handle_resolved, error_handler=handle_error) |
492 | + |
493 | + bus, server = get_dbus_bus_server() |
494 | + domain_name = get_local_hostname()[1] |
495 | + browser = server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, |
496 | + location_discovery_service_type, domain_name, dbus.UInt32(0)) |
497 | + browser_name = bus.get_object(avahi.DBUS_NAME, browser) |
498 | + |
499 | + sbrowser = dbus.Interface(browser_name, |
500 | + avahi.DBUS_INTERFACE_SERVICE_BROWSER) |
501 | + sbrowser.connect_to_signal("ItemNew", new_item_handler) |
502 | + sbrowser.connect_to_signal("ItemRemove", remove_item_handler) |
503 | + sbrowser.connect_to_signal("Failure", |
504 | + lambda *a: logging.error("avahi error %r", a)) |
505 | + |
506 | |
507 | def discover_services(add_commport_name_cb, del_commport_name_cb, |
508 | show_local=False): |
509 | @@ -125,13 +236,10 @@ |
510 | def handle_resolved(*args): |
511 | """Successfully resolved a new service, which we decode and send |
512 | back to our calling environment with the callback function.""" |
513 | - text = {} |
514 | - for l in args[9]: |
515 | - k, v = "".join(chr(i) for i in l).split("=", 1) |
516 | - text[k] = v |
517 | - |
518 | - add_commport_name_cb(args[2], text.get("description", "?"), |
519 | - args[5], args[8], text.get("version", None)) |
520 | + text = avahitext_to_dict(args[9]) |
521 | + name, host, port = args[2], args[5], args[8] |
522 | + add_commport_name_cb(name, text.get("description", "?"), |
523 | + host, port, text.get("version", None)) |
524 | |
525 | if not show_local and flags & avahi.LOOKUP_RESULT_LOCAL: |
526 | return |
527 | @@ -142,14 +250,15 @@ |
528 | |
529 | |
530 | bus, server = get_dbus_bus_server() |
531 | - |
532 | domain_name = get_local_hostname()[1] |
533 | |
534 | browser = server.ServiceBrowserNew(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, |
535 | - discovery_service_type, domain_name, dbus.UInt32(0)) |
536 | + invitations_discovery_service_type, domain_name, dbus.UInt32(0)) |
537 | browser_name = bus.get_object(avahi.DBUS_NAME, browser) |
538 | |
539 | sbrowser = dbus.Interface(browser_name, |
540 | avahi.DBUS_INTERFACE_SERVICE_BROWSER) |
541 | sbrowser.connect_to_signal("ItemNew", new_item_handler) |
542 | sbrowser.connect_to_signal("ItemRemove", remove_item_handler) |
543 | + sbrowser.connect_to_signal("Failure", |
544 | + lambda *a: logging.error("avahi error %r", a)) |
Buggy first try at replication.