Merge lp:~cmiller/ubuntu/oneiric/desktopcouch/1.0.8-0u1 into lp:ubuntu/oneiric/desktopcouch

Proposed by Chad Miller
Status: Merged
Merged at revision: 47
Proposed branch: lp:~cmiller/ubuntu/oneiric/desktopcouch/1.0.8-0u1
Merge into: lp:ubuntu/oneiric/desktopcouch
Diff against target: 1660 lines (+521/-554)
29 files modified
MANIFEST (+126/-0)
PKG-INFO (+21/-2)
config/desktop-couch/default.ini (+3/-0)
data/epydoc.conf (+25/-0)
debian/changelog (+21/-0)
debian/control (+2/-2)
debian/patches/5-defer-dbus-service-for-plugins.patch (+0/-249)
debian/python-desktopcouch-application.install (+6/-2)
debian/python-desktopcouch-recordtypes.install (+4/-1)
debian/rules (+0/-1)
desktopcouch.egg-info/PKG-INFO (+0/-10)
desktopcouch.egg-info/SOURCES.txt (+0/-126)
desktopcouch.egg-info/dependency_links.txt (+0/-1)
desktopcouch.egg-info/top_level.txt (+0/-1)
desktopcouch/application/local_files.py (+5/-2)
desktopcouch/application/platform/linux/tests/test_keyring.py (+1/-1)
desktopcouch/application/platform/windows/tests/test_base_dirs.py (+2/-2)
desktopcouch/application/plugins/__init__.py (+13/-3)
desktopcouch/application/plugins/tests/test_plugins.py (+3/-1)
desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py (+36/-8)
desktopcouch/application/plugins/ubuntuone_pairing.py (+75/-44)
desktopcouch/application/replication.py (+1/-1)
desktopcouch/application/service.py (+51/-11)
desktopcouch/application/start_local_couchdb.py (+1/-1)
desktopcouch/application/tests/test_service.py (+96/-62)
desktopcouch/records/tests/test_mocked_server.py (+4/-3)
desktopcouch/recordtypes/contacts/tests/test_view.py (+8/-8)
setup.cfg (+4/-4)
setup.py (+13/-8)
To merge this branch: bzr merge lp:~cmiller/ubuntu/oneiric/desktopcouch/1.0.8-0u1
Reviewer Review Type Date Requested Status
Ubuntu branches Pending
Review via email: mp+75028@code.launchpad.net

Description of the change

New version:

Behavior change: Preserves Ubuntu One service through longer replication period, 10 minutes changed to 60 minutes.

Feature: Allow COUCH_INI environment variable to override /etc/ default config.

Feature: Install apport hook.

Bug fix: When ubuntuone credentials don't exist, don't wait forever and consume CPU.

Bug fix: Accept "linux3" kernel also.

Bug fix: Clean up all children when service exits.

To post a comment you must log in.
48. By Chad Miller

  - Behavior change: Preserves Ubuntu One service through longer replication
    period, 10 minutes changed to 60 minutes.
  - Feature: Allow COUCH_INI environment variable to override /etc/ default
    config.
  - Feature: Install apport hook.
  - Bug fix: When ubuntuone credentials don't exist, don't wait forever and
    consume CPU.
  - Bug fix: Accept "linux3" kernel also.
  - Bug fix: Clean up all children when service exits.

49. By Chad Miller

    consume CPU. (LP: #760236, #787583)
  - Bug fix: Accept "linux3" kernel also. (LP: #803062)
  - Bug fix: Clean up all children when service exits. (LP: #597197)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'MANIFEST'
--- MANIFEST 1970-01-01 00:00:00 +0000
+++ MANIFEST 2011-09-13 15:01:24 +0000
@@ -0,0 +1,126 @@
1# file GENERATED by distutils, do NOT edit
2COPYING
3COPYING.LESSER
4MANIFEST
5MANIFEST.in
6README
7desktopcouch-pair.desktop.in
8org.desktopcouch.CouchDB.service
9setup.cfg
10setup.py
11start-desktop-couchdb.sh
12stop-desktop-couchdb.sh
13bin/desktopcouch-get-port
14bin/desktopcouch-pair
15bin/desktopcouch-service
16bin/desktopcouch-stop
17config/desktop-couch/compulsory-auth.ini
18config/desktop-couch/default.ini
19data/couchdb.tmpl
20data/epydoc.conf
21data/source_desktopcouch.py
22desktopcouch/__init__.py
23desktopcouch/local_files.py
24desktopcouch/replication.py
25desktopcouch/service.py
26desktopcouch/start_local_couchdb.py
27desktopcouch/stop_local_couchdb.py
28desktopcouch/util.py
29desktopcouch/application/__init__.py
30desktopcouch/application/local_files.py
31desktopcouch/application/replication.py
32desktopcouch/application/server.py
33desktopcouch/application/service.py
34desktopcouch/application/start_local_couchdb.py
35desktopcouch/application/stop_local_couchdb.py
36desktopcouch/application/util.py
37desktopcouch/application/migration/__init__.py
38desktopcouch/application/migration/tests/__init__.py
39desktopcouch/application/migration/tests/test_migration.py
40desktopcouch/application/pair/__init__.py
41desktopcouch/application/pair/couchdb_pairing/__init__.py
42desktopcouch/application/pair/couchdb_pairing/couchdb_io.py
43desktopcouch/application/pair/couchdb_pairing/dbus_io.py
44desktopcouch/application/pair/couchdb_pairing/network_io.py
45desktopcouch/application/pair/tests/__init__.py
46desktopcouch/application/pair/tests/test_couchdb_io.py
47desktopcouch/application/pair/tests/test_network_io.py
48desktopcouch/application/platform/__init__.py
49desktopcouch/application/platform/linux/__init__.py
50desktopcouch/application/platform/linux/base_dirs.py
51desktopcouch/application/platform/linux/ipc.py
52desktopcouch/application/platform/linux/keyring.py
53desktopcouch/application/platform/linux/tests/__init__.py
54desktopcouch/application/platform/linux/tests/test_keyring.py
55desktopcouch/application/platform/windows/__init__.py
56desktopcouch/application/platform/windows/base_dirs.py
57desktopcouch/application/platform/windows/keyring.py
58desktopcouch/application/platform/windows/tests/__init__.py
59desktopcouch/application/platform/windows/tests/test_base_dirs.py
60desktopcouch/application/platform/windows/tests/test_keyring.py
61desktopcouch/application/plugins/__init__.py
62desktopcouch/application/plugins/ubuntuone_pairing.py
63desktopcouch/application/plugins/tests/__init__.py
64desktopcouch/application/plugins/tests/test_plugins.py
65desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py
66desktopcouch/application/replication_services/__init__.py
67desktopcouch/application/replication_services/example.py
68desktopcouch/application/replication_services/ubuntuone.py
69desktopcouch/application/tests/__init__.py
70desktopcouch/application/tests/test_local_files.py
71desktopcouch/application/tests/test_replication.py
72desktopcouch/application/tests/test_service.py
73desktopcouch/application/tests/test_start_local_couchdb.py
74desktopcouch/bookmarks/__init__.py
75desktopcouch/bookmarks/record.py
76desktopcouch/contacts/__init__.py
77desktopcouch/contacts/record.py
78desktopcouch/contacts/view.py
79desktopcouch/notes/__init__.py
80desktopcouch/notes/record.py
81desktopcouch/pair/__init__.py
82desktopcouch/pair/couchdb_pairing/__init__.py
83desktopcouch/pair/couchdb_pairing/couchdb_io.py
84desktopcouch/pair/couchdb_pairing/dbus_io.py
85desktopcouch/pair/couchdb_pairing/network_io.py
86desktopcouch/pair/couchdb_pairing/ubuntuone_pairing.py
87desktopcouch/records/__init__.py
88desktopcouch/records/database.py
89desktopcouch/records/field_registry.py
90desktopcouch/records/http.py
91desktopcouch/records/record.py
92desktopcouch/records/server.py
93desktopcouch/records/server_base.py
94desktopcouch/records/doc/an_example_application.txt
95desktopcouch/records/doc/field_registry.txt
96desktopcouch/records/doc/records.txt
97desktopcouch/records/tests/__init__.py
98desktopcouch/records/tests/test_field_registry.py
99desktopcouch/records/tests/test_mocked_server.py
100desktopcouch/records/tests/test_record.py
101desktopcouch/records/tests/test_server.py
102desktopcouch/recordtypes/__init__.py
103desktopcouch/recordtypes/bookmarks.py
104desktopcouch/recordtypes/notes.py
105desktopcouch/recordtypes/tasks.py
106desktopcouch/recordtypes/contacts/__init__.py
107desktopcouch/recordtypes/contacts/schema.txt
108desktopcouch/recordtypes/contacts/view.py
109desktopcouch/recordtypes/contacts/testing/__init__.py
110desktopcouch/recordtypes/contacts/testing/create.py
111desktopcouch/recordtypes/contacts/tests/__init__.py
112desktopcouch/recordtypes/contacts/tests/test_create.py
113desktopcouch/recordtypes/contacts/tests/test_record.py
114desktopcouch/recordtypes/contacts/tests/test_view.py
115desktopcouch/recordtypes/tests/__init__.py
116desktopcouch/recordtypes/tests/test_bookmarks.py
117desktopcouch/recordtypes/tests/test_notes.py
118desktopcouch/recordtypes/tests/test_tasks.py
119desktopcouch/replication_services/__init__.py
120desktopcouch/replication_services/example.py
121desktopcouch/replication_services/ubuntuone.py
122desktopcouch/tasks/__init__.py
123desktopcouch/tasks/record.py
124desktopcouch/tests/__init__.py
125docs/man/desktopcouch-pair.1
126po/POTFILES.in
0127
=== modified file 'PKG-INFO'
--- PKG-INFO 2011-04-08 20:42:51 +0000
+++ PKG-INFO 2011-09-13 15:01:24 +0000
@@ -1,6 +1,6 @@
1Metadata-Version: 1.01Metadata-Version: 1.1
2Name: desktopcouch2Name: desktopcouch
3Version: 1.0.73Version: 1.0.8
4Summary: A Desktop CouchDB instance.4Summary: A Desktop CouchDB instance.
5Home-page: https://launchpad.net/desktopcouch5Home-page: https://launchpad.net/desktopcouch
6Author: Stuart Langridge6Author: Stuart Langridge
@@ -8,3 +8,22 @@
8License: LGPL-38License: LGPL-3
9Description: UNKNOWN9Description: UNKNOWN
10Platform: UNKNOWN10Platform: UNKNOWN
11Requires: avahi
12Requires: couchdb
13Requires: dbus
14Requires: gnomekeyring
15Requires: gobject
16Requires: gtk
17Requires: mocker
18Requires: oauth
19Requires: pango
20Requires: pygtk
21Requires: simplejson
22Requires: twisted.internet
23Requires: twisted.protocols
24Requires: twisted.python.threadable
25Requires: ubuntu_sso
26Requires: ubuntuone.clientdefs
27Requires: ubuntuone.devtools.testcase
28Requires: xdg.BaseDirectory
29Provides: desktopcouch
1130
=== added file 'config/desktop-couch/default.ini'
--- config/desktop-couch/default.ini 1970-01-01 00:00:00 +0000
+++ config/desktop-couch/default.ini 2011-09-13 15:01:24 +0000
@@ -0,0 +1,3 @@
1[replicator]
2max_http_sessions = 1
3
04
=== added file 'data/epydoc.conf'
--- data/epydoc.conf 1970-01-01 00:00:00 +0000
+++ data/epydoc.conf 2011-09-13 15:01:24 +0000
@@ -0,0 +1,25 @@
1[epydoc] # Epydoc section marker (required by ConfigParser)
2
3# Information about the project.
4name: desktopcouch
5url: http://www.freedesktop.org/wiki/Specifications/desktopcouch
6
7# The list of modules to document. Modules can be named using
8# dotted names, module filenames, or package directory names.
9# This option may be repeated.
10modules: desktopcouch, desktopcouch.records
11exclude: test
12
13# Write html output to the directory "apidocs"
14output: html
15target: docs/html/api/
16
17
18parse: yes
19introspect: no
20
21# Include all automatically generated graphs. These graphs are
22# generated using Graphviz dot.
23graph: all
24dotpath: /usr/bin/dot
25
026
=== modified file 'debian/changelog'
--- debian/changelog 2011-08-25 09:29:15 +0000
+++ debian/changelog 2011-09-13 15:01:24 +0000
@@ -1,3 +1,24 @@
1desktopcouch (1.0.8-0ubuntu1) UNRELEASED; urgency=low
2
3 * New upstream release.
4 - Behavior change: Preserves Ubuntu One service through longer replication
5 period, 10 minutes changed to 60 minutes.
6 - Feature: Allow COUCH_INI environment variable to override /etc/ default
7 config.
8 - Feature: Install apport hook.
9 - Bug fix: When ubuntuone credentials don't exist, don't wait forever and
10 consume CPU. (LP: #760236, #787583)
11 - Bug fix: Accept "linux3" kernel also. (LP: #803062)
12 - Bug fix: Clean up all children when service exits. (LP: #597197)
13 * Update standards-version 3.9.1 to 3.9.2.
14 * Remove brace-expansion from python-desktopcouch-application.install .
15 * Remove brace-expansion from python-desktopcouch-recordtypes.install .
16 * Remove patch
17 - patches/5-defer-dbus-service-for-plugins.patch
18 * Remove deprecated CDBS simple-patch system from rules.
19
20 -- Chad MILLER <chad.miller@canonical.com> Mon, 12 Sep 2011 13:08:25 -0400
21
1desktopcouch (1.0.7-0ubuntu3) oneiric; urgency=low22desktopcouch (1.0.7-0ubuntu3) oneiric; urgency=low
223
3 * debian/rules: langpack.mk does not exist any more, call dh_translations24 * debian/rules: langpack.mk does not exist any more, call dh_translations
425
=== modified file 'debian/control'
--- debian/control 2011-01-12 15:08:25 +0000
+++ debian/control 2011-09-13 15:01:24 +0000
@@ -8,7 +8,7 @@
8 python-distutils-extra (>= 2.8),8 python-distutils-extra (>= 2.8),
9 python-setuptools9 python-setuptools
10Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>10Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
11Standards-Version: 3.9.111Standards-Version: 3.9.2
12XS-Python-Version: current12XS-Python-Version: current
13Homepage: http://launchpad.net/desktopcouch13Homepage: http://launchpad.net/desktopcouch
1414
@@ -20,7 +20,7 @@
20 couchdb-bin (>= 0.10.0-0ubuntu3),20 couchdb-bin (>= 0.10.0-0ubuntu3),
21 python-desktopcouch-application (= ${source:Version})21 python-desktopcouch-application (= ${source:Version})
22Breaks: gwibber-service (<< 2.31.0), gwibber (<< 2.31.0)22Breaks: gwibber-service (<< 2.31.0), gwibber (<< 2.31.0)
23Description: A Desktop CouchDB instance23Description: Desktop CouchDB instance
24 Runs an instance of CouchDB with the user's session. Includes python library24 Runs an instance of CouchDB with the user's session. Includes python library
25 for interacting with database.25 for interacting with database.
2626
2727
=== removed file 'debian/patches/5-defer-dbus-service-for-plugins.patch'
--- debian/patches/5-defer-dbus-service-for-plugins.patch 2011-04-15 23:48:39 +0000
+++ debian/patches/5-defer-dbus-service-for-plugins.patch 1970-01-01 00:00:00 +0000
@@ -1,249 +0,0 @@
1Description: Make plugins be able to stall dbus service from signaling that
2 couchdb is ready to use.
3Bug-Ubuntu: https://bugs.edge.launchpad.net/ubuntu/+source/desktopcouch/+bug/760236
4Applied-upstream: bzr id chad.miller@canonical.com-20110412173802-flmya9dma0et83qr
5Author: Chad Miller <chad.miller@canonical.com>
6
7
8=== modified file 'desktopcouch/application/plugins/__init__.py'
9--- desktopcouch/application/plugins/__init__.py 2011-02-02 18:15:53 +0000
10+++ desktopcouch/application/plugins/__init__.py 2011-04-15 23:40:28 +0000
11@@ -19,8 +19,18 @@
12 DESKTOPCOUCH_PLUGIN_PATHS = [os.path.join(os.path.dirname(__file__))]
13
14
15-def load_plugins(couchdb_port):
16- """Load the desktopcouch application plug-ins."""
17+def load_plugins(couchdb_port, blocking_semaphores, gobject):
18+ """Load the desktopcouch application plug-ins.
19+
20+ The blocking_semaphores set is OPTIONALLY mutated by any plugin to signal
21+ that the service is not ready until a plugin has finished its asynchronous
22+ operations. Plugins may add a distinguishing object to the set, and it
23+ must remove what it adds when it is finished.
24+
25+ couchdb -- the integer of the port number of the running couchdb
26+ blocking_semaphores -- the set() of semaphores, which we will mutate
27+ gobject -- the mainloop module. always 'gobject' except when testing.
28+ """
29 plugin_names = set()
30 for path in DESKTOPCOUCH_PLUGIN_PATHS:
31 try:
32@@ -37,6 +47,6 @@
33 modpath = name.replace(os.path.sep, '.')[:-3]
34 try:
35 plugin = __import__(modpath, None, None, [''])
36- plugin.plugin_init(couchdb_port)
37+ plugin.plugin_init(couchdb_port, blocking_semaphores, gobject)
38 except (ImportError, AttributeError):
39 logging.warning('Failed to load plug-in: %s', modpath)
40
41=== modified file 'desktopcouch/application/plugins/ubuntuone_pairing.py'
42--- desktopcouch/application/plugins/ubuntuone_pairing.py 2011-02-02 18:15:53 +0000
43+++ desktopcouch/application/plugins/ubuntuone_pairing.py 2011-04-15 23:41:29 +0000
44@@ -24,6 +24,7 @@
45 from desktopcouch.application.server import DesktopDatabase
46 from ubuntuone.clientdefs import APP_NAME
47
48+PLUGIN_NAME = __name__
49 U1_PAIR_RECORD = "ubuntu_one_pair_record"
50 MAP_JS = """function(doc) {
51 if (doc.service_name == "ubuntuone") {
52@@ -33,48 +34,57 @@
53 """
54
55
56-def pair_with_ubuntuone(couchdb_port, management_db=None,
57+def pair_with_ubuntuone(couchdb_port, blocking_semaphores,
58+ management_db=None,
59 db_class=DesktopDatabase,
60 put_service_fn=put_static_paired_service):
61 """Adds a pairing record with ubuntu one when needed."""
62- # Use explicit uri so that we do not access dbus service.
63- uri = "http://localhost:%s" % (couchdb_port,)
64- if not management_db:
65- management_db = db_class("management", uri=uri, create=True, ctx=None)
66- # we indeed have credentials to add to the pairing records
67- # but first we ensure that the required view is present
68- if not management_db.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD):
69- management_db.add_view(
70- U1_PAIR_RECORD, MAP_JS, design_doc=U1_PAIR_RECORD)
71- view_results = management_db.execute_view(U1_PAIR_RECORD, U1_PAIR_RECORD)
72- pairing_found = False
73- # Results should contain either one row or no rows
74- # If there is one row, its value will be 0, meaning that there is
75- # already an Ubuntu One pairing record, or 1, meaning that there
76- # was an Ubuntu One pairing record but it has since been unpaired
77- # Only create a new record if there is not one already. Specifically,
78- # do not add the record if there is a deleted one, as this means
79- # that the user explicitly unpaired it!
80- for row in view_results:
81- pairing_found = True
82- if row.value == 1:
83- logging.debug("Not adding desktopcouch pairing since the user "
84- "has explicitly unpaired with Ubuntu One")
85- else:
86- logging.debug("Not adding desktopcouch pairing since we are "
87- "already paired")
88- if not pairing_found:
89- put_service_fn(None, "ubuntuone", uri=uri, ctx=None)
90- logging.debug("Pairing desktopcouch with Ubuntu One")
91-
92-
93-def got_new_credentials(couchdb_port, app_name, credentials):
94+ try:
95+ # Use explicit uri so that we do not access dbus service.
96+ uri = "http://localhost:%s" % (couchdb_port,)
97+ if not management_db:
98+ management_db = db_class("management", uri=uri, create=True,
99+ ctx=None)
100+ # we indeed have credentials to add to the pairing records
101+ # but first we ensure that the required view is present
102+ if not management_db.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD):
103+ management_db.add_view(
104+ U1_PAIR_RECORD, MAP_JS, design_doc=U1_PAIR_RECORD)
105+ view_results = management_db.execute_view(U1_PAIR_RECORD,
106+ U1_PAIR_RECORD)
107+ pairing_found = False
108+ # Results should contain either one row or no rows
109+ # If there is one row, its value will be 0, meaning that there is
110+ # already an Ubuntu One pairing record, or 1, meaning that there
111+ # was an Ubuntu One pairing record but it has since been unpaired
112+ # Only create a new record if there is not one already. Specifically,
113+ # do not add the record if there is a deleted one, as this means
114+ # that the user explicitly unpaired it!
115+ for row in view_results:
116+ pairing_found = True
117+ if row.value == 1:
118+ logging.debug("Not adding desktopcouch pairing since the user "
119+ "has explicitly unpaired with Ubuntu One")
120+ else:
121+ logging.debug("Not adding desktopcouch pairing since we are "
122+ "already paired")
123+ if not pairing_found:
124+ put_service_fn(None, "ubuntuone", uri=uri, ctx=None)
125+ logging.debug("Pairing desktopcouch with Ubuntu One")
126+
127+ finally:
128+ logging.info("removing semaphore for %s", PLUGIN_NAME)
129+ blocking_semaphores.discard(PLUGIN_NAME)
130+
131+
132+def got_new_credentials(couchdb_port, blocking_semaphores,
133+ app_name, credentials):
134 """Pair with Ubuntu One when we get the new credentials."""
135 if app_name == APP_NAME:
136- pair_with_ubuntuone(couchdb_port)
137-
138-
139-def listen_to_dbus(couchdb_port):
140+ pair_with_ubuntuone(couchdb_port, blocking_semaphores)
141+
142+
143+def listen_to_dbus(couchdb_port, blocking_semaphores):
144 """Set up the signal handler on D-Bus for Ubuntu One pairing."""
145 import dbus
146 bus = dbus.SessionBus()
147@@ -82,7 +92,9 @@
148 try:
149 import ubuntu_sso
150
151- receiver = lambda *args: got_new_credentials(couchdb_port, *args)
152+ receiver = lambda *args: \
153+ got_new_credentials(couchdb_port, blocking_semaphores,
154+ *args)
155
156 iface = ubuntu_sso.DBUS_CREDENTIALS_IFACE
157 bus.add_signal_receiver(handler_function=receiver,
158@@ -96,13 +108,20 @@
159 sso_backend.find_credentials(APP_NAME, {})
160 except ImportError:
161 logging.info('Ubuntu SSO is not available.')
162-
163-
164-def plugin_init(couchdb_port):
165+ blocking_semaphores.discard(PLUGIN_NAME)
166+
167+
168+def plugin_init(couchdb_port, blocking_semaphores, gobject):
169 """Set up the signal handler for pairing with Ubuntu One."""
170 logging.info('Loaded Ubuntu One extension for desktopcouch.')
171 if sys.platform == 'win32':
172 logging.warning('Windows support for Ubuntu One is not yet ready.')
173 else:
174- import gobject
175- gobject.idle_add(listen_to_dbus, couchdb_port)
176+
177+ # Signal that we are critical for desktopcouch usage, and the server
178+ # must not begin until we are finished. We are responsible for
179+ # removing this item from the list.
180+ logging.info("adding %s to to blocking semaphore list", PLUGIN_NAME)
181+ blocking_semaphores.add(PLUGIN_NAME)
182+
183+ gobject.idle_add(listen_to_dbus, couchdb_port, blocking_semaphores)
184
185=== modified file 'desktopcouch/application/service.py'
186--- desktopcouch/application/service.py 2011-02-02 18:15:53 +0000
187+++ desktopcouch/application/service.py 2011-04-15 23:42:17 +0000
188@@ -39,6 +39,7 @@
189 import logging
190 import logging.handlers
191 import signal
192+import gobject
193
194 from desktopcouch.application import local_files
195 from desktopcouch.application import replication
196@@ -84,7 +85,8 @@
197 replication_actions=replication,
198 advertiser_factory=PortAdvertiser, set_logging=set_up_logging,
199 fork=os.fork, nice=os.nice,
200- kill=os.kill, sleep=time.sleep):
201+ kill=os.kill, sleep=time.sleep, set_type=set,
202+ gobject_module=gobject):
203 self._mainloop = main_loop
204 self._pid_finder = pid_finder
205 self._port_finder = port_finder
206@@ -97,6 +99,8 @@
207 self._nice = nice
208 self._kill = kill
209 self._sleep = sleep
210+ self._set = set_type
211+ self._gobject = gobject_module
212 # pylint: enable=C0301
213
214 def _start_replicator_main(self, couchdb_port):
215@@ -112,11 +116,30 @@
216 replication.tear_down(*replication_runtime)
217
218 def _start_server_main(self, couchdb_port):
219- """Start server."""
220- self._advertiser_factory(self._mainloop.stop, self._ctx)
221+ """Start server answering DBus calls, and run plugins first."""
222+
223+ def if_all_semaphores_cleared(blocking_semaphores,
224+ func, *args, **kwargs):
225+ """Run a function if no semaphores exist, else try later."""
226+ if blocking_semaphores:
227+ return True # Make idle call try us again.
228+ else:
229+ func(*args, **kwargs)
230+ return False # Handled!
231+
232+ blocking_semaphores = self._set()
233+ load_plugins(couchdb_port, blocking_semaphores, self._gobject)
234+
235+ # Answering queries on DBus signals that we are ready for users
236+ # to connect. We mustn't begin that until every plugin has a chance
237+ # to run to completion if it needs it.
238+ self._gobject.idle_add(if_all_semaphores_cleared, blocking_semaphores,
239+ self._advertiser_factory,
240+ self._mainloop.stop,
241+ self._ctx)
242+
243 logging.debug("starting dbus main loop")
244 try:
245- load_plugins(couchdb_port)
246 self._mainloop.run()
247 finally:
248 logging.debug("ending dbus main loop")
249
2500
=== modified file 'debian/python-desktopcouch-application.install'
--- debian/python-desktopcouch-application.install 2011-01-12 15:08:25 +0000
+++ debian/python-desktopcouch-application.install 2011-09-13 15:01:24 +0000
@@ -1,5 +1,8 @@
1debian/tmp/usr/lib/*/*/desktopcouch/application/*.py1debian/tmp/usr/lib/*/*/desktopcouch/application/*.py
2debian/tmp/usr/lib/*/*/desktopcouch/application/{migration,pair,platform,replication_services}2debian/tmp/usr/lib/*/*/desktopcouch/application/migration
3debian/tmp/usr/lib/*/*/desktopcouch/application/pair
4debian/tmp/usr/lib/*/*/desktopcouch/application/platform
5debian/tmp/usr/lib/*/*/desktopcouch/application/replication_services
3debian/tmp/usr/lib/*/*/desktopcouch/application/plugins/__init__.py6debian/tmp/usr/lib/*/*/desktopcouch/application/plugins/__init__.py
4debian/tmp/usr/lib/*/*/desktopcouch/local_files.py7debian/tmp/usr/lib/*/*/desktopcouch/local_files.py
5debian/tmp/usr/lib/*/*/desktopcouch/replication.py8debian/tmp/usr/lib/*/*/desktopcouch/replication.py
@@ -7,4 +10,5 @@
7debian/tmp/usr/lib/*/*/desktopcouch/start_local_couchdb.py10debian/tmp/usr/lib/*/*/desktopcouch/start_local_couchdb.py
8debian/tmp/usr/lib/*/*/desktopcouch/stop_local_couchdb.py11debian/tmp/usr/lib/*/*/desktopcouch/stop_local_couchdb.py
9debian/tmp/usr/lib/*/*/desktopcouch/util.py12debian/tmp/usr/lib/*/*/desktopcouch/util.py
10debian/tmp/usr/lib/*/*/desktopcouch/{pair,replication_services}13debian/tmp/usr/lib/*/*/desktopcouch/pair
14debian/tmp/usr/lib/*/*/desktopcouch/replication_services
1115
=== modified file 'debian/python-desktopcouch-recordtypes.install'
--- debian/python-desktopcouch-recordtypes.install 2011-01-12 15:08:25 +0000
+++ debian/python-desktopcouch-recordtypes.install 2011-09-13 15:01:24 +0000
@@ -1,2 +1,5 @@
1debian/tmp/usr/lib/*/*/desktopcouch/recordtypes1debian/tmp/usr/lib/*/*/desktopcouch/recordtypes
2debian/tmp/usr/lib/*/*/desktopcouch/{bookmarks,contacts,notes,tasks}2debian/tmp/usr/lib/*/*/desktopcouch/bookmarks
3debian/tmp/usr/lib/*/*/desktopcouch/contacts
4debian/tmp/usr/lib/*/*/desktopcouch/notes
5debian/tmp/usr/lib/*/*/desktopcouch/tasks
36
=== modified file 'debian/rules'
--- debian/rules 2011-08-25 09:28:07 +0000
+++ debian/rules 2011-09-13 15:01:24 +0000
@@ -5,7 +5,6 @@
55
6include /usr/share/cdbs/1/rules/debhelper.mk6include /usr/share/cdbs/1/rules/debhelper.mk
7include /usr/share/cdbs/1/class/python-distutils.mk7include /usr/share/cdbs/1/class/python-distutils.mk
8include /usr/share/cdbs/1/rules/simple-patchsys.mk
98
10common-binary-post-install-indep::9common-binary-post-install-indep::
11 dh_translations10 dh_translations
1211
=== removed directory 'desktopcouch.egg-info'
=== removed file 'desktopcouch.egg-info/PKG-INFO'
--- desktopcouch.egg-info/PKG-INFO 2011-04-08 20:42:51 +0000
+++ desktopcouch.egg-info/PKG-INFO 1970-01-01 00:00:00 +0000
@@ -1,10 +0,0 @@
1Metadata-Version: 1.0
2Name: desktopcouch
3Version: 1.0.7
4Summary: A Desktop CouchDB instance.
5Home-page: https://launchpad.net/desktopcouch
6Author: Stuart Langridge
7Author-email: stuart.langridge@canonical.com
8License: LGPL-3
9Description: UNKNOWN
10Platform: UNKNOWN
110
=== removed file 'desktopcouch.egg-info/SOURCES.txt'
--- desktopcouch.egg-info/SOURCES.txt 2011-01-12 15:08:25 +0000
+++ desktopcouch.egg-info/SOURCES.txt 1970-01-01 00:00:00 +0000
@@ -1,126 +0,0 @@
1COPYING
2COPYING.LESSER
3MANIFEST.in
4README
5desktopcouch-pair.desktop.in
6org.desktopcouch.CouchDB.service
7setup.cfg
8setup.py
9start-desktop-couchdb.sh
10stop-desktop-couchdb.sh
11bin/desktopcouch-get-port
12bin/desktopcouch-pair
13bin/desktopcouch-service
14bin/desktopcouch-stop
15config/desktop-couch/compulsory-auth.ini
16data/couchdb.tmpl
17data/source_desktopcouch.py
18desktopcouch/__init__.py
19desktopcouch/local_files.py
20desktopcouch/replication.py
21desktopcouch/service.py
22desktopcouch/start_local_couchdb.py
23desktopcouch/stop_local_couchdb.py
24desktopcouch/util.py
25desktopcouch.egg-info/PKG-INFO
26desktopcouch.egg-info/SOURCES.txt
27desktopcouch.egg-info/dependency_links.txt
28desktopcouch.egg-info/top_level.txt
29desktopcouch/application/__init__.py
30desktopcouch/application/local_files.py
31desktopcouch/application/replication.py
32desktopcouch/application/server.py
33desktopcouch/application/service.py
34desktopcouch/application/start_local_couchdb.py
35desktopcouch/application/stop_local_couchdb.py
36desktopcouch/application/util.py
37desktopcouch/application/migration/__init__.py
38desktopcouch/application/migration/tests/__init__.py
39desktopcouch/application/migration/tests/test_migration.py
40desktopcouch/application/pair/__init__.py
41desktopcouch/application/pair/couchdb_pairing/__init__.py
42desktopcouch/application/pair/couchdb_pairing/couchdb_io.py
43desktopcouch/application/pair/couchdb_pairing/dbus_io.py
44desktopcouch/application/pair/couchdb_pairing/network_io.py
45desktopcouch/application/pair/tests/__init__.py
46desktopcouch/application/pair/tests/test_couchdb_io.py
47desktopcouch/application/pair/tests/test_network_io.py
48desktopcouch/application/platform/__init__.py
49desktopcouch/application/platform/linux/__init__.py
50desktopcouch/application/platform/linux/base_dirs.py
51desktopcouch/application/platform/linux/ipc.py
52desktopcouch/application/platform/linux/keyring.py
53desktopcouch/application/platform/linux/tests/__init__.py
54desktopcouch/application/platform/linux/tests/test_keyring.py
55desktopcouch/application/platform/windows/__init__.py
56desktopcouch/application/platform/windows/base_dirs.py
57desktopcouch/application/platform/windows/keyring.py
58desktopcouch/application/platform/windows/tests/__init__.py
59desktopcouch/application/platform/windows/tests/test_base_dirs.py
60desktopcouch/application/platform/windows/tests/test_keyring.py
61desktopcouch/application/plugins/__init__.py
62desktopcouch/application/plugins/ubuntuone_pairing.py
63desktopcouch/application/plugins/tests/__init__.py
64desktopcouch/application/plugins/tests/test_plugins.py
65desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py
66desktopcouch/application/replication_services/__init__.py
67desktopcouch/application/replication_services/example.py
68desktopcouch/application/replication_services/ubuntuone.py
69desktopcouch/application/tests/__init__.py
70desktopcouch/application/tests/test_local_files.py
71desktopcouch/application/tests/test_replication.py
72desktopcouch/application/tests/test_service.py
73desktopcouch/application/tests/test_start_local_couchdb.py
74desktopcouch/bookmarks/__init__.py
75desktopcouch/bookmarks/record.py
76desktopcouch/contacts/__init__.py
77desktopcouch/contacts/record.py
78desktopcouch/contacts/view.py
79desktopcouch/notes/__init__.py
80desktopcouch/notes/record.py
81desktopcouch/pair/__init__.py
82desktopcouch/pair/couchdb_pairing/__init__.py
83desktopcouch/pair/couchdb_pairing/couchdb_io.py
84desktopcouch/pair/couchdb_pairing/dbus_io.py
85desktopcouch/pair/couchdb_pairing/network_io.py
86desktopcouch/pair/couchdb_pairing/ubuntuone_pairing.py
87desktopcouch/records/__init__.py
88desktopcouch/records/database.py
89desktopcouch/records/field_registry.py
90desktopcouch/records/http.py
91desktopcouch/records/record.py
92desktopcouch/records/server.py
93desktopcouch/records/server_base.py
94desktopcouch/records/doc/an_example_application.txt
95desktopcouch/records/doc/field_registry.txt
96desktopcouch/records/doc/records.txt
97desktopcouch/records/tests/__init__.py
98desktopcouch/records/tests/test_field_registry.py
99desktopcouch/records/tests/test_mocked_server.py
100desktopcouch/records/tests/test_record.py
101desktopcouch/records/tests/test_server.py
102desktopcouch/recordtypes/__init__.py
103desktopcouch/recordtypes/bookmarks.py
104desktopcouch/recordtypes/notes.py
105desktopcouch/recordtypes/tasks.py
106desktopcouch/recordtypes/contacts/__init__.py
107desktopcouch/recordtypes/contacts/schema.txt
108desktopcouch/recordtypes/contacts/view.py
109desktopcouch/recordtypes/contacts/testing/__init__.py
110desktopcouch/recordtypes/contacts/testing/create.py
111desktopcouch/recordtypes/contacts/tests/__init__.py
112desktopcouch/recordtypes/contacts/tests/test_create.py
113desktopcouch/recordtypes/contacts/tests/test_record.py
114desktopcouch/recordtypes/contacts/tests/test_view.py
115desktopcouch/recordtypes/tests/__init__.py
116desktopcouch/recordtypes/tests/test_bookmarks.py
117desktopcouch/recordtypes/tests/test_notes.py
118desktopcouch/recordtypes/tests/test_tasks.py
119desktopcouch/replication_services/__init__.py
120desktopcouch/replication_services/example.py
121desktopcouch/replication_services/ubuntuone.py
122desktopcouch/tasks/__init__.py
123desktopcouch/tasks/record.py
124desktopcouch/tests/__init__.py
125docs/man/desktopcouch-pair.1
126po/POTFILES.in
127\ No newline at end of file0\ No newline at end of file
1281
=== removed file 'desktopcouch.egg-info/dependency_links.txt'
--- desktopcouch.egg-info/dependency_links.txt 2009-10-22 17:15:57 +0000
+++ desktopcouch.egg-info/dependency_links.txt 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1
20
=== removed file 'desktopcouch.egg-info/top_level.txt'
--- desktopcouch.egg-info/top_level.txt 2009-10-22 17:15:57 +0000
+++ desktopcouch.egg-info/top_level.txt 1970-01-01 00:00:00 +0000
@@ -1,1 +0,0 @@
1desktopcouch
20
=== modified file 'desktopcouch/application/local_files.py'
--- desktopcouch/application/local_files.py 2011-04-08 20:40:53 +0000
+++ desktopcouch/application/local_files.py 2011-09-13 15:01:24 +0000
@@ -83,9 +83,11 @@
83 if "-hashed-" in bookmark_file_contents:83 if "-hashed-" in bookmark_file_contents:
84 raise ValueError("Basic-auth cred lost.")84 raise ValueError("Basic-auth cred lost.")
85 # trial run, check sanity.85 # trial run, check sanity.
86 # pylint: disable=W0106
86 re.findall(87 re.findall(
87 "<!-- !!([^!]+)!!([^!]+)!! -->",88 "<!-- !!([^!]+)!!([^!]+)!! -->",
88 bookmark_file_contents)[-1]89 bookmark_file_contents)[-1]
90 # pylint: enable=W0106
89 self._fill_from_file(self.file_name_used)91 self._fill_from_file(self.file_name_used)
90 return92 return
91 except (IOError, ValueError, IndexError):93 except (IOError, ValueError, IndexError):
@@ -196,7 +198,7 @@
196 for d in (run_dir, db_dir, config_dir):198 for d in (run_dir, db_dir, config_dir):
197 try:199 try:
198 os.makedirs(d, 0700)200 os.makedirs(d, 0700)
199 except OSError, ex:201 except OSError:
200 pass # Probably that it already exists.202 pass # Probably that it already exists.
201 try:203 try:
202 os.chmod(d, 0700)204 os.chmod(d, 0700)
@@ -257,7 +259,8 @@
257 def couch_chain_ini_files(self):259 def couch_chain_ini_files(self):
258 """Chain couchdb ini files."""260 """Chain couchdb ini files."""
259 # Explicitly add default ini file261 # Explicitly add default ini file
260 ini_files = ["/etc/couchdb/default.ini"]262 ini_files = [os.environ.get("COUCHDB_INI") or
263 "/etc/couchdb/default.ini"]
261264
262 # find all ini files in the desktopcouch XDG_CONFIG_DIRS and265 # find all ini files in the desktopcouch XDG_CONFIG_DIRS and
263 # add them to the chain266 # add them to the chain
264267
=== modified file 'desktopcouch/application/platform/linux/tests/test_keyring.py'
--- desktopcouch/application/platform/linux/tests/test_keyring.py 2011-01-12 15:08:25 +0000
+++ desktopcouch/application/platform/linux/tests/test_keyring.py 2011-09-13 15:01:24 +0000
@@ -30,7 +30,7 @@
30try:30try:
31 import gnomekeyring31 import gnomekeyring
32except ImportError, e:32except ImportError, e:
33 if sys.platform != 'linux2':33 if not sys.platform.startswith('linux'):
34 gnomekeyring = None34 gnomekeyring = None
35 else:35 else:
36 raise e36 raise e
3737
=== modified file 'desktopcouch/application/platform/windows/tests/test_base_dirs.py'
--- desktopcouch/application/platform/windows/tests/test_base_dirs.py 2011-02-02 18:15:53 +0000
+++ desktopcouch/application/platform/windows/tests/test_base_dirs.py 2011-09-13 15:01:24 +0000
@@ -63,7 +63,7 @@
63 self.mocker.result('hive')63 self.mocker.result('hive')
64 self._winreg.OpenKey('hive', SHELL_FOLDERS_KEY)64 self._winreg.OpenKey('hive', SHELL_FOLDERS_KEY)
65 self.mocker.result('key')65 self.mocker.result('key')
66 self._winreg.QueryInfoKey('key')[1]66 self._winreg.QueryInfoKey('key')[1] # pylint: disable=W0106
67 self.mocker.throw(Exception('Cannot get info.'))67 self.mocker.throw(Exception('Cannot get info.'))
68 self._winreg.CloseKey('hive')68 self._winreg.CloseKey('hive')
69 self._winreg.CloseKey('key')69 self._winreg.CloseKey('key')
@@ -78,7 +78,7 @@
78 self.mocker.result('hive')78 self.mocker.result('hive')
79 self._winreg.OpenKey('hive', SHELL_FOLDERS_KEY)79 self._winreg.OpenKey('hive', SHELL_FOLDERS_KEY)
80 self.mocker.result('key')80 self.mocker.result('key')
81 self._winreg.QueryInfoKey('key')[1]81 self._winreg.QueryInfoKey('key')[1] # pylint: disable=W0106
82 self.mocker.result(1)82 self.mocker.result(1)
83 self._winreg.EnumValue('key', 0)83 self._winreg.EnumValue('key', 0)
84 self.mocker.result(('AppData', 'path', 1))84 self.mocker.result(('AppData', 'path', 1))
8585
=== modified file 'desktopcouch/application/plugins/__init__.py'
--- desktopcouch/application/plugins/__init__.py 2011-02-02 18:15:53 +0000
+++ desktopcouch/application/plugins/__init__.py 2011-09-13 15:01:24 +0000
@@ -19,8 +19,18 @@
19DESKTOPCOUCH_PLUGIN_PATHS = [os.path.join(os.path.dirname(__file__))]19DESKTOPCOUCH_PLUGIN_PATHS = [os.path.join(os.path.dirname(__file__))]
2020
2121
22def load_plugins(couchdb_port):22def load_plugins(couchdb_port, blocking_semaphores, gobject):
23 """Load the desktopcouch application plug-ins."""23 """Load the desktopcouch application plug-ins.
24
25 The blocking_semaphores set is OPTIONALLY mutated by any plugin to signal
26 that the service is not ready until a plugin has finished its asynchronous
27 operations. Plugins may add a distinguishing object to the set, and it
28 must remove what it adds when it is finished.
29
30 couchdb -- the integer of the port number of the running couchdb
31 blocking_semaphores -- the set() of semaphores, which we will mutate
32 gobject -- the mainloop module. always 'gobject' except when testing.
33 """
24 plugin_names = set()34 plugin_names = set()
25 for path in DESKTOPCOUCH_PLUGIN_PATHS:35 for path in DESKTOPCOUCH_PLUGIN_PATHS:
26 try:36 try:
@@ -37,6 +47,6 @@
37 modpath = name.replace(os.path.sep, '.')[:-3]47 modpath = name.replace(os.path.sep, '.')[:-3]
38 try:48 try:
39 plugin = __import__(modpath, None, None, [''])49 plugin = __import__(modpath, None, None, [''])
40 plugin.plugin_init(couchdb_port)50 plugin.plugin_init(couchdb_port, blocking_semaphores, gobject)
41 except (ImportError, AttributeError):51 except (ImportError, AttributeError):
42 logging.warning('Failed to load plug-in: %s', modpath)52 logging.warning('Failed to load plug-in: %s', modpath)
4353
=== modified file 'desktopcouch/application/plugins/tests/test_plugins.py'
--- desktopcouch/application/plugins/tests/test_plugins.py 2011-02-02 18:15:53 +0000
+++ desktopcouch/application/plugins/tests/test_plugins.py 2011-09-13 15:01:24 +0000
@@ -28,6 +28,8 @@
2828
29 def setUp(self):29 def setUp(self):
30 self.couchdb_port = platform.find_port(ctx=test_context)30 self.couchdb_port = platform.find_port(ctx=test_context)
31 self.blockers = set()
32 self.gobject = None
3133
32 def test_load_plugins(self):34 def test_load_plugins(self):
33 """Test that plug-ins are loaded correctly."""35 """Test that plug-ins are loaded correctly."""
@@ -44,7 +46,7 @@
44 self._imported = modname46 self._imported = modname
45 old_import = __import__47 old_import = __import__
46 __builtins__['__import__'] = _fake_import48 __builtins__['__import__'] = _fake_import
47 plugins.load_plugins(self.couchdb_port)49 plugins.load_plugins(self.couchdb_port, self.blockers, self.gobject)
48 __builtins__['__import__'] = old_import50 __builtins__['__import__'] = old_import
49 plugins.DESKTOPCOUCH_PLUGIN_PATHS = old_paths51 plugins.DESKTOPCOUCH_PLUGIN_PATHS = old_paths
50 self.assertEqual(self._imported, __name__)52 self.assertEqual(self._imported, __name__)
5153
=== modified file 'desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py'
--- desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py 2011-02-02 18:15:53 +0000
+++ desktopcouch/application/plugins/tests/test_ubuntuone_pairing.py 2011-09-13 15:01:24 +0000
@@ -38,9 +38,11 @@
38 self.couchdb_port = self.mocker.mock()38 self.couchdb_port = self.mocker.mock()
39 self.put_static_paired_service = self.mocker.mock()39 self.put_static_paired_service = self.mocker.mock()
40 self.database_class = self.mocker.mock()40 self.database_class = self.mocker.mock()
41 self.blocking_semaphores = self.mocker.mock()
4142
42 def test_pair_with_ubuntuone_no_view(self):43 def test_pair_with_ubuntuone_no_view(self):
43 """Test that when the view is not present it is indeed created."""44 """Test that when the view is not present it is indeed created."""
45 # plugin_init adds name to blocking semaphores, but we remove it.
44 self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD)46 self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD)
45 self.mocker.result(False)47 self.mocker.result(False)
46 # we are interested in the fact that the view is created48 # we are interested in the fact that the view is created
@@ -50,8 +52,11 @@
50 self.couchdb.execute_view(U1_PAIR_RECORD, U1_PAIR_RECORD)52 self.couchdb.execute_view(U1_PAIR_RECORD, U1_PAIR_RECORD)
51 self.mocker.result([])53 self.mocker.result([])
52 self.put_static_paired_service(None, 'ubuntuone', ctx=None, uri=ANY)54 self.put_static_paired_service(None, 'ubuntuone', ctx=None, uri=ANY)
55 self.blocking_semaphores.discard(ANY)
56
53 self.mocker.replay()57 self.mocker.replay()
54 pair_with_ubuntuone(self.couchdb_port, self.couchdb,58 pair_with_ubuntuone(self.couchdb_port, self.blocking_semaphores,
59 management_db=self.couchdb,
55 db_class=self.database_class,60 db_class=self.database_class,
56 put_service_fn=self.put_static_paired_service)61 put_service_fn=self.put_static_paired_service)
57 self.mocker.verify()62 self.mocker.verify()
@@ -59,21 +64,27 @@
59 def test_pair_with_ubuntuone_no_record(self):64 def test_pair_with_ubuntuone_no_record(self):
60 """Ensure pairing is not done when there are no records ."""65 """Ensure pairing is not done when there are no records ."""
61 # execute the steps when no records are returned by the view66 # execute the steps when no records are returned by the view
67 # plugin_init adds name to blocking semaphores, but we remove it.
62 self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD)68 self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD)
63 self.mocker.result(True)69 self.mocker.result(True)
64 self.couchdb.execute_view(U1_PAIR_RECORD, U1_PAIR_RECORD)70 self.couchdb.execute_view(U1_PAIR_RECORD, U1_PAIR_RECORD)
65 self.mocker.result([])71 self.mocker.result([])
66 self.put_static_paired_service(None, 'ubuntuone', ctx=None, uri=ANY)72 self.put_static_paired_service(None, 'ubuntuone', ctx=None, uri=ANY)
73 self.blocking_semaphores.discard(ANY)
74
67 self.mocker.replay()75 self.mocker.replay()
68 pair_with_ubuntuone(self.couchdb_port, self.couchdb,76 pair_with_ubuntuone(self.couchdb_port, self.blocking_semaphores,
77 management_db=self.couchdb,
69 db_class=self.database_class,78 db_class=self.database_class,
70 put_service_fn=self.put_static_paired_service)79 put_service_fn=self.put_static_paired_service)
80
71 self.mocker.verify()81 self.mocker.verify()
7282
73 def test_pair_with_ubuntuone_user_deleted_record(self):83 def test_pair_with_ubuntuone_user_deleted_record(self):
74 """Ensure pairing is not done when the user explicitly removed it."""84 """Ensure pairing is not done when the user explicitly removed it."""
75 # create a mock object that will be the result from the view85 # create a mock object that will be the result from the view
76 row = self.mocker.mock()86 row = self.mocker.mock()
87 # plugin_init adds name to blocking semaphores, but we remove it.
77 # execute the steps to show that the user deleted the record88 # execute the steps to show that the user deleted the record
78 self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD)89 self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD)
79 self.mocker.result(True)90 self.mocker.result(True)
@@ -82,14 +93,18 @@
82 # FIXME does this do anything?93 # FIXME does this do anything?
83 _ = row.value94 _ = row.value
84 self.mocker.result(1)95 self.mocker.result(1)
96 self.blocking_semaphores.discard(ANY)
97
85 self.mocker.replay()98 self.mocker.replay()
86 pair_with_ubuntuone(self.couchdb_port, self.couchdb)99 pair_with_ubuntuone(self.couchdb_port, self.blocking_semaphores,
100 management_db=self.couchdb)
87 self.mocker.verify()101 self.mocker.verify()
88102
89 def test_pair_with_ubuntuone_record_present(self):103 def test_pair_with_ubuntuone_record_present(self):
90 """Ensure pairing is not done when the record is already present."""104 """Ensure pairing is not done when the record is already present."""
91 # create a mock object that will be the result from the view105 # create a mock object that will be the result from the view
92 row = self.mocker.mock()106 row = self.mocker.mock()
107 # plugin_init adds name to blocking semaphores, but we remove it.
93 # execute the steps to show that the user deleted the record108 # execute the steps to show that the user deleted the record
94 self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD)109 self.couchdb.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD)
95 self.mocker.result(True)110 self.mocker.result(True)
@@ -98,8 +113,11 @@
98 # FIXME does this do anything?113 # FIXME does this do anything?
99 _ = row.value114 _ = row.value
100 self.mocker.result(0)115 self.mocker.result(0)
116 self.blocking_semaphores.discard(ANY)
117
101 self.mocker.replay()118 self.mocker.replay()
102 pair_with_ubuntuone(self.couchdb_port, self.couchdb)119 pair_with_ubuntuone(self.couchdb_port, self.blocking_semaphores,
120 management_db=self.couchdb)
103 self.mocker.verify()121 self.mocker.verify()
104122
105123
@@ -109,6 +127,7 @@
109 def setUp(self):127 def setUp(self):
110 super(TestUbuntuOnePlugin, self).setUp()128 super(TestUbuntuOnePlugin, self).setUp()
111 self.couchdb_port = self.mocker.mock()129 self.couchdb_port = self.mocker.mock()
130 self.blocking_semaphores = self.mocker.mock()
112 self.called = False131 self.called = False
113132
114 def test_got_new_credentials_other(self):133 def test_got_new_credentials_other(self):
@@ -117,21 +136,27 @@
117 """Fail if we get called."""136 """Fail if we get called."""
118 self.called = True137 self.called = True
119138
139 saved_pair = uone.pair_with_ubuntuone
120 uone.pair_with_ubuntuone = fail_if_called140 uone.pair_with_ubuntuone = fail_if_called
121 uone.got_new_credentials(self.couchdb_port, 'Unknown App', {})141 uone.got_new_credentials(self.couchdb_port, self.blocking_semaphores,
142 'Unknown App', {})
122 self.assertFalse(self.called, 'pair_with_ubuntuone was not expected.')143 self.assertFalse(self.called, 'pair_with_ubuntuone was not expected.')
123 self.mocker.replay()144 self.mocker.replay()
145 uone.pair_with_ubuntuone = saved_pair
124146
125 def test_got_new_credentials(self):147 def test_got_new_credentials(self):
126 """Check that pairing is called for Ubuntu One."""148 """Check that pairing is called for Ubuntu One."""
127 def pass_if_called(db=None):149 def pass_if_called(db=None, semaphores=None):
128 """Check that pair_with_ubuntuone was called."""150 """Check that pair_with_ubuntuone was called."""
129 self.called = True151 self.called = True
130152
153 saved_pair = uone.pair_with_ubuntuone
131 uone.pair_with_ubuntuone = pass_if_called154 uone.pair_with_ubuntuone = pass_if_called
132 uone.got_new_credentials(self.couchdb_port, uone.APP_NAME, {})155 uone.got_new_credentials(self.couchdb_port, self.blocking_semaphores,
156 uone.APP_NAME, {})
133 self.assertTrue(self.called, 'pair_with_ubuntuone was not called.')157 self.assertTrue(self.called, 'pair_with_ubuntuone was not called.')
134 self.mocker.replay()158 self.mocker.replay()
159 uone.pair_with_ubuntuone = saved_pair
135160
136 def test_listen_to_dbus(self):161 def test_listen_to_dbus(self):
137 """Test that listening to credentails works."""162 """Test that listening to credentails works."""
@@ -144,6 +169,9 @@
144169
145 iface = ubuntu_sso.DBUS_CREDENTIALS_IFACE170 iface = ubuntu_sso.DBUS_CREDENTIALS_IFACE
146 bus.add_signal_receiver(handler_function=ANY,171 bus.add_signal_receiver(handler_function=ANY,
172 signal_name='CredentialsNotFound',
173 dbus_interface=iface)
174 bus.add_signal_receiver(handler_function=ANY,
147 signal_name='CredentialsFound',175 signal_name='CredentialsFound',
148 dbus_interface=iface)176 dbus_interface=iface)
149177
@@ -161,4 +189,4 @@
161189
162 self.mocker.replay()190 self.mocker.replay()
163191
164 uone.listen_to_dbus(self.couchdb_port)192 uone.listen_to_dbus(self.couchdb_port, self.blocking_semaphores)
165193
=== modified file 'desktopcouch/application/plugins/ubuntuone_pairing.py'
--- desktopcouch/application/plugins/ubuntuone_pairing.py 2011-02-02 18:15:53 +0000
+++ desktopcouch/application/plugins/ubuntuone_pairing.py 2011-09-13 15:01:24 +0000
@@ -24,6 +24,8 @@
24from desktopcouch.application.server import DesktopDatabase24from desktopcouch.application.server import DesktopDatabase
25from ubuntuone.clientdefs import APP_NAME25from ubuntuone.clientdefs import APP_NAME
2626
27TIMEOUT_SEC = 20
28PLUGIN_NAME = __name__
27U1_PAIR_RECORD = "ubuntu_one_pair_record"29U1_PAIR_RECORD = "ubuntu_one_pair_record"
28MAP_JS = """function(doc) {30MAP_JS = """function(doc) {
29 if (doc.service_name == "ubuntuone") {31 if (doc.service_name == "ubuntuone") {
@@ -32,49 +34,57 @@
32 }34 }
33"""35"""
3436
3537def pair_with_ubuntuone(couchdb_port, blocking_semaphores,
36def pair_with_ubuntuone(couchdb_port, management_db=None,38 management_db=None,
37 db_class=DesktopDatabase,39 db_class=DesktopDatabase,
38 put_service_fn=put_static_paired_service):40 put_service_fn=put_static_paired_service):
39 """Adds a pairing record with ubuntu one when needed."""41 """Adds a pairing record with ubuntu one when needed."""
40 # Use explicit uri so that we do not access dbus service.42 try:
41 uri = "http://localhost:%s" % (couchdb_port,)43 # Use explicit uri so that we do not access dbus service.
42 if not management_db:44 uri = "http://localhost:%s" % (couchdb_port,)
43 management_db = db_class("management", uri=uri, create=True, ctx=None)45 if not management_db:
44 # we indeed have credentials to add to the pairing records46 management_db = db_class("management", uri=uri, create=True,
45 # but first we ensure that the required view is present47 ctx=None)
46 if not management_db.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD):48 # we indeed have credentials to add to the pairing records
47 management_db.add_view(49 # but first we ensure that the required view is present
48 U1_PAIR_RECORD, MAP_JS, design_doc=U1_PAIR_RECORD)50 if not management_db.view_exists(U1_PAIR_RECORD, U1_PAIR_RECORD):
49 view_results = management_db.execute_view(U1_PAIR_RECORD, U1_PAIR_RECORD)51 management_db.add_view(
50 pairing_found = False52 U1_PAIR_RECORD, MAP_JS, design_doc=U1_PAIR_RECORD)
51 # Results should contain either one row or no rows53 view_results = management_db.execute_view(U1_PAIR_RECORD,
52 # If there is one row, its value will be 0, meaning that there is54 U1_PAIR_RECORD)
53 # already an Ubuntu One pairing record, or 1, meaning that there55 pairing_found = False
54 # was an Ubuntu One pairing record but it has since been unpaired56 # Results should contain either one row or no rows
55 # Only create a new record if there is not one already. Specifically,57 # If there is one row, its value will be 0, meaning that there is
56 # do not add the record if there is a deleted one, as this means58 # already an Ubuntu One pairing record, or 1, meaning that there
57 # that the user explicitly unpaired it!59 # was an Ubuntu One pairing record but it has since been unpaired
58 for row in view_results:60 # Only create a new record if there is not one already. Specifically,
59 pairing_found = True61 # do not add the record if there is a deleted one, as this means
60 if row.value == 1:62 # that the user explicitly unpaired it!
61 logging.debug("Not adding desktopcouch pairing since the user "63 for row in view_results:
62 "has explicitly unpaired with Ubuntu One")64 pairing_found = True
63 else:65 if row.value == 1:
64 logging.debug("Not adding desktopcouch pairing since we are "66 logging.debug("Not adding desktopcouch pairing since the user "
65 "already paired")67 "has explicitly unpaired with Ubuntu One")
66 if not pairing_found:68 else:
67 put_service_fn(None, "ubuntuone", uri=uri, ctx=None)69 logging.debug("Not adding desktopcouch pairing since we are "
68 logging.debug("Pairing desktopcouch with Ubuntu One")70 "already paired")
6971 if not pairing_found:
7072 put_service_fn(None, "ubuntuone", uri=uri, ctx=None)
71def got_new_credentials(couchdb_port, app_name, credentials):73 logging.debug("Pairing desktopcouch with Ubuntu One")
74
75 finally:
76 logging.info("removing semaphore for %s", PLUGIN_NAME)
77 blocking_semaphores.discard(PLUGIN_NAME)
78
79
80def got_new_credentials(couchdb_port, blocking_semaphores,
81 app_name, credentials):
72 """Pair with Ubuntu One when we get the new credentials."""82 """Pair with Ubuntu One when we get the new credentials."""
73 if app_name == APP_NAME:83 if app_name == APP_NAME:
74 pair_with_ubuntuone(couchdb_port)84 pair_with_ubuntuone(couchdb_port, blocking_semaphores)
7585
7686
77def listen_to_dbus(couchdb_port):87def listen_to_dbus(couchdb_port, blocking_semaphores):
78 """Set up the signal handler on D-Bus for Ubuntu One pairing."""88 """Set up the signal handler on D-Bus for Ubuntu One pairing."""
79 import dbus89 import dbus
80 bus = dbus.SessionBus()90 bus = dbus.SessionBus()
@@ -82,9 +92,20 @@
82 try:92 try:
83 import ubuntu_sso93 import ubuntu_sso
8494
85 receiver = lambda *args: got_new_credentials(couchdb_port, *args)95 receiver = lambda *args: \
96 got_new_credentials(couchdb_port, blocking_semaphores,
97 *args)
98 def abort_getting_credentials(*args):
99 """Log and remove our semaphore."""
100 logging.warn("Credentials not found for ubuntuone.")
101 blocking_semaphores.discard(PLUGIN_NAME)
86102
87 iface = ubuntu_sso.DBUS_CREDENTIALS_IFACE103 iface = ubuntu_sso.DBUS_CREDENTIALS_IFACE
104
105 bus.add_signal_receiver(handler_function=abort_getting_credentials,
106 signal_name='CredentialsNotFound',
107 dbus_interface=iface)
108
88 bus.add_signal_receiver(handler_function=receiver,109 bus.add_signal_receiver(handler_function=receiver,
89 signal_name='CredentialsFound',110 signal_name='CredentialsFound',
90 dbus_interface=iface)111 dbus_interface=iface)
@@ -96,13 +117,23 @@
96 sso_backend.find_credentials(APP_NAME, {})117 sso_backend.find_credentials(APP_NAME, {})
97 except ImportError:118 except ImportError:
98 logging.info('Ubuntu SSO is not available.')119 logging.info('Ubuntu SSO is not available.')
99120 blocking_semaphores.discard(PLUGIN_NAME)
100121
101def plugin_init(couchdb_port):122
123def plugin_init(couchdb_port, blocking_semaphores, gobject):
102 """Set up the signal handler for pairing with Ubuntu One."""124 """Set up the signal handler for pairing with Ubuntu One."""
103 logging.info('Loaded Ubuntu One extension for desktopcouch.')125 logging.info('Loaded Ubuntu One extension for desktopcouch.')
104 if sys.platform == 'win32':126 if sys.platform == 'win32':
105 logging.warning('Windows support for Ubuntu One is not yet ready.')127 logging.warning('Windows support for Ubuntu One is not yet ready.')
106 else:128 else:
107 import gobject129
108 gobject.idle_add(listen_to_dbus, couchdb_port)130 # Signal that we are critical for desktopcouch usage, and the server
131 # must not begin until we are finished. We are responsible for
132 # removing this item from the list.
133 logging.info("adding %s to to blocking semaphore list", PLUGIN_NAME)
134 blocking_semaphores.add(PLUGIN_NAME)
135
136 gobject.idle_add(listen_to_dbus, couchdb_port, blocking_semaphores)
137
138 gobject.timeout_add_seconds(TIMEOUT_SEC, blocking_semaphores.discard,
139 PLUGIN_NAME)
109140
=== modified file 'desktopcouch/application/replication.py'
--- desktopcouch/application/replication.py 2011-01-12 15:08:25 +0000
+++ desktopcouch/application/replication.py 2011-09-13 15:01:24 +0000
@@ -246,7 +246,7 @@
246 dbus_io.maintain_discovered_servers()246 dbus_io.maintain_discovered_servers()
247247
248 task_running = task.LoopingCall(do_all_replication, int(port))248 task_running = task.LoopingCall(do_all_replication, int(port))
249 task_running.start(600)249 task_running.start(3600)
250250
251 # TODO: port may change, so every so often, check it and251 # TODO: port may change, so every so often, check it and
252 # perhaps refresh the beacons. We return an array of beacons, so we could252 # perhaps refresh the beacons. We return an array of beacons, so we could
253253
=== modified file 'desktopcouch/application/service.py'
--- desktopcouch/application/service.py 2011-02-02 18:15:53 +0000
+++ desktopcouch/application/service.py 2011-09-13 15:01:24 +0000
@@ -39,6 +39,7 @@
39import logging39import logging
40import logging.handlers40import logging.handlers
41import signal41import signal
42import gobject
4243
43from desktopcouch.application import local_files44from desktopcouch.application import local_files
44from desktopcouch.application import replication45from desktopcouch.application import replication
@@ -84,7 +85,8 @@
84 replication_actions=replication,85 replication_actions=replication,
85 advertiser_factory=PortAdvertiser, set_logging=set_up_logging,86 advertiser_factory=PortAdvertiser, set_logging=set_up_logging,
86 fork=os.fork, nice=os.nice,87 fork=os.fork, nice=os.nice,
87 kill=os.kill, sleep=time.sleep):88 kill=os.kill, sleep=time.sleep, set_type=set,
89 gobject_module=gobject):
88 self._mainloop = main_loop90 self._mainloop = main_loop
89 self._pid_finder = pid_finder91 self._pid_finder = pid_finder
90 self._port_finder = port_finder92 self._port_finder = port_finder
@@ -97,6 +99,8 @@
97 self._nice = nice99 self._nice = nice
98 self._kill = kill100 self._kill = kill
99 self._sleep = sleep101 self._sleep = sleep
102 self._set = set_type
103 self._gobject = gobject_module
100 # pylint: enable=C0301104 # pylint: enable=C0301
101105
102 def _start_replicator_main(self, couchdb_port):106 def _start_replicator_main(self, couchdb_port):
@@ -112,25 +116,46 @@
112 replication.tear_down(*replication_runtime)116 replication.tear_down(*replication_runtime)
113117
114 def _start_server_main(self, couchdb_port):118 def _start_server_main(self, couchdb_port):
115 """Start server."""119 """Start server answering DBus calls, and run plugins first."""
116 self._advertiser_factory(self._mainloop.stop, self._ctx)120
121 def if_all_semaphores_cleared(blocking_semaphores,
122 func, *args, **kwargs):
123 """Run a function if no semaphores exist, else try later."""
124 if blocking_semaphores:
125 self._sleep(0.2) # Never peg the CPU
126 return True # Make idle call try us again.
127 else:
128 func(*args, **kwargs)
129 return False # Handled!
130
131 blocking_semaphores = self._set()
132 load_plugins(couchdb_port, blocking_semaphores, self._gobject)
133
134 # Answering queries on DBus signals that we are ready for users
135 # to connect. We mustn't begin that until every plugin has a chance
136 # to run to completion if it needs it.
137 self._gobject.idle_add(if_all_semaphores_cleared, blocking_semaphores,
138 self._advertiser_factory,
139 self._mainloop.stop,
140 self._ctx)
141
117 logging.debug("starting dbus main loop")142 logging.debug("starting dbus main loop")
118 try:143 try:
119 load_plugins(couchdb_port)
120 self._mainloop.run()144 self._mainloop.run()
121 finally:145 finally:
122 logging.debug("ending dbus main loop")146 logging.debug("ending dbus main loop")
123147
124 def start(self):148 def start(self):
125 """Start the services used by desktopcouch."""149 """Start the services used by desktopcouch."""
126 should_shut_down_couchdb = False150 maintained_child_pids = list()
127 couchdb_pid = self._pid_finder(start_if_not_running=False,151 couchdb_pid = self._pid_finder(start_if_not_running=False,
128 ctx=self._ctx)152 ctx=self._ctx)
129 if couchdb_pid is None:153 if couchdb_pid is None:
130 logging.warn("Starting up personal couchdb.")154 logging.warn("Starting up personal couchdb.")
131 couchdb_pid = self._pid_finder(start_if_not_running=True,155 couchdb_pid = self._pid_finder(start_if_not_running=True,
132 ctx=self._ctx)156 ctx=self._ctx)
133 should_shut_down_couchdb = True157 if couchdb_pid:
158 maintained_child_pids.append(couchdb_pid)
134 else:159 else:
135 logging.warn("Personal couchdb is already running at PID#%d.",160 logging.warn("Personal couchdb is already running at PID#%d.",
136 couchdb_pid)161 couchdb_pid)
@@ -145,6 +170,7 @@
145 return170 return
146 else:171 else:
147 assert child_pid > 0172 assert child_pid > 0
173 maintained_child_pids.append(child_pid)
148 child_pid = self._fork() # Split!174 child_pid = self._fork() # Split!
149 if child_pid == 0:175 if child_pid == 0:
150 # Let's be the migration tool!176 # Let's be the migration tool!
@@ -161,8 +187,18 @@
161 return187 return
162 else:188 else:
163 assert child_pid > 0189 assert child_pid > 0
190 maintained_child_pids.append(child_pid)
164 # Let's be the dbus server!191 # Let's be the dbus server!
165 # This is the parent process. When we exit, we kill children.192 # This is the parent process. When we exit, we kill children.
193
194 def receive_signal(signum, stackframe):
195 """On signal, quit main loop gracefully."""
196 logging.warn("Received signal %d. Quitting.", signum)
197 self._mainloop.stop()
198
199 signal.signal(signal.SIGHUP, receive_signal)
200 signal.signal(signal.SIGTERM, receive_signal)
201
166 try:202 try:
167 set_up_logging("dbus")203 set_up_logging("dbus")
168 # offer the dbus service204 # offer the dbus service
@@ -172,14 +208,18 @@
172 "uncaught exception makes us shut down.")208 "uncaught exception makes us shut down.")
173 finally:209 finally:
174 logging.info("exiting.")210 logging.info("exiting.")
175 if should_shut_down_couchdb:211 self._stop_couchdb(ctx=self._ctx)
176 logging.warn("shutting down personal couchdb.")
177 self._stop_couchdb(ctx=self._ctx)
178212
213 for child_pid in maintained_child_pids:
179 try:214 try:
180 self._kill(child_pid, signal.SIGTERM)215 self._kill(child_pid, signal.SIGTERM)
181 self._sleep(1)216 logging.warn("Sent SIGTERM to %d", child_pid)
217 except OSError:
218 pass
219 self._sleep(1)
220 for child_pid in maintained_child_pids:
221 try:
182 self._kill(child_pid, signal.SIGKILL)222 self._kill(child_pid, signal.SIGKILL)
223 logging.warn("Sent SIGKILL to %d", child_pid)
183 except OSError:224 except OSError:
184 pass225 pass
185 return # pylint: disable=W0150
186226
=== modified file 'desktopcouch/application/start_local_couchdb.py'
--- desktopcouch/application/start_local_couchdb.py 2011-04-08 20:40:53 +0000
+++ desktopcouch/application/start_local_couchdb.py 2011-09-13 15:01:24 +0000
@@ -166,7 +166,7 @@
166 break166 break
167 except RuntimeError, ex:167 except RuntimeError, ex:
168 saved_exception = ex168 saved_exception = ex
169 logging.exception("Starting couchdb failed on try %d", (retry,))169 logging.exception("Starting couchdb failed on try %d", retry)
170 time.sleep(1)170 time.sleep(1)
171 continue171 continue
172172
173173
=== modified file 'desktopcouch/application/tests/test_service.py'
--- desktopcouch/application/tests/test_service.py 2011-02-02 18:15:53 +0000
+++ desktopcouch/application/tests/test_service.py 2011-09-13 15:01:24 +0000
@@ -37,6 +37,8 @@
37 self._replication = self.mocker.mock()37 self._replication = self.mocker.mock()
38 self._advertiser = self.mocker.mock()38 self._advertiser = self.mocker.mock()
39 self._resources = self.mocker.mock()39 self._resources = self.mocker.mock()
40 self._set = self.mocker.mock()
41 self._gobject = self.mocker.mock()
40 self._service = DesktopcouchService(self._mainloop,42 self._service = DesktopcouchService(self._mainloop,
41 pid_finder=self._pid_finder,43 pid_finder=self._pid_finder,
42 port_finder=self._port_finder,44 port_finder=self._port_finder,
@@ -48,65 +50,97 @@
48 fork=self._fork,50 fork=self._fork,
49 nice=self._nice,51 nice=self._nice,
50 kill=self._kill,52 kill=self._kill,
51 sleep=self._sleep)53 sleep=self._sleep,
5254 set_type=self._set,
53 def test_start_new_desktopcouch_no_extensions(self):55 gobject_module=self._gobject)
54 """Test that desktopcouch is started.56
5557 self.gobject_idle_task_list = list()
56 We test that when the pid cannot be found we ensure58
57 that the desktopcouch instance is started and that the59 def test_start_new_desktopcouch_with_plugins(self):
58 start as the dbus service,60 """Test that desktopcouch is started.
59 """61
60 self._pid_finder(start_if_not_running=False, ctx=self._ctx)62 We test that when the pid cannot be found we ensure
61 self.mocker.result(None)63 that the desktopcouch instance is started and that the
62 self._pid_finder(start_if_not_running=True, ctx=self._ctx)64 start as the dbus service,
63 self.mocker.result(self._pid_result)65 """
64 self._port_finder(pid=self._pid_result, ctx=self._ctx)66 self._pid_finder(start_if_not_running=False, ctx=self._ctx)
65 self.mocker.result(self._port_result)67 self.mocker.result(None)
66 self._fork()68 self._pid_finder(start_if_not_running=True, ctx=self._ctx)
67 self.mocker.result(234)69 self.mocker.result(self._pid_result)
68 self._fork()70 self._port_finder(pid=self._pid_result, ctx=self._ctx)
69 self.mocker.result(234)71 self.mocker.result(self._port_result)
70 # XXX: call this?72 self._fork()
71 self._mainloop.stop # pylint: disable=W010473 self.mocker.result(234) # We are parent
72 self.mocker.result(ANY)74 self._fork()
73 self._advertiser(ANY, self._ctx)75 self.mocker.result(345) # We are parent
74 self._mainloop.run()76
75 self._stop_couchdb(ctx=self._ctx)77 # plugins load
76 self._kill(234, signal.SIGTERM)78 semaphores = self.mocker.mock()
77 self._sleep(1)79 self._set()
78 self._kill(234, signal.SIGKILL)80 self.mocker.result(semaphores)
79 self.mocker.replay()81
80 self._service.start()82 self._mainloop.stop # pylint: disable=W0104
8183 self.mocker.result(ANY)
82 def test_start_new_desktopcouch_extensions(self):84
83 """Test that desktopcouch is started.85 semaphores.discard # pylint: disable=W0104
8486 self.mocker.result("disc")
85 We test that when the pid cannot be found we ensure87 self._gobject.timeout_add_seconds(ANY, "disc", ANY)
86 that the desktopcouch instance is started and that the88
87 start as the dbus service,89 self._gobject.idle_add(ANY, self._port_result, semaphores)
88 """90 self._gobject.idle_add(ANY, semaphores, self._advertiser, ANY,
89 self._pid_finder(start_if_not_running=False, ctx=self._ctx)91 self._ctx)
90 self.mocker.result(None)92
91 self._pid_finder(start_if_not_running=True, ctx=self._ctx)93 self._mainloop.run()
92 self.mocker.result(self._pid_result)94
93 self._port_finder(pid=self._pid_result, ctx=self._ctx)95 # Tasks are added to the mainloop's idle queue. With that^
94 self.mocker.result(self._port_result)96 # mainloop.run, they'd be called. Simulate their call.
95 self._fork()97
96 self.mocker.result(234)98 # Idle loop to add plugins calls a plugin. ubuntuone_pairing
97 self._fork()99 semaphores.add(ANY) # A plugin asserts it must be completed.
98 self.mocker.result(234)100
99 # XXX: call this?101 # Idle loop calls plugin for ubuntuone_pairing, which fires up DBus
100 self._mainloop.stop # pylint: disable=W0104102 # client to get credentials from ubuntu-login app. When that returns,
101 self.mocker.result(ANY)103 # it calls got_new_credentials, which calls pair_with_ubuntuone.
102 self._advertiser(ANY, self._ctx)104
103 self._mainloop.run()105 # Mock call to pair_with_ubuntuone
104 self._stop_couchdb(ctx=self._ctx)106 management_db = self.mocker.mock()
105 self._kill(234, signal.SIGTERM)107 put_service_fn = self.mocker.mock()
106 self._sleep(1)108
107 self._kill(234, signal.SIGKILL)109 bool(management_db)
108 self.mocker.replay()110 self.mocker.result(True)
109 self._service.start()111
112 management_db.view_exists(ANY, ANY)
113 self.mocker.result(False)
114 management_db.add_view(ANY, ANY, design_doc=ANY)
115
116 row = self.mocker.mock()
117 management_db.execute_view(ANY, ANY)
118 self.mocker.result([row])
119
120 row.value # pylint: disable=W0104
121 self.mocker.result(1)
122
123 semaphores.discard(ANY)
124
125 # and then dbus service is ready to answer queries.
126
127 self._stop_couchdb(ctx=self._ctx)
128 self._kill(self._pid_result, signal.SIGTERM)
129 self._kill(234, signal.SIGTERM)
130 self._kill(345, signal.SIGTERM)
131 self._sleep(1)
132 self._kill(self._pid_result, signal.SIGKILL)
133 self._kill(234, signal.SIGKILL)
134 self._kill(345, signal.SIGKILL)
135
136 self.mocker.replay()
137
138 self._service.start()
139 # Manually do what dbus idle-loop would do.
140 import desktopcouch.application.plugins.ubuntuone_pairing as u_p
141 u_p.pair_with_ubuntuone(self._port_result, semaphores,
142 management_db=management_db,
143 put_service_fn=put_service_fn)
110144
111 def test_start_desktopcouch_replication(self):145 def test_start_desktopcouch_replication(self):
112 """ Test that the repliciation works.146 """ Test that the repliciation works.
@@ -119,7 +153,7 @@
119 self._port_finder(pid=self._pid_result, ctx=self._ctx)153 self._port_finder(pid=self._pid_result, ctx=self._ctx)
120 self.mocker.result(self._port_result)154 self.mocker.result(self._port_result)
121 self._fork()155 self._fork()
122 self.mocker.result(0)156 self.mocker.result(0) # We are child process
123 self._nice(10)157 self._nice(10)
124 self._replication.set_up(ANY)158 self._replication.set_up(ANY)
125 self._mainloop.run()159 self._mainloop.run()
@@ -146,9 +180,9 @@
146 self._port_finder(pid=self._pid_result, ctx=self._ctx)180 self._port_finder(pid=self._pid_result, ctx=self._ctx)
147 self.mocker.result(self._port_result)181 self.mocker.result(self._port_result)
148 self._fork()182 self._fork()
149 self.mocker.result(567)183 self.mocker.result(567) # We are parent process
150 self._fork()184 self._fork()
151 self.mocker.result(0)185 self.mocker.result(0) # We are child process
152 self._sleep(ANY)186 self._sleep(ANY)
153 self._ctx.db_dir # searching for files # pylint: disable=W0104187 self._ctx.db_dir # searching for files # pylint: disable=W0104
154 self.mocker.result("/tmp/migration-data/does/not/exist")188 self.mocker.result("/tmp/migration-data/does/not/exist")
155189
=== modified file 'desktopcouch/records/tests/test_mocked_server.py'
--- desktopcouch/records/tests/test_mocked_server.py 2011-04-08 20:40:53 +0000
+++ desktopcouch/records/tests/test_mocked_server.py 2011-09-13 15:01:24 +0000
@@ -61,6 +61,7 @@
6161
6262
63class TestMockedCouchDatabaseCreateStates(MockerTestCase):63class TestMockedCouchDatabaseCreateStates(MockerTestCase):
64 """Mocked Couch Database tests for create= flag states."""
64 def setUp(self):65 def setUp(self):
65 """Set up tests."""66 """Set up tests."""
66 super(TestMockedCouchDatabaseCreateStates, self).setUp()67 super(TestMockedCouchDatabaseCreateStates, self).setUp()
@@ -85,7 +86,7 @@
85 self.mocker.result({'update_seq': []})86 self.mocker.result({'update_seq': []})
8687
87 self.mocker.replay()88 self.mocker.replay()
88 database = DesktopDatabase(self.dbname, uri=self.uri,89 DesktopDatabase(self.dbname, uri=self.uri,
89 record_factory=self.record_factory, create=True,90 record_factory=self.record_factory, create=True,
90 server_class=self.server_class,91 server_class=self.server_class,
91 oauth_tokens=self.oauth_tokens, ctx=self.ctx)92 oauth_tokens=self.oauth_tokens, ctx=self.ctx)
@@ -105,7 +106,7 @@
105 info["update_seq"]106 info["update_seq"]
106 self.mocker.result({})107 self.mocker.result({})
107 self.mocker.replay()108 self.mocker.replay()
108 database = DesktopDatabase(self.dbname, uri=self.uri,109 DesktopDatabase(self.dbname, uri=self.uri,
109 record_factory=self.record_factory, create=True,110 record_factory=self.record_factory, create=True,
110 server_class=self.server_class,111 server_class=self.server_class,
111 oauth_tokens=self.oauth_tokens, ctx=self.ctx)112 oauth_tokens=self.oauth_tokens, ctx=self.ctx)
@@ -140,7 +141,7 @@
140 self.mocker.result({})141 self.mocker.result({})
141142
142 self.mocker.replay()143 self.mocker.replay()
143 database = DesktopDatabase(self.dbname, uri=self.uri,144 DesktopDatabase(self.dbname, uri=self.uri,
144 record_factory=self.record_factory, create=False,145 record_factory=self.record_factory, create=False,
145 server_class=self.server_class,146 server_class=self.server_class,
146 oauth_tokens=self.oauth_tokens, ctx=self.ctx)147 oauth_tokens=self.oauth_tokens, ctx=self.ctx)
147148
=== modified file 'desktopcouch/recordtypes/contacts/tests/test_view.py'
--- desktopcouch/recordtypes/contacts/tests/test_view.py 2011-01-12 15:08:25 +0000
+++ desktopcouch/recordtypes/contacts/tests/test_view.py 2011-09-13 15:01:24 +0000
@@ -280,29 +280,29 @@
280280
281 contacts = list(281 contacts = list(
282 view.find_contacts_starting(self.db, first_name="Frances"))282 view.find_contacts_starting(self.db, first_name="Frances"))
283 self.assertEqual(len(contacts), 1)283 self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,))
284284
285 contacts = list(285 contacts = list(
286 view.find_contacts_starting(self.db, birth_date="1918"))286 view.find_contacts_starting(self.db, birth_date="1918"))
287 self.assertEqual(len(contacts), 1)287 self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,))
288288
289 contacts = list(289 contacts = list(
290 view.find_contacts_starting(self.db, birth_date="1918-08"))290 view.find_contacts_starting(self.db, birth_date="1918-08"))
291 self.assertEqual(len(contacts), 1)291 self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,))
292292
293 contacts = list(293 contacts = list(
294 view.find_contacts_starting(self.db, wedding_date="1970"))294 view.find_contacts_starting(self.db, wedding_date="1970"))
295 self.assertEqual(len(contacts), 1)295 self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,))
296296
297 contacts = list(297 contacts = list(
298 view.find_contacts_starting(298 view.find_contacts_starting(
299 self.db, email_addressesaddress="blah.example.com"))299 self.db, email_addressesaddress="blah.example.com"))
300 self.assertEqual(len(contacts), 1)300 self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,))
301301
302 contacts = list(302 contacts = list(
303 view.find_contacts_starting(303 view.find_contacts_starting(
304 self.db, email_addressesaddress="berkeley"))304 self.db, email_addressesaddress="berkeley"))
305 self.assertEqual(len(contacts), 1)305 self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,))
306306
307 contacts = list(307 contacts = list(
308 view.find_contacts_starting(self.db, first_name="random"))308 view.find_contacts_starting(self.db, first_name="random"))
@@ -317,7 +317,7 @@
317317
318 contacts = list(318 contacts = list(
319 view.find_contacts_exact(self.db, first_name="Frances"))319 view.find_contacts_exact(self.db, first_name="Frances"))
320 self.assertEqual(len(contacts), 1)320 self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,))
321321
322 contacts = list(view.find_contacts_exact(self.db, birth_date="-08-23"))322 contacts = list(view.find_contacts_exact(self.db, birth_date="-08-23"))
323 self.assertEqual(len(contacts), 1)323 self.assertEqual(len(contacts), 1, "Length of 1. %s" % (contacts,))
324324
=== modified file 'setup.cfg'
--- setup.cfg 2011-01-12 15:08:25 +0000
+++ setup.cfg 2011-09-13 15:01:24 +0000
@@ -4,10 +4,10 @@
4tag_svn_revision = 04tag_svn_revision = 0
55
6[build]6[build]
7i18n = True7i18n=True
8icons = True8icons=True
99
10[build_i18n]10[build_i18n]
11domain = desktopcouch11domain=desktopcouch
12desktop_files = [("share/applications", ("desktopcouch-pair.desktop.in",))]12desktop_files=[("share/applications", ("desktopcouch-pair.desktop.in",))]
1313
1414
=== modified file 'setup.py'
--- setup.py 2011-04-08 20:42:51 +0000
+++ setup.py 2011-09-13 15:01:24 +0000
@@ -1,6 +1,6 @@
1#!/usr/bin/env python1#!/usr/bin/env python
2#2#
3# Copyright 2009-2010 Canonical Ltd.3# Copyright 2009-2011 Canonical Ltd.
4#4#
5# This file is part of desktopcouch.5# This file is part of desktopcouch.
6#6#
@@ -17,12 +17,13 @@
17# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.17# along with desktopcouch. If not, see <http://www.gnu.org/licenses/>.
18"""setup.py"""18"""setup.py"""
1919
20from setuptools import setup, find_packages20from setuptools import find_packages
21from DistUtilsExtra.command import build_extra, build_i18n21from DistUtilsExtra.command import build_extra, build_i18n
22from DistUtilsExtra.auto import setup
2223
23setup(24setup(
24 name='desktopcouch',25 name='desktopcouch',
25 version='1.0.7',26 version='1.0.8',
26 description='A Desktop CouchDB instance.',27 description='A Desktop CouchDB instance.',
27 url='https://launchpad.net/desktopcouch',28 url='https://launchpad.net/desktopcouch',
28 license='LGPL-3',29 license='LGPL-3',
@@ -30,19 +31,23 @@
30 author_email='stuart.langridge@canonical.com',31 author_email='stuart.langridge@canonical.com',
31 packages=find_packages(),32 packages=find_packages(),
32 scripts=['bin/desktopcouch-pair'],33 scripts=['bin/desktopcouch-pair'],
33 data_files=[('/usr/lib/desktopcouch/', ['bin/desktopcouch-service',34 data_files=[('lib/desktopcouch/', ['bin/desktopcouch-service',
34 'bin/desktopcouch-pair',35 'bin/desktopcouch-pair',
35 'bin/desktopcouch-get-port',36 'bin/desktopcouch-get-port',
36 'bin/desktopcouch-stop']),37 'bin/desktopcouch-stop']),
37 # Be sure all additions are reflected in MANIFEST.in !38 # Be sure all additions are reflected in MANIFEST.in !
38 ('/usr/share/doc/python-desktopcouch-records/api/',39 ('share/doc/python-desktopcouch-records/api/',
39 ['desktopcouch/records/doc/records.txt',40 ['desktopcouch/records/doc/records.txt',
41 'desktopcouch/records/doc/an_example_application.txt',
40 'desktopcouch/records/doc/field_registry.txt',42 'desktopcouch/records/doc/field_registry.txt',
41 'desktopcouch/recordtypes/contacts/schema.txt']),43 'desktopcouch/recordtypes/contacts/schema.txt']),
42 ('/etc/xdg/desktop-couch/',44 ('/etc/xdg/desktop-couch/',
43 ['config/desktop-couch/compulsory-auth.ini']),45 ['config/desktop-couch/compulsory-auth.ini',
44 ('/usr/share/desktopcouch/', ['data/couchdb.tmpl']),46 'config/desktop-couch/default.ini']),
45 ('/usr/share/dbus-1/services/', [47 ('share/desktopcouch/', ['data/couchdb.tmpl']),
48 ('share/apport/package-hooks/', [
49 'data/source_desktopcouch.py']),
50 ('share/dbus-1/services/', [
46 'org.desktopcouch.CouchDB.service']),51 'org.desktopcouch.CouchDB.service']),
47 ('share/man/man1/', ['docs/man/desktopcouch-pair.1'])],52 ('share/man/man1/', ['docs/man/desktopcouch-pair.1'])],
48 cmdclass={"build": build_extra.build_extra,53 cmdclass={"build": build_extra.build_extra,

Subscribers

People subscribed via source and target branches

to all changes: