Merge lp://qastaging/~ken-vandine/desktopcouch/desktopcouch-dbus-activated into lp://qastaging/desktopcouch

Proposed by Ken VanDine
Status: Merged
Approved by: Elliot Murphy
Approved revision: 34
Merged at revision: not available
Proposed branch: lp://qastaging/~ken-vandine/desktopcouch/desktopcouch-dbus-activated
Merge into: lp://qastaging/desktopcouch
Diff against target: None lines
To merge this branch: bzr merge lp://qastaging/~ken-vandine/desktopcouch/desktopcouch-dbus-activated
Reviewer Review Type Date Requested Status
Elliot Murphy (community) Approve
Review via email: mp+9338@code.qastaging.launchpad.net

This proposal supersedes a proposal from 2009-07-24.

Commit message

A collection of cleanups in preparation for packaging:

Bug #399019: losf "command of doom" in advertisePort doesn't work on Karmic (Medium – Confirmed)
Bug #400157: provide dbus api to couchdb port (High – Incomplete)
Don't fallback to system couchdb.
Use xdg to find the path for the bookmark template.
Added a shortcut file.

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

Hardcoded paths are bad, mmmkay. For the tmpl file, I'd suggest using xdg.BaseDirectory to look in XDG_DATA_DIRS. For the script, it probably also doesn't belong in /usr/bin, as it's something the user shouldn't be running. Though Python distutils/setuptools doesn't handle $libexecdir I don't think, so this can be annoying. You will also of course, need to update setup.py to install stuff properly, since it doesn't now.

236 - bookmark_template = os.path.join(os.path.split(__file__)[0], "couchdb.tmpl")
237 + if os.path.exists("../utilities/couchdb.html"):
238 + bookmark_template = os.path.join(os.path.split(__file__)[0], "couchdb.tmpl")
239 + else:
240 + bookmark_template = os.path.join(os.path.split(__file__)[0], "/usr/share/desktopcouch/couchdb.tmpl")

I don't know if the html is a typo there or not. I suspect so, since you're not adding that file, and calling it tmpl elsewhere. And you should use the same path for os.path.exists() and assigning to bookmark_template, rather than creating a different path string. (use os.path.exists(os.path.join(os.path.split(__file__)[0], "couchdb.tmpl")) instead)

review: Needs Fixing
Revision history for this message
Elliot Murphy (statik) wrote :

Looks great, thanks for working on this. Should the copyright and header text be corrected for the pot file?

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'MANIFEST.in'
2--- MANIFEST.in 2009-07-09 20:17:15 +0000
3+++ MANIFEST.in 2009-07-24 20:15:37 +0000
4@@ -1,3 +1,2 @@
5 include COPYING COPYING.LESSER README
6-recursive-include desktopcouche *.tmpl
7-recursive-include utilities *.py
8+recursive-include data *.tmpl
9
10=== added directory 'bin'
11=== renamed file 'desktopcouch/pair/pair.py' => 'bin/desktopcouch-pair'
12--- desktopcouch/pair/pair.py 2009-07-10 22:13:01 +0000
13+++ bin/desktopcouch-pair 2009-07-27 18:16:52 +0000
14@@ -1,5 +1,4 @@
15-#!/bin/sh
16-""""exec ${PYTHON:-python} -t $0 "$@";" """
17+#!/usr/bin/python
18 # Copyright 2009 Canonical Ltd.
19 #
20 # This file is part of desktopcouch.
21@@ -63,8 +62,8 @@
22 import gobject
23 import pango
24
25-from couchdb_pairing import network_io
26-from couchdb_pairing import dbus_io
27+from desktopcouch.pair.couchdb_pairing import network_io
28+from desktopcouch.pair.couchdb_pairing import dbus_io
29
30 discovery_tool_version = "1"
31
32
33=== renamed file 'utilities/advertisePort.py' => 'bin/desktopcouch-service' (properties changed: -x to +x)
34--- utilities/advertisePort.py 2009-07-21 19:14:38 +0000
35+++ bin/desktopcouch-service 2009-07-24 19:32:52 +0000
36@@ -34,6 +34,7 @@
37
38 """
39
40+import desktopcouch
41 from desktopcouch import local_files
42
43 import re
44@@ -65,85 +66,8 @@
45 self.death()
46
47 if __name__ == "__main__":
48- # Work out whether CouchDB is running by looking at its pid file
49- fp = open(local_files.FILE_PID)
50- pid = int(fp.read())
51- fp.close()
52- try:
53- os.kill(pid, 0)
54- except os.error, detail:
55- if detail.errno == errno.ESRCH:
56- # pidfile is stale
57- # start CouchDB by running the startup script
58- print "Desktop CouchDB is not running; starting it."
59- import start_local_couchdb
60- start_local_couchdb.start_couchdb()
61- time.sleep(2) # give the process a chance to start
62-
63- # Look in the CouchDB log to find the port number, someday.
64- # Currently, we have to grovel around in /proc instead.
65- # Oh, the huge manatee... (this replaced an lsof shell recipe
66- # which was shorter but less reliable)
67-
68- # get the pid
69- try:
70- with open(local_files.FILE_PID) as pid_file:
71- pid = int(pid_file.read().strip())
72- except IOError, e:
73- if e.errno == ENOENT:
74- raise RuntimeError("desktop-couch not started")
75- else:
76- raise
77- proc_dir = "/proc/%s" % (pid,)
78-
79- # check to make sure that the process still exists
80- if not os.path.isdir(proc_dir):
81- raise RuntimeError("Stale PID file or desktop-couch crashed")
82-
83- # check to make sure it is actually a desktop-couch instance
84- with open(os.path.join(proc_dir, 'cmdline')) as cmd_file:
85- cmd = cmd_file.read()
86- if re.search('desktop-couch', cmd) is None:
87- raise RuntimeError("Stale PID file")
88-
89- # enumerate the process' file descriptors
90- fd_dir = os.path.join(proc_dir, 'fd')
91- fd_paths = [os.readlink(os.path.join(fd_dir, fd))
92- for fd in os.listdir(fd_dir)]
93-
94- # identify socket fds
95- socket_matches = [re.match('socket:\\[([0-9]+)\\]', p) for p in fd_paths]
96- # extract their inode numbers
97- socket_inodes = [m.group(1) for m in socket_matches if m is not None]
98-
99- # construct a subexpression which matches any one of these inodes
100- inode_subexp = "|".join(socket_inodes)
101- # construct regexp to match /proc/net/tcp entries which are listening
102- # sockets having one of the given inode numbers
103- listening_regexp = re.compile(r'''
104- \s*\d+:\s* # sl
105- 0100007F:([0-9A-F]{4})\s+ # local_address
106- 00000000:0000\s+ # rem_address
107- 0A\s+ # st (0A = listening)
108- [0-9A-F]{8}: # tx_queue
109- [0-9A-F]{8}\s+ # rx_queue
110- [0-9A-F]{2}: # tr
111- [0-9A-F]{8}\s+ # tm->when
112- [0-9A-F]{8}\s* # retrnsmt
113- \d+\s+\d+\s+ # uid, timeout
114- (?:%s)\s+ # inode
115- ''' % (inode_subexp,), re.VERBOSE)
116-
117- # extract the TCP port from the first matching line in /proc/$pid/net/tcp
118- port = None
119- with open(os.path.join(proc_dir, 'net', 'tcp')) as tcp_file:
120- for line in tcp_file:
121- match = listening_regexp.match(line)
122- if match is not None:
123- port = int(match.group(1), 16)
124- break
125- if port is None:
126- raise RuntimeError("Unable to find listening port")
127+ pid = desktopcouch.find_pid()
128+ port = desktopcouch.find_port(pid)
129
130 # Advertise the port
131 mainloop = gobject.MainLoop()
132
133=== renamed file 'utilities/stop_local_couchdb.py' => 'bin/desktopcouch-stop'
134=== renamed directory 'utilities' => 'data'
135=== added file 'desktopcouch-pair.desktop.in'
136--- desktopcouch-pair.desktop.in 1970-01-01 00:00:00 +0000
137+++ desktopcouch-pair.desktop.in 2009-07-27 19:18:55 +0000
138@@ -0,0 +1,8 @@
139+[Desktop Entry]
140+_Name=CouchDB Pairing Tool
141+Type=Application
142+_Comment=Utility for pairing Desktop CouchDB
143+Exec=desktopcouch-pair
144+Icon=
145+Categories=Network;
146+_GenericName=CouchDB Pairing Tool
147
148=== modified file 'desktopcouch/__init__.py'
149--- desktopcouch/__init__.py 2009-07-08 17:48:11 +0000
150+++ desktopcouch/__init__.py 2009-07-24 19:32:52 +0000
151@@ -14,3 +14,95 @@
152 # You should have received a copy of the GNU Lesser General Public License
153 # along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
154 "Desktop Couch helper files"
155+
156+import os
157+import re
158+from desktopcouch import local_files
159+import errno
160+import time
161+
162+def find_pid():
163+ # Work out whether CouchDB is running by looking at its pid file
164+ fp = open(local_files.FILE_PID)
165+ pid = int(fp.read())
166+ fp.close()
167+ try:
168+ os.kill(pid, 0)
169+ except os.error, detail:
170+ if detail.errno == errno.ESRCH:
171+ # pidfile is stale
172+ # start CouchDB by running the startup script
173+ print "Desktop CouchDB is not running; starting it."
174+ from desktopcouch import start_local_couchdb
175+ start_local_couchdb.start_couchdb()
176+ time.sleep(2) # give the process a chance to start
177+
178+ # get the pid
179+ try:
180+ with open(local_files.FILE_PID) as pid_file:
181+ pid = int(pid_file.read().strip())
182+ except IOError, e:
183+ if e.errno == ENOENT:
184+ raise RuntimeError("desktop-couch not started")
185+ else:
186+ raise
187+ return pid
188+
189+def find_port(pid):
190+ # Look in the CouchDB log to find the port number, someday.
191+ # Currently, we have to grovel around in /proc instead.
192+ # Oh, the huge manatee... (this replaced an lsof shell recipe
193+ # which was shorter but less reliable)
194+
195+ proc_dir = "/proc/%s" % (pid,)
196+
197+ # check to make sure that the process still exists
198+ if not os.path.isdir(proc_dir):
199+ raise RuntimeError("Stale PID file or desktop-couch crashed")
200+
201+ # check to make sure it is actually a desktop-couch instance
202+ with open(os.path.join(proc_dir, 'cmdline')) as cmd_file:
203+ cmd = cmd_file.read()
204+ if re.search('desktop-couch', cmd) is None:
205+ raise RuntimeError("Stale PID file")
206+
207+ # enumerate the process' file descriptors
208+ fd_dir = os.path.join(proc_dir, 'fd')
209+ fd_paths = [os.readlink(os.path.join(fd_dir, fd))
210+ for fd in os.listdir(fd_dir)]
211+
212+ # identify socket fds
213+ socket_matches = [re.match('socket:\\[([0-9]+)\\]', p) for p in fd_paths]
214+ # extract their inode numbers
215+ socket_inodes = [m.group(1) for m in socket_matches if m is not None]
216+
217+ # construct a subexpression which matches any one of these inodes
218+ inode_subexp = "|".join(socket_inodes)
219+ # construct regexp to match /proc/net/tcp entries which are listening
220+ # sockets having one of the given inode numbers
221+ listening_regexp = re.compile(r'''
222+ \s*\d+:\s* # sl
223+ 0100007F:([0-9A-F]{4})\s+ # local_address
224+ 00000000:0000\s+ # rem_address
225+ 0A\s+ # st (0A = listening)
226+ [0-9A-F]{8}: # tx_queue
227+ [0-9A-F]{8}\s+ # rx_queue
228+ [0-9A-F]{2}: # tr
229+ [0-9A-F]{8}\s+ # tm->when
230+ [0-9A-F]{8}\s* # retrnsmt
231+ \d+\s+\d+\s+ # uid, timeout
232+ (?:%s)\s+ # inode
233+ ''' % (inode_subexp,), re.VERBOSE)
234+
235+ # extract the TCP port from the first matching line in /proc/$pid/net/tcp
236+ port = None
237+ with open(os.path.join(proc_dir, 'net', 'tcp')) as tcp_file:
238+ for line in tcp_file:
239+ match = listening_regexp.match(line)
240+ if match is not None:
241+ port = int(match.group(1), 16)
242+ break
243+ if port is None:
244+ raise RuntimeError("Unable to find listening port")
245+
246+ return str(port)
247
248=== modified file 'desktopcouch/local_files.py'
249--- desktopcouch/local_files.py 2009-07-21 19:14:38 +0000
250+++ desktopcouch/local_files.py 2009-07-24 18:34:34 +0000
251@@ -66,7 +66,7 @@
252 sys.exit(1)
253
254 def couch_chain_flag():
255- process = subprocess.Popen([COUCH_EXE, '-V'], shell=True,
256+ process = subprocess.Popen([COUCH_EXE, '-V'], shell=False,
257 stdout=subprocess.PIPE)
258 line = process.stdout.read().split('\n')[0]
259 couchversion = line.split()[-1]
260@@ -82,4 +82,3 @@
261 COUCH_EXEC_COMMAND = [COUCH_EXE, couch_chain_flag(), FILE_INI, '-p', FILE_PID,
262 '-o', FILE_STDOUT, '-e', FILE_STDERR]
263
264-
265
266=== modified file 'desktopcouch/records/server.py'
267--- desktopcouch/records/server.py 2009-07-13 12:22:21 +0000
268+++ desktopcouch/records/server.py 2009-07-27 15:40:59 +0000
269@@ -22,8 +22,8 @@
270
271 from couchdb import Server
272 from couchdb.client import ResourceNotFound, ResourceConflict
273-
274-from record import Record
275+import desktopcouch
276+from desktopcouch.record import Record
277
278
279 class NoSuchDatabase(Exception):
280@@ -43,7 +43,8 @@
281
282 def __init__(self, database, uri=None, record_factory=None, create=False):
283 if not uri:
284- port = self._find_couchdb_port()
285+ pid = desktopcouch.find_pid()
286+ port = desktopcouch.find_port(pid)
287 self.server_uri = "http://localhost:%s" % port
288 else:
289 self.server_uri = uri
290@@ -56,29 +57,6 @@
291 self.db = self._server[database]
292 self.record_factory = record_factory or Record
293
294- def _find_couchdb_port(self):
295- """Look for a running Desktop CouchDB. If there isn't one, fall back
296- to the system CouchDB on 5984."""
297- # Currently using horrible lsof command; this will eventually use D-Bus
298- # pylint: disable-msg=W0702
299- try:
300- import subprocess
301- from desktopcouch import local_files
302- lsofcmd = 'lsof -i4TCP@localhost | grep $(couchdb -C %s ' + \
303- '-p %s -s | cut -d" " -f7 | cut -d "," -f1) 2>/dev/null | ' + \
304- 'grep "TCP " | grep LISTEN | ' + \
305- 'awk \'{print $8}\' | cut -d":" -f2'
306- actual_lsof_cmd = lsofcmd % (local_files.FILE_INI,
307- local_files.FILE_PID)
308- stdout, stderr = subprocess.Popen(actual_lsof_cmd, shell=True,
309- stdout=subprocess.PIPE).communicate()
310- port = stdout.strip()
311- except:
312- port = 5984
313- if not port:
314- port = 5984
315- return port
316-
317 def query(self, map_fun, reduce_fun=None, language='javascript',
318 wrapper=None, **options):
319 """Pass-through to CouchDB query"""
320
321=== renamed file 'utilities/start_local_couchdb.py' => 'desktopcouch/start_local_couchdb.py'
322--- utilities/start_local_couchdb.py 2009-07-21 19:14:38 +0000
323+++ desktopcouch/start_local_couchdb.py 2009-07-27 18:16:19 +0000
324@@ -33,7 +33,10 @@
325
326 from __future__ import with_statement
327 import os, subprocess, sys
328+import desktopcouch
329 from desktopcouch import local_files
330+import xdg.BaseDirectory
331+import time
332
333 def dump_ini(data, filename):
334 """Dump INI data with sorted sections and keywords"""
335@@ -102,21 +105,22 @@
336 def write_bookmark_file():
337 """Write out an HTML document that the user can bookmark to find their DB"""
338 bookmark_file = os.path.join(local_files.DIR_DB, "couchdb.html")
339- bookmark_template = os.path.join(os.path.split(__file__)[0], "couchdb.tmpl")
340+
341+ if os.path.exists(os.path.join(os.path.split(__file__)[0], "../data/couchdb.tmpl")):
342+ bookmark_template = os.path.join(os.path.split(__file__)[0], "../data/couchdb.tmpl")
343+ else:
344+ for base in xdg.BaseDirectory.xdg_data_dirs:
345+ template_path = os.path.join(base, "desktopcouch", "couchdb.tmpl")
346+ if os.path.exists(template_path):
347+ bookmark_template = os.path.join(os.path.split(__file__)[0], template_path)
348+
349 fp = open(bookmark_template)
350 html = fp.read()
351 fp.close()
352
353- # work out port number the horrible lsof way
354- import time
355 time.sleep(1)
356- lsofcmd = 'lsof -i4TCP@localhost | grep $(couchdb -C %s -p %s -s | ' + \
357- 'cut -d" " -f7 | cut -d "," -f1) | grep "TCP " | ' + \
358- 'grep LISTEN | awk \'{print $8}\' | cut -d":" -f2'
359- actual_lsof_cmd = lsofcmd % (local_files.FILE_INI, local_files.FILE_PID)
360- stdout, stderr = subprocess.Popen(actual_lsof_cmd, shell=True,
361- stdout=subprocess.PIPE).communicate()
362- port = stdout.strip()
363+ pid = desktopcouch.find_pid()
364+ port = desktopcouch.find_port(pid)
365
366 fp = open(bookmark_file, "w")
367 fp.write(html.replace("[[COUCHDB_PORT]]", port))
368
369=== added file 'org.desktopcouch.CouchDB.service'
370--- org.desktopcouch.CouchDB.service 1970-01-01 00:00:00 +0000
371+++ org.desktopcouch.CouchDB.service 2009-07-24 19:32:52 +0000
372@@ -0,0 +1,3 @@
373+[D-BUS Service]
374+Name=org.desktopcouch.CouchDB
375+Exec=/usr/bin/desktopcouch-service
376
377=== added directory 'po'
378=== added file 'po/desktopcouch.pot'
379--- po/desktopcouch.pot 1970-01-01 00:00:00 +0000
380+++ po/desktopcouch.pot 2009-07-27 19:18:55 +0000
381@@ -0,0 +1,103 @@
382+# SOME DESCRIPTIVE TITLE.
383+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
384+# This file is distributed under the same license as the PACKAGE package.
385+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
386+#
387+#, fuzzy
388+msgid ""
389+msgstr ""
390+"Project-Id-Version: PACKAGE VERSION\n"
391+"Report-Msgid-Bugs-To: \n"
392+"POT-Creation-Date: 2009-07-27 15:06-0400\n"
393+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
394+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
395+"Language-Team: LANGUAGE <LL@li.org>\n"
396+"MIME-Version: 1.0\n"
397+"Content-Type: text/plain; charset=CHARSET\n"
398+"Content-Transfer-Encoding: 8bit\n"
399+
400+#: ../desktopcouch-pair.desktop.in.h:1 ../bin/desktopcouch-pair.py:592
401+msgid "CouchDB Pairing Tool"
402+msgstr ""
403+
404+#: ../desktopcouch-pair.desktop.in.h:2
405+msgid "Utility for pairing Desktop CouchDB"
406+msgstr ""
407+
408+#: ../bin/desktopcouch-pair.py:153
409+#, python-format
410+msgid "Inviting %s to pair for CouchDB Pairing"
411+msgstr ""
412+
413+#: ../bin/desktopcouch-pair.py:167
414+#, python-format
415+msgid "We're inviting %s to pair with\n"
416+msgstr ""
417+
418+#: ../bin/desktopcouch-pair.py:223
419+msgid "Accepting Invitation"
420+msgstr ""
421+
422+#: ../bin/desktopcouch-pair.py:232
423+#, python-format
424+msgid "To verify your pairing with %s, enter its secret."
425+msgstr ""
426+
427+#: ../bin/desktopcouch-pair.py:260
428+msgid "Verify and connect"
429+msgstr ""
430+
431+#: ../bin/desktopcouch-pair.py:355
432+msgid "Waiting for CouchDB Pairing Invitations"
433+msgstr ""
434+
435+#: ../bin/desktopcouch-pair.py:376
436+msgid "Add 60 seconds"
437+msgstr ""
438+
439+#: ../bin/desktopcouch-pair.py:390
440+msgid "We're listening for invitations! From another\n"
441+msgstr ""
442+
443+#: ../bin/desktopcouch-pair.py:414
444+#, python-format
445+msgid "%d seconds remaining"
446+msgstr ""
447+
448+#. pylint: disable-msg=W0201
449+#: ../bin/desktopcouch-pair.py:451 ../bin/desktopcouch-pair.py:452
450+msgid "service name"
451+msgstr ""
452+
453+#: ../bin/desktopcouch-pair.py:457
454+msgid "Pick a listening host to invite it to pair with us."
455+msgstr ""
456+
457+#: ../bin/desktopcouch-pair.py:560
458+msgid "Add this host to the list for others to see?"
459+msgstr ""
460+
461+#: ../bin/desktopcouch-pair.py:564
462+msgid "Listen for invitations"
463+msgstr ""
464+
465+#: ../bin/desktopcouch-pair.py:576
466+msgid "I also know of CouchDB sessions here. Pick one "
467+msgstr ""
468+
469+#: ../bin/desktopcouch-pair.py:600
470+msgid "Copyright 2009 Canonical"
471+msgstr ""
472+
473+#. Some kind of two-phase commit would be nice here, before we say
474+#. successful.
475+#. couchdb_io.replicate_to(...)
476+#: ../bin/desktopcouch-pair.py:620
477+#, python-format
478+msgid "Paired with %(host)s"
479+msgstr ""
480+
481+#: ../bin/desktopcouch-pair.py:625
482+#, python-format
483+msgid "Successfully paired with %(host)s %(info)s."
484+msgstr ""
485
486=== modified file 'setup.py'
487--- setup.py 2009-07-09 19:44:43 +0000
488+++ setup.py 2009-07-24 20:31:01 +0000
489@@ -23,15 +23,14 @@
490 # pylint: disable-msg=E0611
491 # pylint: disable-msg=F0401
492
493-from setuptools import setup, find_packages
494+from DistUtilsExtra.auto import setup
495
496 setup(
497 name='desktopcouch',
498 version='0.0.1',
499- description='A dictionary based record representation.',
500+ description='A Desktop CouchDB instance.',
501 url='https://launchpad.net/desktopcouch',
502- license='LGPLv3',
503+ license='LGPL-3',
504 author='Stuart Langridge',
505 author_email='stuart.langridge@canonical.com',
506- packages=find_packages(),
507 )
508
509=== added file 'start-desktop-couchdb.sh'
510--- start-desktop-couchdb.sh 1970-01-01 00:00:00 +0000
511+++ start-desktop-couchdb.sh 2009-07-24 19:32:52 +0000
512@@ -0,0 +1,12 @@
513+#!/bin/bash
514+
515+if [ -f 'desktopcouch/local_files.py' ];
516+then
517+ PYTHONPATH=.
518+fi
519+
520+PYTHONPATH=$PYTHONPATH python desktopcouch/start_local_couchdb.py
521+
522+if [ "$DBUS_SESSION_BUS_ADDRESS" ]; then
523+ PYTHONPATH=$PYTHONPATH python bin/desktopcouch-service & # background, 'cos a mainloop
524+fi
525
526=== added file 'stop-desktop-couchdb.sh'
527--- stop-desktop-couchdb.sh 1970-01-01 00:00:00 +0000
528+++ stop-desktop-couchdb.sh 2009-07-24 19:32:52 +0000
529@@ -0,0 +1,16 @@
530+#!/bin/bash
531+
532+if [ -f 'desktopcouch/local_files.py' ];
533+then
534+ PYTHONPATH=.
535+fi
536+
537+# Stop Desktop CouchDB itself
538+PYTHONPATH=$PYTHONPATH python bin/desktopcouch-stop
539+
540+# Stop advertising the slave's port over D-Bus
541+if [ "$DBUS_SESSION_BUS_ADDRESS" ]; then
542+ dbus-send --session --dest=org.desktopcouch.CouchDB \
543+ --type=method_call / org.desktopcouch.CouchDB.quit
544+fi
545+

Subscribers

People subscribed via source and target branches