Merge lp://qastaging/~cmiller/desktopcouch/add-running-context-as-parameter into lp://qastaging/desktopcouch
- add-running-context-as-parameter
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Eric Casteleijn |
Approved revision: | 106 |
Merged at revision: | not available |
Proposed branch: | lp://qastaging/~cmiller/desktopcouch/add-running-context-as-parameter |
Merge into: | lp://qastaging/desktopcouch |
Diff against target: |
1259 lines (+337/-228) 18 files modified
desktopcouch/__init__.py (+3/-3) desktopcouch/contacts/contactspicker.py (+3/-2) desktopcouch/contacts/testing/create.py (+4/-3) desktopcouch/contacts/tests/test_contactspicker.py (+4/-4) desktopcouch/local_files.py (+92/-60) desktopcouch/pair/couchdb_pairing/couchdb_io.py (+37/-26) desktopcouch/pair/tests/test_couchdb_io.py (+27/-19) desktopcouch/records/couchgrid.py (+6/-4) desktopcouch/records/doc/records.txt (+1/-1) desktopcouch/records/server.py (+9/-6) desktopcouch/records/tests/test_couchgrid.py (+19/-14) desktopcouch/records/tests/test_record.py (+5/-1) desktopcouch/records/tests/test_server.py (+3/-3) desktopcouch/start_local_couchdb.py (+41/-25) desktopcouch/tests/__init__.py (+42/-41) desktopcouch/tests/test_local_files.py (+9/-7) desktopcouch/tests/test_replication.py (+21/-0) desktopcouch/tests/test_start_local_couchdb.py (+11/-9) |
To merge this branch: | bzr merge lp://qastaging/~cmiller/desktopcouch/add-running-context-as-parameter |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Eric Casteleijn (community) | Approve | ||
Nicola Larosa (community) | Abstain | ||
Guillermo Gonzalez | Approve | ||
Review via email:
|
Commit message
Use an explicit test context for testing. Add an implicit context for normal usage.
Add test context to pairing test functions.
Add a context to the update_
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Nicola Larosa (teknico) wrote : | # |
I cannot find a way to test this, please document it somewhere.
First I run "setup.py build".
Then I run "setup.py check", pylint cannot find a config file, so it uses defaults, and spews out a *huge* amount of notices.
Last I run "setup.py test", it says "running test" but does not seem to do anything.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Nicola Larosa (teknico) wrote : | # |
Ok, it's "trial desktopcouch", there was some fog this morning. Tests pass, but I'm not looking at the code, I'll let Eric do that.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Eric Casteleijn (thisfred) wrote : | # |
After updating trunk and merging this in I get a fairly trivial conflict in desktopcouch/
Also in that file a default argument is used:
local_files.
but local_files is never imported. Is this a kind of magic that I forgot about, or are no errors triggered since the tests always pass an argument? In that case, we should add a test that does not pass a kw argument (but does not manipulate anything in the context, since that was what we set out to prevent)
Looks good, but when running;
PYTHONPATH=. trial desktopcouch
I get asked for access to the keyring 4 times, which I'm pretty sure is wrong, because the tests should not be using the tokens from there, unless I'm mistaken. (denying access makes the tests still pass)
Tests all pass.
- 106. By Chad Miller
-
Merge from trunk.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Chad Miller (cmiller) wrote : | # |
> Also in __init__.py a default argument is used:
>
> local_files.
>
> but local_files is never imported. Is this a kind of magic that I forgot
> about, or are no errors triggered since the tests always pass an argument? In
> that case, we should add a test that does not pass a kw argument (but does not
> manipulate anything in the context, since that was what we set out to prevent)
It's the __init__ for this module. Other parts of this module are available. We don't need to import ourselves, e.g., and access desktopcouch.
Try it. In __init__.py, add "print local_files" and start a python shell and "import desktopcouch" .
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Eric Casteleijn (thisfred) wrote : | # |
> Try it. In __init__.py, add "print local_files" and start a python shell and
> "import desktopcouch" .
Doh! I usually avoid using these kinds of imports and it has softened my brain into not recognizing them when I see them. NM
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Eric Casteleijn (thisfred) wrote : | # |
All issues resolved!
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Eric Casteleijn (thisfred) : | # |
Preview Diff
1 | === modified file 'desktopcouch/__init__.py' | |||
2 | --- desktopcouch/__init__.py 2009-10-29 21:45:19 +0000 | |||
3 | +++ desktopcouch/__init__.py 2009-11-18 18:51:14 +0000 | |||
4 | @@ -40,14 +40,14 @@ | |||
5 | 40 | logging.getLogger('').setLevel(logging.DEBUG) | 40 | logging.getLogger('').setLevel(logging.DEBUG) |
6 | 41 | log = logging.getLogger('') | 41 | log = logging.getLogger('') |
7 | 42 | 42 | ||
9 | 43 | def find_pid(start_if_not_running=True): | 43 | def find_pid(start_if_not_running=True, ctx=local_files.DEFAULT_CONTEXT): |
10 | 44 | # Work out whether CouchDB is running by looking at its pid file | 44 | # Work out whether CouchDB is running by looking at its pid file |
12 | 45 | pid = read_pidfile() | 45 | pid = read_pidfile(ctx=ctx) |
13 | 46 | if not process_is_couchdb(pid) and start_if_not_running: | 46 | if not process_is_couchdb(pid) and start_if_not_running: |
14 | 47 | # start CouchDB by running the startup script | 47 | # start CouchDB by running the startup script |
15 | 48 | log.info("Desktop CouchDB is not running; starting it.") | 48 | log.info("Desktop CouchDB is not running; starting it.") |
16 | 49 | from desktopcouch import start_local_couchdb | 49 | from desktopcouch import start_local_couchdb |
18 | 50 | pid = start_local_couchdb.start_couchdb() | 50 | pid = start_local_couchdb.start_couchdb(ctx=ctx) |
19 | 51 | # now load the design documents, because it's started | 51 | # now load the design documents, because it's started |
20 | 52 | start_local_couchdb.update_design_documents() | 52 | start_local_couchdb.update_design_documents() |
21 | 53 | 53 | ||
22 | 54 | 54 | ||
23 | === modified file 'desktopcouch/contacts/contactspicker.py' | |||
24 | --- desktopcouch/contacts/contactspicker.py 2009-08-24 20:35:25 +0000 | |||
25 | +++ desktopcouch/contacts/contactspicker.py 2009-11-18 18:51:14 +0000 | |||
26 | @@ -19,6 +19,7 @@ | |||
27 | 19 | """A widget to allow users to pick contacts""" | 19 | """A widget to allow users to pick contacts""" |
28 | 20 | 20 | ||
29 | 21 | import gtk | 21 | import gtk |
30 | 22 | import desktopcouch | ||
31 | 22 | from desktopcouch.contacts.record import CONTACT_RECORD_TYPE | 23 | from desktopcouch.contacts.record import CONTACT_RECORD_TYPE |
32 | 23 | from desktopcouch.records.couchgrid import CouchGrid | 24 | from desktopcouch.records.couchgrid import CouchGrid |
33 | 24 | 25 | ||
34 | @@ -26,7 +27,7 @@ | |||
35 | 26 | class ContactsPicker(gtk.VBox): | 27 | class ContactsPicker(gtk.VBox): |
36 | 27 | """A contacts picker""" | 28 | """A contacts picker""" |
37 | 28 | 29 | ||
39 | 29 | def __init__(self, uri=None): | 30 | def __init__(self, uri=None, ctx=desktopcouch.local_files.DEFAULT_CONTEXT): |
40 | 30 | """Create a new ContactsPicker widget.""" | 31 | """Create a new ContactsPicker widget.""" |
41 | 31 | 32 | ||
42 | 32 | gtk.VBox.__init__(self) | 33 | gtk.VBox.__init__(self) |
43 | @@ -43,7 +44,7 @@ | |||
44 | 43 | hbox.pack_start(self.search_button, False, False, 3) | 44 | hbox.pack_start(self.search_button, False, False, 3) |
45 | 44 | 45 | ||
46 | 45 | # Create CouchGrid to contain list of contacts | 46 | # Create CouchGrid to contain list of contacts |
48 | 46 | self.contacts_list = CouchGrid('contacts', uri=uri) | 47 | self.contacts_list = CouchGrid('contacts', uri=uri, ctx=ctx) |
49 | 47 | self.contacts_list.editable = False | 48 | self.contacts_list.editable = False |
50 | 48 | self.contacts_list.keys = [ "first_name", "last_name" ] | 49 | self.contacts_list.keys = [ "first_name", "last_name" ] |
51 | 49 | self.contacts_list.record_type = CONTACT_RECORD_TYPE | 50 | self.contacts_list.record_type = CONTACT_RECORD_TYPE |
52 | 50 | 51 | ||
53 | === modified file 'desktopcouch/contacts/testing/create.py' | |||
54 | --- desktopcouch/contacts/testing/create.py 2009-09-14 23:42:59 +0000 | |||
55 | +++ desktopcouch/contacts/testing/create.py 2009-11-18 18:51:14 +0000 | |||
56 | @@ -21,6 +21,7 @@ | |||
57 | 21 | 21 | ||
58 | 22 | import random, string, uuid | 22 | import random, string, uuid |
59 | 23 | 23 | ||
60 | 24 | import desktopcouch.tests as test_environment | ||
61 | 24 | from desktopcouch.records.server import OAuthCapableServer | 25 | from desktopcouch.records.server import OAuthCapableServer |
62 | 25 | import desktopcouch | 26 | import desktopcouch |
63 | 26 | 27 | ||
64 | @@ -163,10 +164,10 @@ | |||
65 | 163 | app_annots=None, server_class=OAuthCapableServer): | 164 | app_annots=None, server_class=OAuthCapableServer): |
66 | 164 | """Make many contacts and create their records""" | 165 | """Make many contacts and create their records""" |
67 | 165 | if port is None: | 166 | if port is None: |
70 | 166 | desktopcouch.find_pid() | 167 | pid = desktopcouch.find_pid(ctx=test_environment.test_context) |
71 | 167 | port = desktopcouch.find_port() | 168 | port = desktopcouch.find_port(pid) |
72 | 168 | server_url = 'http://%s:%s/' % (host, port) | 169 | server_url = 'http://%s:%s/' % (host, port) |
74 | 169 | server = server_class(server_url) | 170 | server = server_class(server_url, ctx=test_environment.test_context) |
75 | 170 | db = server[db_name] if db_name in server else server.create(db_name) | 171 | db = server[db_name] if db_name in server else server.create(db_name) |
76 | 171 | record_ids = [] | 172 | record_ids = [] |
77 | 172 | for maincount in range(1, num_contacts + 1): | 173 | for maincount in range(1, num_contacts + 1): |
78 | 173 | 174 | ||
79 | === modified file 'desktopcouch/contacts/tests/test_contactspicker.py' | |||
80 | --- desktopcouch/contacts/tests/test_contactspicker.py 2009-09-01 22:29:01 +0000 | |||
81 | +++ desktopcouch/contacts/tests/test_contactspicker.py 2009-11-18 18:51:14 +0000 | |||
82 | @@ -21,7 +21,7 @@ | |||
83 | 21 | import testtools | 21 | import testtools |
84 | 22 | import gtk | 22 | import gtk |
85 | 23 | 23 | ||
87 | 24 | from desktopcouch.tests import xdg_cache | 24 | import desktopcouch.tests as test_environment |
88 | 25 | 25 | ||
89 | 26 | from desktopcouch.contacts.contactspicker import ContactsPicker | 26 | from desktopcouch.contacts.contactspicker import ContactsPicker |
90 | 27 | from desktopcouch.records.server import CouchDatabase | 27 | from desktopcouch.records.server import CouchDatabase |
91 | @@ -32,9 +32,9 @@ | |||
92 | 32 | def setUp(self): | 32 | def setUp(self): |
93 | 33 | """setup each test""" | 33 | """setup each test""" |
94 | 34 | # Connect to CouchDB server | 34 | # Connect to CouchDB server |
95 | 35 | self.assert_(xdg_cache) | ||
96 | 36 | self.dbname = 'contacts' | 35 | self.dbname = 'contacts' |
98 | 37 | self.database = CouchDatabase(self.dbname, create=True) | 36 | self.database = CouchDatabase(self.dbname, create=True, |
99 | 37 | ctx=test_environment.test_context) | ||
100 | 38 | 38 | ||
101 | 39 | def tearDown(self): | 39 | def tearDown(self): |
102 | 40 | """tear down each test""" | 40 | """tear down each test""" |
103 | @@ -49,6 +49,6 @@ | |||
104 | 49 | win.resize(300, 450) | 49 | win.resize(300, 450) |
105 | 50 | 50 | ||
106 | 51 | # Create the contacts picker widget | 51 | # Create the contacts picker widget |
108 | 52 | picker = ContactsPicker() | 52 | picker = ContactsPicker(ctx=test_environment.test_context) |
109 | 53 | win.get_content_area().pack_start(picker, True, True, 3) | 53 | win.get_content_area().pack_start(picker, True, True, 3) |
110 | 54 | self.assert_(picker.get_contacts_list()) | 54 | self.assert_(picker.get_contacts_list()) |
111 | 55 | 55 | ||
112 | === modified file 'desktopcouch/local_files.py' | |||
113 | --- desktopcouch/local_files.py 2009-09-30 19:54:16 +0000 | |||
114 | +++ desktopcouch/local_files.py 2009-11-18 18:51:14 +0000 | |||
115 | @@ -24,7 +24,7 @@ | |||
116 | 24 | """ | 24 | """ |
117 | 25 | from __future__ import with_statement | 25 | from __future__ import with_statement |
118 | 26 | import os | 26 | import os |
120 | 27 | import xdg.BaseDirectory | 27 | import xdg.BaseDirectory as xdg_base_dirs |
121 | 28 | import subprocess | 28 | import subprocess |
122 | 29 | import logging | 29 | import logging |
123 | 30 | try: | 30 | try: |
124 | @@ -32,23 +32,6 @@ | |||
125 | 32 | except ImportError: | 32 | except ImportError: |
126 | 33 | import configparser | 33 | import configparser |
127 | 34 | 34 | ||
128 | 35 | def mkpath(rootdir, path): | ||
129 | 36 | "Remove .. from paths" | ||
130 | 37 | return os.path.realpath(os.path.join(rootdir, path)) | ||
131 | 38 | |||
132 | 39 | rootdir = os.path.join(xdg.BaseDirectory.xdg_cache_home, "desktop-couch") | ||
133 | 40 | if not os.path.isdir(rootdir): | ||
134 | 41 | os.mkdir(rootdir) | ||
135 | 42 | |||
136 | 43 | config_dir = xdg.BaseDirectory.save_config_path("desktop-couch") | ||
137 | 44 | |||
138 | 45 | FILE_INI = os.path.join(config_dir, "desktop-couchdb.ini") | ||
139 | 46 | DIR_DB = xdg.BaseDirectory.save_data_path("desktop-couch") | ||
140 | 47 | |||
141 | 48 | FILE_PID = mkpath(rootdir, "desktop-couchdb.pid") | ||
142 | 49 | FILE_LOG = mkpath(rootdir, "desktop-couchdb.log") | ||
143 | 50 | FILE_STDOUT = mkpath(rootdir, "desktop-couchdb.stdout") | ||
144 | 51 | FILE_STDERR = mkpath(rootdir, "desktop-couchdb.stderr") | ||
145 | 52 | 35 | ||
146 | 53 | COUCH_EXE = os.environ.get('COUCHDB') | 36 | COUCH_EXE = os.environ.get('COUCHDB') |
147 | 54 | if not COUCH_EXE: | 37 | if not COUCH_EXE: |
148 | @@ -58,30 +41,85 @@ | |||
149 | 58 | if not COUCH_EXE: | 41 | if not COUCH_EXE: |
150 | 59 | raise ImportError("Could not find couchdb") | 42 | raise ImportError("Could not find couchdb") |
151 | 60 | 43 | ||
176 | 61 | def couch_chain_ini_files(): | 44 | |
177 | 62 | process = subprocess.Popen([COUCH_EXE, '-V'], shell=False, | 45 | class Context(): |
178 | 63 | stdout=subprocess.PIPE) | 46 | """A mimic of xdg BaseDirectory, with overridable values that do not |
179 | 64 | line = process.stdout.read().split('\n')[0] | 47 | depend on environment variables.""" |
180 | 65 | couchversion = line.split()[-1] | 48 | |
181 | 66 | 49 | def __init__(self, run_dir, db_dir, config_dir): # (cache, data, config) | |
182 | 67 | # Explicitly add default ini file | 50 | |
183 | 68 | ini_files = ["/etc/couchdb/default.ini"] | 51 | self.couchdb_log_level = 'info' |
184 | 69 | 52 | ||
185 | 70 | # find all ini files in the desktopcouch XDG_CONFIG_DIRS and add them to | 53 | for d in (run_dir, db_dir, config_dir): |
186 | 71 | # the chain | 54 | if not os.path.isdir(d): |
187 | 72 | xdg_config_dirs = xdg.BaseDirectory.load_config_paths("desktop-couch") | 55 | os.makedirs(d, 0700) |
188 | 73 | # Reverse the list because it's in most-important-first order | 56 | else: |
189 | 74 | for folder in reversed(list(xdg_config_dirs)): | 57 | os.chmod(d, 0700) |
190 | 75 | ini_files.extend([os.path.join(folder, x) | 58 | |
191 | 76 | for x in sorted(os.listdir(folder)) | 59 | self.run_dir = os.path.realpath(run_dir) |
192 | 77 | if x.endswith(".ini")]) | 60 | self.config_dir = os.path.realpath(config_dir) |
193 | 78 | 61 | self.db_dir = os.path.realpath(db_dir) | |
194 | 79 | if FILE_INI not in ini_files: | 62 | |
195 | 80 | ini_files.append(FILE_INI) | 63 | self.file_ini = os.path.join(config_dir, "desktop-couchdb.ini") |
196 | 81 | 64 | self.file_pid = os.path.join(run_dir, "desktop-couchdb.pid") | |
197 | 82 | chain = "-n -a %s " % " -a ".join(ini_files) | 65 | self.file_log = os.path.join(run_dir, "desktop-couchdb.log") |
198 | 83 | 66 | self.file_stdout = os.path.join(run_dir, "desktop-couchdb.stdout") | |
199 | 84 | return chain | 67 | self.file_stderr = os.path.join(run_dir, "desktop-couchdb.stderr") |
200 | 68 | |||
201 | 69 | # You will need to add -b or -k on the end of this | ||
202 | 70 | self.couch_exec_command = [COUCH_EXE, self.couch_chain_ini_files(), | ||
203 | 71 | '-p', self.file_pid, | ||
204 | 72 | '-o', self.file_stdout, | ||
205 | 73 | '-e', self.file_stderr] | ||
206 | 74 | |||
207 | 75 | |||
208 | 76 | def ensure_files_not_readable(self): | ||
209 | 77 | for descr in ("ini", "pid", "log", "stdout", "stderr",): | ||
210 | 78 | f = getattr(self, "file_" + descr) | ||
211 | 79 | if os.path.isfile(f): | ||
212 | 80 | os.chmod(f, 0600) | ||
213 | 81 | |||
214 | 82 | def load_config_paths(self): | ||
215 | 83 | """This is xdg/BaseDirectory.py load_config_paths() with hard-code to | ||
216 | 84 | use desktop-couch resource and read from this context.""" | ||
217 | 85 | yield self.config_dir | ||
218 | 86 | for config_dir in \ | ||
219 | 87 | os.environ.get('XDG_CONFIG_DIRS', '/etc/xdg').split(':'): | ||
220 | 88 | path = os.path.join(config_dir, "desktop-couch") | ||
221 | 89 | if os.path.exists(path): | ||
222 | 90 | yield path | ||
223 | 91 | |||
224 | 92 | def couch_chain_ini_files(self): | ||
225 | 93 | process = subprocess.Popen([COUCH_EXE, '-V'], shell=False, | ||
226 | 94 | stdout=subprocess.PIPE) | ||
227 | 95 | line = process.stdout.read().split('\n')[0] | ||
228 | 96 | couchversion = line.split()[-1] | ||
229 | 97 | |||
230 | 98 | # Explicitly add default ini file | ||
231 | 99 | ini_files = ["/etc/couchdb/default.ini"] | ||
232 | 100 | |||
233 | 101 | # find all ini files in the desktopcouch XDG_CONFIG_DIRS and add them to | ||
234 | 102 | # the chain | ||
235 | 103 | config_dirs = self.load_config_paths() | ||
236 | 104 | # Reverse the list because it's in most-important-first order | ||
237 | 105 | for folder in reversed(list(config_dirs)): | ||
238 | 106 | ini_files.extend([os.path.join(folder, x) | ||
239 | 107 | for x in sorted(os.listdir(folder)) | ||
240 | 108 | if x.endswith(".ini")]) | ||
241 | 109 | |||
242 | 110 | if self.file_ini not in ini_files: | ||
243 | 111 | ini_files.append(self.file_ini) | ||
244 | 112 | |||
245 | 113 | chain = "-n -a %s " % " -a ".join(ini_files) | ||
246 | 114 | |||
247 | 115 | return chain | ||
248 | 116 | |||
249 | 117 | |||
250 | 118 | DEFAULT_CONTEXT = Context( | ||
251 | 119 | os.path.join(xdg_base_dirs.xdg_cache_home, "desktop-couch"), | ||
252 | 120 | xdg_base_dirs.save_data_path("desktop-couch"), | ||
253 | 121 | xdg_base_dirs.save_config_path("desktop-couch")) | ||
254 | 122 | |||
255 | 85 | 123 | ||
256 | 86 | class NoOAuthTokenException(Exception): | 124 | class NoOAuthTokenException(Exception): |
257 | 87 | def __init__(self, file_name): | 125 | def __init__(self, file_name): |
258 | @@ -91,7 +129,7 @@ | |||
259 | 91 | return "OAuth details were not found in the ini file (%s)" % ( | 129 | return "OAuth details were not found in the ini file (%s)" % ( |
260 | 92 | self.file_name) | 130 | self.file_name) |
261 | 93 | 131 | ||
263 | 94 | def get_oauth_tokens(config_file_name=FILE_INI): | 132 | def get_oauth_tokens(config_file_name=None): |
264 | 95 | """Return the OAuth tokens from the desktop Couch ini file. | 133 | """Return the OAuth tokens from the desktop Couch ini file. |
265 | 96 | CouchDB OAuth is two-legged OAuth (not three-legged like most OAuth). | 134 | CouchDB OAuth is two-legged OAuth (not three-legged like most OAuth). |
266 | 97 | We have one "consumer", defined by a consumer_key and a secret, | 135 | We have one "consumer", defined by a consumer_key and a secret, |
267 | @@ -101,6 +139,9 @@ | |||
268 | 101 | (More traditional 3-legged OAuth starts with a "request token" which is | 139 | (More traditional 3-legged OAuth starts with a "request token" which is |
269 | 102 | then used to procure an "access token". We do not require this.) | 140 | then used to procure an "access token". We do not require this.) |
270 | 103 | """ | 141 | """ |
271 | 142 | if config_file_name is None: | ||
272 | 143 | config_file_name = DEFAULT_CONTEXT.file_ini | ||
273 | 144 | |||
274 | 104 | c = configparser.ConfigParser() | 145 | c = configparser.ConfigParser() |
275 | 105 | # monkeypatch ConfigParser to stop it lower-casing option names | 146 | # monkeypatch ConfigParser to stop it lower-casing option names |
276 | 106 | c.optionxform = lambda s: s | 147 | c.optionxform = lambda s: s |
277 | @@ -123,9 +164,11 @@ | |||
278 | 123 | raise NoOAuthTokenException(config_file_name) | 164 | raise NoOAuthTokenException(config_file_name) |
279 | 124 | return out | 165 | return out |
280 | 125 | 166 | ||
283 | 126 | 167 | def get_bind_address(config_file_name=None): | |
282 | 127 | def get_bind_address(config_file_name=FILE_INI): | ||
284 | 128 | """Retreive a string if it exists, or None if it doesn't.""" | 168 | """Retreive a string if it exists, or None if it doesn't.""" |
285 | 169 | if config_file_name is None: | ||
286 | 170 | config_file_name = DEFAULT_CONTEXT.file_ini | ||
287 | 171 | |||
288 | 129 | c = configparser.ConfigParser() | 172 | c = configparser.ConfigParser() |
289 | 130 | try: | 173 | try: |
290 | 131 | c.read(config_file_name) | 174 | c.read(config_file_name) |
291 | @@ -134,7 +177,10 @@ | |||
292 | 134 | logging.warn("config file %r error. %s", config_file_name, e) | 177 | logging.warn("config file %r error. %s", config_file_name, e) |
293 | 135 | return None | 178 | return None |
294 | 136 | 179 | ||
296 | 137 | def set_bind_address(address, config_file_name=FILE_INI): | 180 | def set_bind_address(address, config_file_name=None): |
297 | 181 | if config_file_name is None: | ||
298 | 182 | config_file_name = DEFAULT_CONTEXT.file_ini | ||
299 | 183 | |||
300 | 138 | c = configparser.SafeConfigParser() | 184 | c = configparser.SafeConfigParser() |
301 | 139 | # monkeypatch ConfigParser to stop it lower-casing option names | 185 | # monkeypatch ConfigParser to stop it lower-casing option names |
302 | 140 | c.optionxform = lambda s: s | 186 | c.optionxform = lambda s: s |
303 | @@ -145,17 +191,3 @@ | |||
304 | 145 | with open(config_file_name, 'wb') as configfile: | 191 | with open(config_file_name, 'wb') as configfile: |
305 | 146 | c.write(configfile) | 192 | c.write(configfile) |
306 | 147 | 193 | ||
307 | 148 | |||
308 | 149 | # You will need to add -b or -k on the end of this | ||
309 | 150 | COUCH_EXEC_COMMAND = [COUCH_EXE, couch_chain_ini_files(), '-p', FILE_PID, | ||
310 | 151 | '-o', FILE_STDOUT, '-e', FILE_STDERR] | ||
311 | 152 | |||
312 | 153 | |||
313 | 154 | # Set appropriate permissions on relevant files and folders | ||
314 | 155 | for fn in [FILE_PID, FILE_STDOUT, FILE_STDERR, FILE_INI, FILE_LOG]: | ||
315 | 156 | if os.path.exists(fn): | ||
316 | 157 | os.chmod(fn, 0600) | ||
317 | 158 | for dn in [rootdir, config_dir, DIR_DB]: | ||
318 | 159 | if os.path.isdir(dn): | ||
319 | 160 | os.chmod(dn, 0700) | ||
320 | 161 | |||
321 | 162 | 194 | ||
322 | === modified file 'desktopcouch/pair/couchdb_pairing/couchdb_io.py' | |||
323 | --- desktopcouch/pair/couchdb_pairing/couchdb_io.py 2009-10-27 19:00:33 +0000 | |||
324 | +++ desktopcouch/pair/couchdb_pairing/couchdb_io.py 2009-11-18 18:51:14 +0000 | |||
325 | @@ -24,7 +24,7 @@ | |||
326 | 24 | import datetime | 24 | import datetime |
327 | 25 | from itertools import cycle | 25 | from itertools import cycle |
328 | 26 | 26 | ||
330 | 27 | from desktopcouch import find_pid, find_port as desktopcouch_find_port | 27 | from desktopcouch import find_pid, find_port as desktopcouch_find_port, local_files |
331 | 28 | from desktopcouch.records import server | 28 | from desktopcouch.records import server |
332 | 29 | from desktopcouch.records.record import Record | 29 | from desktopcouch.records.record import Record |
333 | 30 | 30 | ||
334 | @@ -56,11 +56,13 @@ | |||
335 | 56 | port = str(port) | 56 | port = str(port) |
336 | 57 | return "%s://%s%s:%s/%s" % (protocol, auth, hostname, port, path) | 57 | return "%s://%s%s:%s/%s" % (protocol, auth, hostname, port, path) |
337 | 58 | 58 | ||
339 | 59 | def _get_db(name, create=True, uri=None): | 59 | def _get_db(name, create=True, uri=None, |
340 | 60 | ctx=local_files.DEFAULT_CONTEXT): | ||
341 | 60 | """Get (and create?) a database.""" | 61 | """Get (and create?) a database.""" |
343 | 61 | return server.CouchDatabase(name, create=create, uri=uri) | 62 | return server.CouchDatabase(name, create=create, uri=uri, ctx=ctx) |
344 | 62 | 63 | ||
346 | 63 | def put_paired_host(oauth_data, uri=None, **kwargs): | 64 | def put_paired_host(oauth_data, uri=None, |
347 | 65 | ctx=local_files.DEFAULT_CONTEXT, **kwargs): | ||
348 | 64 | """Create a new paired-host record. OAuth information is required, and | 66 | """Create a new paired-host record. OAuth information is required, and |
349 | 65 | after the uri, keyword parameters are added to the record.""" | 67 | after the uri, keyword parameters are added to the record.""" |
350 | 66 | pairing_id = str(uuid.uuid4()) | 68 | pairing_id = str(uuid.uuid4()) |
351 | @@ -77,25 +79,28 @@ | |||
352 | 77 | "token_secret": str(oauth_data["token_secret"]), | 79 | "token_secret": str(oauth_data["token_secret"]), |
353 | 78 | } | 80 | } |
354 | 79 | data.update(kwargs) | 81 | data.update(kwargs) |
356 | 80 | d = _get_db("management", uri=uri) | 82 | d = _get_db("management", uri=uri, ctx=ctx) |
357 | 81 | r = Record(data) | 83 | r = Record(data) |
358 | 82 | record_id = d.put_record(r) | 84 | record_id = d.put_record(r) |
359 | 83 | return record_id | 85 | return record_id |
360 | 84 | 86 | ||
362 | 85 | def put_static_paired_service(oauth_data, service_name, uri=None): | 87 | def put_static_paired_service(oauth_data, service_name, uri=None, |
363 | 88 | ctx=local_files.DEFAULT_CONTEXT): | ||
364 | 86 | """Create a new service record.""" | 89 | """Create a new service record.""" |
365 | 87 | return put_paired_host(oauth_data, uri=uri, service_name=service_name, | 90 | return put_paired_host(oauth_data, uri=uri, service_name=service_name, |
367 | 88 | pull_from_server=True, push_to_server=True) | 91 | pull_from_server=True, push_to_server=True, ctx=ctx) |
368 | 89 | 92 | ||
370 | 90 | def put_dynamic_paired_host(hostname, remote_uuid, oauth_data, uri=None): | 93 | def put_dynamic_paired_host(hostname, remote_uuid, oauth_data, uri=None, |
371 | 94 | ctx=local_files.DEFAULT_CONTEXT): | ||
372 | 91 | """Create a new dynamic-host record.""" | 95 | """Create a new dynamic-host record.""" |
373 | 92 | return put_paired_host(oauth_data, uri=uri, pairing_identifier=remote_uuid, | 96 | return put_paired_host(oauth_data, uri=uri, pairing_identifier=remote_uuid, |
375 | 93 | push_to_server=True, server=hostname) | 97 | push_to_server=True, server=hostname, ctx=ctx) |
376 | 94 | 98 | ||
378 | 95 | def get_static_paired_hosts(uri=None): | 99 | def get_static_paired_hosts(uri=None, |
379 | 100 | ctx=local_files.DEFAULT_CONTEXT): | ||
380 | 96 | """Retreive a list of static hosts' information in the form of | 101 | """Retreive a list of static hosts' information in the form of |
381 | 97 | (ID, service name, to_push, to_pull) .""" | 102 | (ID, service name, to_push, to_pull) .""" |
383 | 98 | db = _get_db("management", uri=uri) | 103 | db = _get_db("management", uri=uri, ctx=ctx) |
384 | 99 | results = db.get_records(create_view=True) | 104 | results = db.get_records(create_view=True) |
385 | 100 | found = dict() | 105 | found = dict() |
386 | 101 | for row in results[PAIRED_SERVER_RECORD_TYPE]: | 106 | for row in results[PAIRED_SERVER_RECORD_TYPE]: |
387 | @@ -113,14 +118,16 @@ | |||
388 | 113 | logging.debug("static pairings are %s", unique_hosts) | 118 | logging.debug("static pairings are %s", unique_hosts) |
389 | 114 | return unique_hosts | 119 | return unique_hosts |
390 | 115 | 120 | ||
392 | 116 | def get_database_names_replicatable(uri, oauth_tokens=None, service=False): | 121 | def get_database_names_replicatable(uri, oauth_tokens=None, service=False, |
393 | 122 | ctx=local_files.DEFAULT_CONTEXT): | ||
394 | 117 | """Find a list of local databases, minus dbs that we do not want to | 123 | """Find a list of local databases, minus dbs that we do not want to |
395 | 118 | replicate (explicitly or implicitly).""" | 124 | replicate (explicitly or implicitly).""" |
396 | 119 | if not uri: | 125 | if not uri: |
399 | 120 | find_pid() | 126 | pid = find_pid(ctx=ctx) |
400 | 121 | port = desktopcouch_find_port() | 127 | port = desktopcouch_find_port(pid=pid) |
401 | 122 | uri = "http://localhost:%s" % port | 128 | uri = "http://localhost:%s" % port |
403 | 123 | couchdb_server = server.OAuthCapableServer(uri, oauth_tokens=oauth_tokens) | 129 | couchdb_server = server.OAuthCapableServer(uri, oauth_tokens=oauth_tokens, |
404 | 130 | ctx=ctx) | ||
405 | 124 | try: | 131 | try: |
406 | 125 | all_dbs = set([db_name for db_name in couchdb_server]) | 132 | all_dbs = set([db_name for db_name in couchdb_server]) |
407 | 126 | except socket.error, e: | 133 | except socket.error, e: |
408 | @@ -132,18 +139,19 @@ | |||
409 | 132 | excluded.add("users") | 139 | excluded.add("users") |
410 | 133 | if not service: | 140 | if not service: |
411 | 134 | excluded_msets = _get_management_data(PAIRED_SERVER_RECORD_TYPE, | 141 | excluded_msets = _get_management_data(PAIRED_SERVER_RECORD_TYPE, |
413 | 135 | "excluded_names", uri=uri) | 142 | "excluded_names", uri=uri, ctx=ctx) |
414 | 136 | for excluded_mset in excluded_msets: | 143 | for excluded_mset in excluded_msets: |
415 | 137 | excluded.update(excluded_mset) | 144 | excluded.update(excluded_mset) |
416 | 138 | 145 | ||
417 | 139 | return all_dbs - excluded | 146 | return all_dbs - excluded |
418 | 140 | 147 | ||
420 | 141 | def get_my_host_unique_id(uri=None, create=True): | 148 | def get_my_host_unique_id(uri=None, create=True, |
421 | 149 | ctx=local_files.DEFAULT_CONTEXT): | ||
422 | 142 | """Returns a list of ids we call ourselves. We complain in the log if it's | 150 | """Returns a list of ids we call ourselves. We complain in the log if it's |
423 | 143 | more than one, but it's really no error. If there are zero (id est, we've | 151 | more than one, but it's really no error. If there are zero (id est, we've |
424 | 144 | never paired with anyone), then returns None.""" | 152 | never paired with anyone), then returns None.""" |
425 | 145 | 153 | ||
427 | 146 | db = _get_db("management", uri=uri) | 154 | db = _get_db("management", uri=uri, ctx=ctx) |
428 | 147 | ids = _get_management_data(MY_ID_RECORD_TYPE, "self_identity", uri=uri) | 155 | ids = _get_management_data(MY_ID_RECORD_TYPE, "self_identity", uri=uri) |
429 | 148 | ids = list(set(ids)) # uniqify | 156 | ids = list(set(ids)) # uniqify |
430 | 149 | if len(ids) > 1: | 157 | if len(ids) > 1: |
431 | @@ -165,11 +173,12 @@ | |||
432 | 165 | logging.debug("set new self-identity value: %r", data["self_identity"]) | 173 | logging.debug("set new self-identity value: %r", data["self_identity"]) |
433 | 166 | return [data["self_identity"]] | 174 | return [data["self_identity"]] |
434 | 167 | 175 | ||
436 | 168 | def get_all_known_pairings(uri=None): | 176 | def get_all_known_pairings(uri=None, |
437 | 177 | ctx=local_files.DEFAULT_CONTEXT): | ||
438 | 169 | """Info dicts about all pairings, even if marked "unpaired", keyed on | 178 | """Info dicts about all pairings, even if marked "unpaired", keyed on |
439 | 170 | hostid with another dict as the value.""" | 179 | hostid with another dict as the value.""" |
440 | 171 | d = {} | 180 | d = {} |
442 | 172 | db = _get_db("management", uri=uri) | 181 | db = _get_db("management", uri=uri, ctx=ctx) |
443 | 173 | for row in db.get_records(PAIRED_SERVER_RECORD_TYPE): | 182 | for row in db.get_records(PAIRED_SERVER_RECORD_TYPE): |
444 | 174 | v = dict() | 183 | v = dict() |
445 | 175 | v["record_id"] = row.id | 184 | v["record_id"] = row.id |
446 | @@ -182,8 +191,9 @@ | |||
447 | 182 | d[hostid] = v | 191 | d[hostid] = v |
448 | 183 | return d | 192 | return d |
449 | 184 | 193 | ||
452 | 185 | def _get_management_data(record_type, key, uri=None): | 194 | def _get_management_data(record_type, key, uri=None, |
453 | 186 | db = _get_db("management", uri=uri) | 195 | ctx=local_files.DEFAULT_CONTEXT): |
454 | 196 | db = _get_db("management", uri=uri, ctx=ctx) | ||
455 | 187 | results = db.get_records(create_view=True) | 197 | results = db.get_records(create_view=True) |
456 | 188 | values = list() | 198 | values = list() |
457 | 189 | for record in results[record_type]: | 199 | for record in results[record_type]: |
458 | @@ -267,9 +277,9 @@ | |||
459 | 267 | logging.exception("can't replicate %r %r <== %r", source_database, | 277 | logging.exception("can't replicate %r %r <== %r", source_database, |
460 | 268 | url, obsfuscate(record)) | 278 | url, obsfuscate(record)) |
461 | 269 | 279 | ||
463 | 270 | def get_pairings(uri=None): | 280 | def get_pairings(uri=None, ctx=local_files.DEFAULT_CONTEXT): |
464 | 271 | """Get a list of paired servers.""" | 281 | """Get a list of paired servers.""" |
466 | 272 | db = _get_db("management", create=True, uri=None) | 282 | db = _get_db("management", create=True, uri=None, ctx=ctx) |
467 | 273 | 283 | ||
468 | 274 | design_doc = "paired_servers" | 284 | design_doc = "paired_servers" |
469 | 275 | if not db.view_exists("paired_servers", design_doc): | 285 | if not db.view_exists("paired_servers", design_doc): |
470 | @@ -292,10 +302,11 @@ | |||
471 | 292 | 302 | ||
472 | 293 | return db.execute_view("paired_servers") | 303 | return db.execute_view("paired_servers") |
473 | 294 | 304 | ||
475 | 295 | def remove_pairing(record_id, is_reconciled, uri=None): | 305 | def remove_pairing(record_id, is_reconciled, uri=None, |
476 | 306 | ctx=local_files.DEFAULT_CONTEXT): | ||
477 | 296 | """Remove a pairing record (or mark it as dead so it can be cleaned up | 307 | """Remove a pairing record (or mark it as dead so it can be cleaned up |
478 | 297 | properly later).""" | 308 | properly later).""" |
480 | 298 | db = _get_db("management", create=True, uri=None) | 309 | db = _get_db("management", create=True, uri=None, ctx=ctx) |
481 | 299 | if is_reconciled: | 310 | if is_reconciled: |
482 | 300 | db.delete_record(record_id) | 311 | db.delete_record(record_id) |
483 | 301 | else: | 312 | else: |
484 | 302 | 313 | ||
485 | === modified file 'desktopcouch/pair/tests/test_couchdb_io.py' | |||
486 | --- desktopcouch/pair/tests/test_couchdb_io.py 2009-10-27 19:00:33 +0000 | |||
487 | +++ desktopcouch/pair/tests/test_couchdb_io.py 2009-11-18 18:51:14 +0000 | |||
488 | @@ -18,23 +18,26 @@ | |||
489 | 18 | import pygtk | 18 | import pygtk |
490 | 19 | pygtk.require('2.0') | 19 | pygtk.require('2.0') |
491 | 20 | 20 | ||
493 | 21 | import desktopcouch.tests as dctests | 21 | import desktopcouch.tests as test_environment |
494 | 22 | 22 | ||
495 | 23 | from desktopcouch.pair.couchdb_pairing import couchdb_io | 23 | from desktopcouch.pair.couchdb_pairing import couchdb_io |
496 | 24 | from desktopcouch.records.server import CouchDatabase | 24 | from desktopcouch.records.server import CouchDatabase |
497 | 25 | from desktopcouch.records.record import Record | 25 | from desktopcouch.records.record import Record |
499 | 26 | import unittest | 26 | from twisted.trial import unittest |
500 | 27 | import uuid | 27 | import uuid |
501 | 28 | import os | 28 | import os |
502 | 29 | import httplib2 | 29 | import httplib2 |
503 | 30 | import socket | ||
504 | 30 | URI = None # use autodiscovery that desktopcouch.tests permits. | 31 | URI = None # use autodiscovery that desktopcouch.tests permits. |
505 | 31 | 32 | ||
506 | 32 | class TestCouchdbIo(unittest.TestCase): | 33 | class TestCouchdbIo(unittest.TestCase): |
507 | 33 | 34 | ||
508 | 34 | def setUp(self): | 35 | def setUp(self): |
509 | 35 | """setup each test""" | 36 | """setup each test""" |
512 | 36 | self.mgt_database = CouchDatabase('management', create=True, uri=URI) | 37 | self.mgt_database = CouchDatabase('management', create=True, uri=URI, |
513 | 37 | self.foo_database = CouchDatabase('foo', create=True, uri=URI) | 38 | ctx=test_environment.test_context) |
514 | 39 | self.foo_database = CouchDatabase('foo', create=True, uri=URI, | ||
515 | 40 | ctx=test_environment.test_context) | ||
516 | 38 | #create some records to pull out and test | 41 | #create some records to pull out and test |
517 | 39 | self.foo_database.put_record(Record({ | 42 | self.foo_database.put_record(Record({ |
518 | 40 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", | 43 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", |
519 | @@ -71,8 +74,8 @@ | |||
520 | 71 | "token": str("opqrst"), | 74 | "token": str("opqrst"), |
521 | 72 | "token_secret": str("uvwxyz"), | 75 | "token_secret": str("uvwxyz"), |
522 | 73 | } | 76 | } |
525 | 74 | couchdb_io.put_static_paired_service(oauth_data, service_name, uri=URI) | 77 | couchdb_io.put_static_paired_service(oauth_data, service_name, uri=URI, ctx=test_environment.test_context) |
526 | 75 | pairings = list(couchdb_io.get_pairings()) | 78 | pairings = list(couchdb_io.get_pairings(ctx=test_environment.test_context)) |
527 | 76 | 79 | ||
528 | 77 | def test_put_dynamic_paired_host(self): | 80 | def test_put_dynamic_paired_host(self): |
529 | 78 | hostname = "host%d" % (os.getpid(),) | 81 | hostname = "host%d" % (os.getpid(),) |
530 | @@ -85,43 +88,48 @@ | |||
531 | 85 | } | 88 | } |
532 | 86 | 89 | ||
533 | 87 | couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, | 90 | couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, |
539 | 88 | uri=URI) | 91 | uri=URI, ctx=test_environment.test_context) |
540 | 89 | couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, | 92 | couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, |
541 | 90 | uri=URI) | 93 | uri=URI, ctx=test_environment.test_context) |
542 | 91 | couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, | 94 | couchdb_io.put_dynamic_paired_host(hostname, remote_uuid, oauth_data, |
543 | 92 | uri=URI) | 95 | uri=URI, ctx=test_environment.test_context) |
544 | 93 | 96 | ||
546 | 94 | pairings = list(couchdb_io.get_pairings()) | 97 | pairings = list(couchdb_io.get_pairings(ctx=test_environment.test_context)) |
547 | 95 | self.assertEqual(3, len(pairings)) | 98 | self.assertEqual(3, len(pairings)) |
548 | 96 | self.assertEqual(pairings[0].value["oauth"], oauth_data) | 99 | self.assertEqual(pairings[0].value["oauth"], oauth_data) |
549 | 97 | self.assertEqual(pairings[0].value["server"], hostname) | 100 | self.assertEqual(pairings[0].value["server"], hostname) |
550 | 98 | self.assertEqual(pairings[0].value["pairing_identifier"], remote_uuid) | 101 | self.assertEqual(pairings[0].value["pairing_identifier"], remote_uuid) |
551 | 99 | 102 | ||
552 | 100 | for i, row in enumerate(pairings): | 103 | for i, row in enumerate(pairings): |
554 | 101 | couchdb_io.remove_pairing(row.id, i == 1) | 104 | couchdb_io.remove_pairing(row.id, i == 1, ctx=test_environment.test_context) |
555 | 102 | 105 | ||
557 | 103 | pairings = list(couchdb_io.get_pairings()) | 106 | pairings = list(couchdb_io.get_pairings(ctx=test_environment.test_context)) |
558 | 104 | self.assertEqual(0, len(pairings)) | 107 | self.assertEqual(0, len(pairings)) |
559 | 105 | 108 | ||
560 | 106 | 109 | ||
561 | 107 | def test_get_database_names_replicatable_bad_server(self): | 110 | def test_get_database_names_replicatable_bad_server(self): |
563 | 108 | # If this resolves, FIRE YOUR DNS PROVIDER. | 111 | hostname = "test.desktopcouch.example.com" |
564 | 112 | try: | ||
565 | 113 | socket.gethostbyname(hostname) | ||
566 | 114 | raise unittest.SkipTest("nxdomain hijacked") | ||
567 | 115 | except socket.gaierror: | ||
568 | 116 | pass | ||
569 | 109 | 117 | ||
570 | 110 | try: | 118 | try: |
571 | 111 | names = couchdb_io.get_database_names_replicatable( | 119 | names = couchdb_io.get_database_names_replicatable( |
573 | 112 | uri='http://test.desktopcouch.example.com:9/') | 120 | uri='http://' + hostname + ':9/') |
574 | 113 | self.assertEqual(set(), names) | 121 | self.assertEqual(set(), names) |
575 | 114 | except httplib2.ServerNotFoundError: | 122 | except httplib2.ServerNotFoundError: |
576 | 115 | pass | 123 | pass |
577 | 116 | 124 | ||
578 | 117 | def test_get_database_names_replicatable(self): | 125 | def test_get_database_names_replicatable(self): |
580 | 118 | names = couchdb_io.get_database_names_replicatable(uri=URI) | 126 | names = couchdb_io.get_database_names_replicatable(uri=URI, ctx=test_environment.test_context) |
581 | 119 | self.assertFalse('management' in names) | 127 | self.assertFalse('management' in names) |
582 | 120 | self.assertTrue('foo' in names) | 128 | self.assertTrue('foo' in names) |
583 | 121 | 129 | ||
584 | 122 | def test_get_my_host_unique_id(self): | 130 | def test_get_my_host_unique_id(self): |
587 | 123 | got = couchdb_io.get_my_host_unique_id(uri=URI) | 131 | got = couchdb_io.get_my_host_unique_id(uri=URI, ctx=test_environment.test_context) |
588 | 124 | again = couchdb_io.get_my_host_unique_id(uri=URI) | 132 | again = couchdb_io.get_my_host_unique_id(uri=URI, ctx=test_environment.test_context) |
589 | 125 | self.assertEquals(len(got), 1) | 133 | self.assertEquals(len(got), 1) |
590 | 126 | self.assertEquals(got, again) | 134 | self.assertEquals(got, again) |
591 | 127 | 135 | ||
592 | 128 | 136 | ||
593 | === modified file 'desktopcouch/records/couchgrid.py' | |||
594 | --- desktopcouch/records/couchgrid.py 2009-10-10 23:56:45 +0000 | |||
595 | +++ desktopcouch/records/couchgrid.py 2009-11-18 18:51:14 +0000 | |||
596 | @@ -22,11 +22,12 @@ | |||
597 | 22 | import gobject | 22 | import gobject |
598 | 23 | from server import CouchDatabase | 23 | from server import CouchDatabase |
599 | 24 | from record import Record | 24 | from record import Record |
600 | 25 | import desktopcouch | ||
601 | 25 | 26 | ||
602 | 26 | class CouchGrid(gtk.TreeView): | 27 | class CouchGrid(gtk.TreeView): |
603 | 27 | 28 | ||
606 | 28 | def __init__( | 29 | def __init__(self, database_name, record_type=None, keys=None, uri=None, |
607 | 29 | self, database_name, record_type=None, keys=None, uri=None): | 30 | ctx=desktopcouch.local_files.DEFAULT_CONTEXT): |
608 | 30 | """Create a new Couchwidget | 31 | """Create a new Couchwidget |
609 | 31 | arguments: | 32 | arguments: |
610 | 32 | database_name - specify the name of the database in the desktop | 33 | database_name - specify the name of the database in the desktop |
611 | @@ -63,6 +64,7 @@ | |||
612 | 63 | self.__keys = keys | 64 | self.__keys = keys |
613 | 64 | self.__editable = False | 65 | self.__editable = False |
614 | 65 | self.uri = uri | 66 | self.uri = uri |
615 | 67 | self.ctx = ctx | ||
616 | 66 | 68 | ||
617 | 67 | #set the datatabase | 69 | #set the datatabase |
618 | 68 | self.database = database_name | 70 | self.database = database_name |
619 | @@ -122,9 +124,9 @@ | |||
620 | 122 | @database.setter | 124 | @database.setter |
621 | 123 | def database(self, db_name): | 125 | def database(self, db_name): |
622 | 124 | if self.uri: | 126 | if self.uri: |
624 | 125 | self.__db = CouchDatabase(db_name, create=True, uri=self.uri) | 127 | self.__db = CouchDatabase(db_name, create=True, uri=self.uri, ctx=self.ctx) |
625 | 126 | else: | 128 | else: |
627 | 127 | self.__db = CouchDatabase(db_name, create=True) | 129 | self.__db = CouchDatabase(db_name, create=True, ctx=self.ctx) |
628 | 128 | if self.record_type != None: | 130 | if self.record_type != None: |
629 | 129 | self.__populate_treeview()(self.record_type) | 131 | self.__populate_treeview()(self.record_type) |
630 | 130 | 132 | ||
631 | 131 | 133 | ||
632 | === modified file 'desktopcouch/records/doc/records.txt' | |||
633 | --- desktopcouch/records/doc/records.txt 2009-10-05 13:39:38 +0000 | |||
634 | +++ desktopcouch/records/doc/records.txt 2009-11-18 18:51:14 +0000 | |||
635 | @@ -6,7 +6,7 @@ | |||
636 | 6 | Create a database object. Your database needs to exist. If it doesn't, you | 6 | Create a database object. Your database needs to exist. If it doesn't, you |
637 | 7 | can create it by passing create=True. | 7 | can create it by passing create=True. |
638 | 8 | 8 | ||
640 | 9 | >>> db = CouchDatabase('testing', create=True) | 9 | >> db = CouchDatabase('testing', create=True) |
641 | 10 | 10 | ||
642 | 11 | Create a Record object. Records have a record type, which should be a | 11 | Create a Record object. Records have a record type, which should be a |
643 | 12 | URL. The URL should point to a human-readable document which | 12 | URL. The URL should point to a human-readable document which |
644 | 13 | 13 | ||
645 | === modified file 'desktopcouch/records/server.py' | |||
646 | --- desktopcouch/records/server.py 2009-10-14 17:28:43 +0000 | |||
647 | +++ desktopcouch/records/server.py 2009-11-18 18:51:14 +0000 | |||
648 | @@ -28,13 +28,15 @@ | |||
649 | 28 | import urlparse | 28 | import urlparse |
650 | 29 | 29 | ||
651 | 30 | class OAuthCapableServer(Server): | 30 | class OAuthCapableServer(Server): |
653 | 31 | def __init__(self, uri, oauth_tokens=None): | 31 | def __init__(self, uri, oauth_tokens=None, ctx=None): |
654 | 32 | """Subclass of couchdb.client.Server which creates a custom | 32 | """Subclass of couchdb.client.Server which creates a custom |
655 | 33 | httplib2.Http subclass which understands OAuth""" | 33 | httplib2.Http subclass which understands OAuth""" |
656 | 34 | http = server_base.OAuthCapableHttp(scheme=urlparse.urlparse(uri)[0]) | 34 | http = server_base.OAuthCapableHttp(scheme=urlparse.urlparse(uri)[0]) |
657 | 35 | http.force_exception_to_status_code = False | 35 | http.force_exception_to_status_code = False |
658 | 36 | if ctx is None: | ||
659 | 37 | ctx = desktopcouch.local_files.DEFAULT_CONTEXT | ||
660 | 36 | if oauth_tokens is None: | 38 | if oauth_tokens is None: |
662 | 37 | oauth_tokens = desktopcouch.local_files.get_oauth_tokens() | 39 | oauth_tokens = desktopcouch.local_files.get_oauth_tokens(ctx.file_ini) |
663 | 38 | (consumer_key, consumer_secret, token, token_secret) = ( | 40 | (consumer_key, consumer_secret, token, token_secret) = ( |
664 | 39 | oauth_tokens["consumer_key"], oauth_tokens["consumer_secret"], | 41 | oauth_tokens["consumer_key"], oauth_tokens["consumer_secret"], |
665 | 40 | oauth_tokens["token"], oauth_tokens["token_secret"]) | 42 | oauth_tokens["token"], oauth_tokens["token_secret"]) |
666 | @@ -45,11 +47,12 @@ | |||
667 | 45 | """An small records specific abstraction over a couch db database.""" | 47 | """An small records specific abstraction over a couch db database.""" |
668 | 46 | 48 | ||
669 | 47 | def __init__(self, database, uri=None, record_factory=None, create=False, | 49 | def __init__(self, database, uri=None, record_factory=None, create=False, |
671 | 48 | server_class=OAuthCapableServer, oauth_tokens=None): | 50 | server_class=OAuthCapableServer, oauth_tokens=None, |
672 | 51 | ctx=desktopcouch.local_files.DEFAULT_CONTEXT): | ||
673 | 49 | if not uri: | 52 | if not uri: |
676 | 50 | desktopcouch.find_pid() | 53 | pid = desktopcouch.find_pid(ctx=ctx) |
677 | 51 | port = desktopcouch.find_port() | 54 | port = desktopcouch.find_port(pid) |
678 | 52 | uri = "http://localhost:%s" % port | 55 | uri = "http://localhost:%s" % port |
679 | 53 | super(CouchDatabase, self).__init__( | 56 | super(CouchDatabase, self).__init__( |
680 | 54 | database, uri, record_factory=record_factory, create=create, | 57 | database, uri, record_factory=record_factory, create=create, |
682 | 55 | server_class=server_class, oauth_tokens=oauth_tokens) | 58 | server_class=server_class, oauth_tokens=oauth_tokens, ctx=ctx) |
683 | 56 | 59 | ||
684 | === modified file 'desktopcouch/records/tests/test_couchgrid.py' | |||
685 | --- desktopcouch/records/tests/test_couchgrid.py 2009-10-10 23:52:01 +0000 | |||
686 | +++ desktopcouch/records/tests/test_couchgrid.py 2009-11-18 18:51:14 +0000 | |||
687 | @@ -20,7 +20,7 @@ | |||
688 | 20 | 20 | ||
689 | 21 | from testtools import TestCase | 21 | from testtools import TestCase |
690 | 22 | 22 | ||
692 | 23 | from desktopcouch.tests import xdg_cache | 23 | import desktopcouch.tests as test_environment |
693 | 24 | 24 | ||
694 | 25 | from desktopcouch.records.record import Record | 25 | from desktopcouch.records.record import Record |
695 | 26 | from desktopcouch.records.server import CouchDatabase | 26 | from desktopcouch.records.server import CouchDatabase |
696 | @@ -31,9 +31,9 @@ | |||
697 | 31 | """Test the CouchGrid functionality""" | 31 | """Test the CouchGrid functionality""" |
698 | 32 | 32 | ||
699 | 33 | def setUp(self): | 33 | def setUp(self): |
700 | 34 | self.assert_(xdg_cache) | ||
701 | 35 | self.dbname = self._testMethodName | 34 | self.dbname = self._testMethodName |
703 | 36 | self.db = CouchDatabase(self.dbname, create=True) | 35 | self.db = CouchDatabase(self.dbname, create=True, |
704 | 36 | ctx=test_environment.test_context) | ||
705 | 37 | self.record_type = "test_record_type" | 37 | self.record_type = "test_record_type" |
706 | 38 | 38 | ||
707 | 39 | def tearDown(self): | 39 | def tearDown(self): |
708 | @@ -46,7 +46,7 @@ | |||
709 | 46 | database name. | 46 | database name. |
710 | 47 | """ | 47 | """ |
711 | 48 | try: | 48 | try: |
713 | 49 | cw = CouchGrid(None) | 49 | cw = CouchGrid(None, ctx=test_environment.test_context) |
714 | 50 | except TypeError, inst: | 50 | except TypeError, inst: |
715 | 51 | self.assertEqual( | 51 | self.assertEqual( |
716 | 52 | inst.args[0],"database_name is required and must be a string") | 52 | inst.args[0],"database_name is required and must be a string") |
717 | @@ -55,7 +55,7 @@ | |||
718 | 55 | """Test a simple creating a CouchGrid """ | 55 | """Test a simple creating a CouchGrid """ |
719 | 56 | 56 | ||
720 | 57 | #create a test widget with test database values | 57 | #create a test widget with test database values |
722 | 58 | cw = CouchGrid(self.dbname) | 58 | cw = CouchGrid(self.dbname, ctx=test_environment.test_context) |
723 | 59 | 59 | ||
724 | 60 | #allow editing | 60 | #allow editing |
725 | 61 | cw.editable = True | 61 | cw.editable = True |
726 | @@ -87,7 +87,7 @@ | |||
727 | 87 | 87 | ||
728 | 88 | try: | 88 | try: |
729 | 89 | #create a test widget with test database values | 89 | #create a test widget with test database values |
731 | 90 | cw = CouchGrid(self.dbname) | 90 | cw = CouchGrid(self.dbname, ctx=test_environment.test_context) |
732 | 91 | 91 | ||
733 | 92 | #set the record_type for the TreeView | 92 | #set the record_type for the TreeView |
734 | 93 | #it will not populate without this value being set | 93 | #it will not populate without this value being set |
735 | @@ -113,7 +113,8 @@ | |||
736 | 113 | 113 | ||
737 | 114 | def test_all_from_database(self): | 114 | def test_all_from_database(self): |
738 | 115 | #create some records | 115 | #create some records |
740 | 116 | db = CouchDatabase(self.dbname, create=True) | 116 | db = CouchDatabase(self.dbname, create=True, |
741 | 117 | ctx=test_environment.test_context) | ||
742 | 117 | db.put_record(Record({ | 118 | db.put_record(Record({ |
743 | 118 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", | 119 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", |
744 | 119 | "record_type": self.record_type})) | 120 | "record_type": self.record_type})) |
745 | @@ -122,7 +123,7 @@ | |||
746 | 122 | "record_type": self.record_type})) | 123 | "record_type": self.record_type})) |
747 | 123 | 124 | ||
748 | 124 | #build the CouchGrid | 125 | #build the CouchGrid |
750 | 125 | cw = CouchGrid(self.dbname) | 126 | cw = CouchGrid(self.dbname, ctx=test_environment.test_context) |
751 | 126 | cw.record_type = self.record_type | 127 | cw.record_type = self.record_type |
752 | 127 | #make sure there are three columns and two rows | 128 | #make sure there are three columns and two rows |
753 | 128 | self.assertEqual(cw.get_model().get_n_columns(),4) | 129 | self.assertEqual(cw.get_model().get_n_columns(),4) |
754 | @@ -130,7 +131,8 @@ | |||
755 | 130 | 131 | ||
756 | 131 | def test_selected_id_property(self): | 132 | def test_selected_id_property(self): |
757 | 132 | #create some records | 133 | #create some records |
759 | 133 | db = CouchDatabase(self.dbname, create=True) | 134 | db = CouchDatabase(self.dbname, create=True, |
760 | 135 | ctx=test_environment.test_context) | ||
761 | 134 | id1 = db.put_record(Record({ | 136 | id1 = db.put_record(Record({ |
762 | 135 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", | 137 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", |
763 | 136 | "record_type": self.record_type})) | 138 | "record_type": self.record_type})) |
764 | @@ -139,7 +141,7 @@ | |||
765 | 139 | "record_type": self.record_type})) | 141 | "record_type": self.record_type})) |
766 | 140 | 142 | ||
767 | 141 | #build the CouchGrid | 143 | #build the CouchGrid |
769 | 142 | cw = CouchGrid(self.dbname) | 144 | cw = CouchGrid(self.dbname, ctx=test_environment.test_context) |
770 | 143 | cw.record_type = self.record_type | 145 | cw.record_type = self.record_type |
771 | 144 | 146 | ||
772 | 145 | #make sure the record ids are selected properly | 147 | #make sure the record ids are selected properly |
773 | @@ -158,7 +160,7 @@ | |||
774 | 158 | "key1_1": "val2_1", "key1_2": "val2_2", "key1_3": "val2_3", | 160 | "key1_1": "val2_1", "key1_2": "val2_2", "key1_3": "val2_3", |
775 | 159 | "record_type": self.record_type})) | 161 | "record_type": self.record_type})) |
776 | 160 | #build the CouchGrid | 162 | #build the CouchGrid |
778 | 161 | cw = CouchGrid(self.dbname) | 163 | cw = CouchGrid(self.dbname, ctx=test_environment.test_context) |
779 | 162 | cw.keys = ["key1_1"] | 164 | cw.keys = ["key1_1"] |
780 | 163 | cw.record_type = self.record_type | 165 | cw.record_type = self.record_type |
781 | 164 | #make sure there are three columns and two rows | 166 | #make sure there are three columns and two rows |
782 | @@ -176,7 +178,8 @@ | |||
783 | 176 | "record_type": self.record_type})) | 178 | "record_type": self.record_type})) |
784 | 177 | 179 | ||
785 | 178 | #create a test widget with test database values | 180 | #create a test widget with test database values |
787 | 179 | cw = CouchGrid(self.dbname, record_type=self.record_type) | 181 | cw = CouchGrid(self.dbname, record_type=self.record_type, |
788 | 182 | ctx=test_environment.test_context) | ||
789 | 180 | 183 | ||
790 | 181 | #make sure there are three columns and two rows | 184 | #make sure there are three columns and two rows |
791 | 182 | self.assertEqual(cw.get_model().get_n_columns(),4) | 185 | self.assertEqual(cw.get_model().get_n_columns(),4) |
792 | @@ -188,7 +191,8 @@ | |||
793 | 188 | #create a test widget with test database values | 191 | #create a test widget with test database values |
794 | 189 | cw = CouchGrid( | 192 | cw = CouchGrid( |
795 | 190 | self.dbname, record_type=self.record_type, | 193 | self.dbname, record_type=self.record_type, |
797 | 191 | keys=["Key1", "Key2", "Key3", "Key4"]) | 194 | keys=["Key1", "Key2", "Key3", "Key4"], |
798 | 195 | ctx=test_environment.test_context) | ||
799 | 192 | 196 | ||
800 | 193 | #create a row with all four columns set | 197 | #create a row with all four columns set |
801 | 194 | cw.append_row(["val1", "val2", "val3", "val4"]) | 198 | cw.append_row(["val1", "val2", "val3", "val4"]) |
802 | @@ -214,7 +218,8 @@ | |||
803 | 214 | "record_type": self.record_type})) | 218 | "record_type": self.record_type})) |
804 | 215 | 219 | ||
805 | 216 | #create a test widget with test database values | 220 | #create a test widget with test database values |
807 | 217 | cw = CouchGrid(self.dbname, record_type=self.record_type) | 221 | cw = CouchGrid(self.dbname, record_type=self.record_type, |
808 | 222 | ctx=test_environment.test_context) | ||
809 | 218 | 223 | ||
810 | 219 | #allow editing | 224 | #allow editing |
811 | 220 | cw.append_row(["boo", "ray"]) | 225 | cw.append_row(["boo", "ray"]) |
812 | 221 | 226 | ||
813 | === modified file 'desktopcouch/records/tests/test_record.py' | |||
814 | --- desktopcouch/records/tests/test_record.py 2009-11-17 22:03:11 +0000 | |||
815 | +++ desktopcouch/records/tests/test_record.py 2009-11-18 18:51:14 +0000 | |||
816 | @@ -23,8 +23,10 @@ | |||
817 | 23 | 23 | ||
818 | 24 | # pylint does not like relative imports from containing packages | 24 | # pylint does not like relative imports from containing packages |
819 | 25 | # pylint: disable-msg=F0401 | 25 | # pylint: disable-msg=F0401 |
820 | 26 | from desktopcouch.records.server import CouchDatabase | ||
821 | 26 | from desktopcouch.records.record import (Record, RecordDict, MergeableList, | 27 | from desktopcouch.records.record import (Record, RecordDict, MergeableList, |
822 | 27 | record_factory, IllegalKeyException, validate, NoRecordTypeSpecified) | 28 | record_factory, IllegalKeyException, validate, NoRecordTypeSpecified) |
823 | 29 | import desktopcouch.tests as test_environment | ||
824 | 28 | 30 | ||
825 | 29 | 31 | ||
826 | 30 | class TestRecords(TestCase): | 32 | class TestRecords(TestCase): |
827 | @@ -202,7 +204,9 @@ | |||
828 | 202 | self.record.record_type) | 204 | self.record.record_type) |
829 | 203 | 205 | ||
830 | 204 | def test_run_doctests(self): | 206 | def test_run_doctests(self): |
832 | 205 | results = doctest.testfile('../doc/records.txt') | 207 | ctx = test_environment.test_context |
833 | 208 | globs = { "db": CouchDatabase('testing', create=True, ctx=ctx) } | ||
834 | 209 | results = doctest.testfile('../doc/records.txt', globs=globs) | ||
835 | 206 | self.assertEqual(0, results.failed) | 210 | self.assertEqual(0, results.failed) |
836 | 207 | 211 | ||
837 | 208 | 212 | ||
838 | 209 | 213 | ||
839 | === modified file 'desktopcouch/records/tests/test_server.py' | |||
840 | --- desktopcouch/records/tests/test_server.py 2009-11-18 18:29:04 +0000 | |||
841 | +++ desktopcouch/records/tests/test_server.py 2009-11-18 18:51:14 +0000 | |||
842 | @@ -19,7 +19,7 @@ | |||
843 | 19 | """testing database/contact.py module""" | 19 | """testing database/contact.py module""" |
844 | 20 | import testtools | 20 | import testtools |
845 | 21 | 21 | ||
847 | 22 | from desktopcouch.tests import xdg_cache | 22 | import desktopcouch.tests as test_environment |
848 | 23 | from desktopcouch.records.server import CouchDatabase | 23 | from desktopcouch.records.server import CouchDatabase |
849 | 24 | from desktopcouch.records.server_base import row_is_deleted, NoSuchDatabase | 24 | from desktopcouch.records.server_base import row_is_deleted, NoSuchDatabase |
850 | 25 | from desktopcouch.records.record import Record | 25 | from desktopcouch.records.record import Record |
851 | @@ -41,9 +41,9 @@ | |||
852 | 41 | def setUp(self): | 41 | def setUp(self): |
853 | 42 | """setup each test""" | 42 | """setup each test""" |
854 | 43 | # Connect to CouchDB server | 43 | # Connect to CouchDB server |
855 | 44 | self.assert_(xdg_cache) | ||
856 | 45 | self.dbname = self._testMethodName | 44 | self.dbname = self._testMethodName |
858 | 46 | self.database = CouchDatabase(self.dbname, create=True) | 45 | self.database = CouchDatabase(self.dbname, create=True, |
859 | 46 | ctx=test_environment.test_context) | ||
860 | 47 | #create some records to pull out and test | 47 | #create some records to pull out and test |
861 | 48 | self.database.put_record(Record({ | 48 | self.database.put_record(Record({ |
862 | 49 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", | 49 | "key1_1": "val1_1", "key1_2": "val1_2", "key1_3": "val1_3", |
863 | 50 | 50 | ||
864 | === modified file 'desktopcouch/start_local_couchdb.py' | |||
865 | --- desktopcouch/start_local_couchdb.py 2009-11-15 21:11:21 +0000 | |||
866 | +++ desktopcouch/start_local_couchdb.py 2009-11-18 18:51:14 +0000 | |||
867 | @@ -41,6 +41,8 @@ | |||
868 | 41 | import errno | 41 | import errno |
869 | 42 | import time, gnomekeyring | 42 | import time, gnomekeyring |
870 | 43 | from desktopcouch.records.server import CouchDatabase | 43 | from desktopcouch.records.server import CouchDatabase |
871 | 44 | import logging | ||
872 | 45 | import itertools | ||
873 | 44 | 46 | ||
874 | 45 | ACCEPTABLE_USERNAME_PASSWORD_CHARS = string.lowercase + string.uppercase | 47 | ACCEPTABLE_USERNAME_PASSWORD_CHARS = string.lowercase + string.uppercase |
875 | 46 | 48 | ||
876 | @@ -58,9 +60,12 @@ | |||
877 | 58 | fd.write("\n") | 60 | fd.write("\n") |
878 | 59 | fd.close() | 61 | fd.close() |
879 | 60 | 62 | ||
881 | 61 | def create_ini_file(port="0"): | 63 | def create_ini_file(port="0", ctx=local_files.DEFAULT_CONTEXT): |
882 | 62 | """Write CouchDB ini file if not already present""" | 64 | """Write CouchDB ini file if not already present""" |
884 | 63 | if os.path.exists(local_files.FILE_INI): | 65 | |
885 | 66 | print "ini file is at", ctx.file_ini | ||
886 | 67 | |||
887 | 68 | if os.path.exists(ctx.file_ini): | ||
888 | 64 | # load the username and password from the keyring | 69 | # load the username and password from the keyring |
889 | 65 | try: | 70 | try: |
890 | 66 | data = gnomekeyring.find_items_sync(gnomekeyring.ITEM_GENERIC_SECRET, | 71 | data = gnomekeyring.find_items_sync(gnomekeyring.ITEM_GENERIC_SECRET, |
891 | @@ -85,19 +90,18 @@ | |||
892 | 85 | consumer_secret = make_random_string(10) | 90 | consumer_secret = make_random_string(10) |
893 | 86 | token = make_random_string(10) | 91 | token = make_random_string(10) |
894 | 87 | token_secret = make_random_string(10) | 92 | token_secret = make_random_string(10) |
895 | 88 | db_dir = local_files.DIR_DB | ||
896 | 89 | local = { | 93 | local = { |
897 | 90 | 'couchdb': { | 94 | 'couchdb': { |
900 | 91 | 'database_dir': db_dir, | 95 | 'database_dir': ctx.db_dir, |
901 | 92 | 'view_index_dir': db_dir, | 96 | 'view_index_dir': ctx.db_dir, |
902 | 93 | }, | 97 | }, |
903 | 94 | 'httpd': { | 98 | 'httpd': { |
904 | 95 | 'bind_address': '127.0.0.1', | 99 | 'bind_address': '127.0.0.1', |
905 | 96 | 'port': port, | 100 | 'port': port, |
906 | 97 | }, | 101 | }, |
907 | 98 | 'log': { | 102 | 'log': { |
910 | 99 | 'file': local_files.FILE_LOG, | 103 | 'file': ctx.file_log, |
911 | 100 | 'level': 'info', | 104 | 'level': ctx.couchdb_log_level, |
912 | 101 | }, | 105 | }, |
913 | 102 | 'admins': { | 106 | 'admins': { |
914 | 103 | admin_account_username: admin_account_basic_auth_password | 107 | admin_account_username: admin_account_basic_auth_password |
915 | @@ -116,7 +120,9 @@ | |||
916 | 116 | } | 120 | } |
917 | 117 | } | 121 | } |
918 | 118 | 122 | ||
920 | 119 | dump_ini(local, local_files.FILE_INI) | 123 | dump_ini(local, ctx.file_ini) |
921 | 124 | ctx.ensure_files_not_readable() | ||
922 | 125 | |||
923 | 120 | # save admin account details in keyring | 126 | # save admin account details in keyring |
924 | 121 | item_id = gnomekeyring.item_create_sync( | 127 | item_id = gnomekeyring.item_create_sync( |
925 | 122 | None, | 128 | None, |
926 | @@ -169,25 +175,31 @@ | |||
927 | 169 | except KeyError: | 175 | except KeyError: |
928 | 170 | raise NotImplementedError("os %r is not yet supported" % (os_name,)) | 176 | raise NotImplementedError("os %r is not yet supported" % (os_name,)) |
929 | 171 | 177 | ||
931 | 172 | def read_pidfile(): | 178 | def read_pidfile(ctx=local_files.DEFAULT_CONTEXT): |
932 | 173 | try: | 179 | try: |
934 | 174 | pid_file = local_files.FILE_PID | 180 | pid_file = ctx.file_pid |
935 | 181 | if not os.path.exists(pid_file): | ||
936 | 182 | return None | ||
937 | 175 | with open(pid_file) as fp: | 183 | with open(pid_file) as fp: |
938 | 176 | try: | 184 | try: |
939 | 177 | contents = fp.read() | 185 | contents = fp.read() |
940 | 186 | if contents == "\n": | ||
941 | 187 | return None # not yet written to pid file | ||
942 | 178 | return int(contents) | 188 | return int(contents) |
943 | 179 | except ValueError: | 189 | except ValueError: |
944 | 190 | logging.warn("Pid file does not contain int: %r", contents) | ||
945 | 180 | return None | 191 | return None |
947 | 181 | except IOError: | 192 | except IOError, e: |
948 | 193 | logging.warn("Reading pid file caused error. %s", e) | ||
949 | 182 | return None | 194 | return None |
950 | 183 | 195 | ||
952 | 184 | def run_couchdb(): | 196 | def run_couchdb(ctx=local_files.DEFAULT_CONTEXT): |
953 | 185 | """Actually start the CouchDB process. Return its PID.""" | 197 | """Actually start the CouchDB process. Return its PID.""" |
955 | 186 | pid = read_pidfile() | 198 | pid = read_pidfile(ctx) |
956 | 187 | if pid is not None and not process_is_couchdb(pid): | 199 | if pid is not None and not process_is_couchdb(pid): |
957 | 188 | print "Removing stale, deceptive pid file." | 200 | print "Removing stale, deceptive pid file." |
960 | 189 | os.remove(local_files.FILE_PID) | 201 | os.remove(ctx.file_pid) |
961 | 190 | local_exec = local_files.COUCH_EXEC_COMMAND + ['-b'] | 202 | local_exec = ctx.couch_exec_command + ['-b'] |
962 | 191 | try: | 203 | try: |
963 | 192 | # subprocess is buggy. Chad patched, but that takes time to propagate. | 204 | # subprocess is buggy. Chad patched, but that takes time to propagate. |
964 | 193 | proc = subprocess.Popen(local_exec) | 205 | proc = subprocess.Popen(local_exec) |
965 | @@ -209,14 +221,15 @@ | |||
966 | 209 | 221 | ||
967 | 210 | # give the process a chance to start | 222 | # give the process a chance to start |
968 | 211 | for timeout in xrange(1000): | 223 | for timeout in xrange(1000): |
970 | 212 | pid = read_pidfile() | 224 | pid = read_pidfile(ctx=ctx) |
971 | 213 | time.sleep(0.3) | 225 | time.sleep(0.3) |
972 | 214 | if pid is not None and process_is_couchdb(pid): | 226 | if pid is not None and process_is_couchdb(pid): |
973 | 215 | break | 227 | break |
974 | 216 | print "...waiting for couchdb to start..." | 228 | print "...waiting for couchdb to start..." |
975 | 229 | ctx.ensure_files_not_readable() | ||
976 | 217 | return pid | 230 | return pid |
977 | 218 | 231 | ||
979 | 219 | def update_design_documents(): | 232 | def update_design_documents(ctx=local_files.DEFAULT_CONTEXT): |
980 | 220 | """Check system design documents and update any that need updating | 233 | """Check system design documents and update any that need updating |
981 | 221 | 234 | ||
982 | 222 | A database should be created if | 235 | A database should be created if |
983 | @@ -225,17 +238,20 @@ | |||
984 | 225 | $XDG_DATA_DIRs/desktop-couch/databases/dbname/_design/designdocname/views/viewname/map.js | 238 | $XDG_DATA_DIRs/desktop-couch/databases/dbname/_design/designdocname/views/viewname/map.js |
985 | 226 | reduce.js may also exist in the same folder. | 239 | reduce.js may also exist in the same folder. |
986 | 227 | """ | 240 | """ |
988 | 228 | for base in xdg.BaseDirectory.xdg_data_dirs: | 241 | ctx_data_dir = os.path.split(ctx.db_dir)[0] |
989 | 242 | for base in itertools.chain([ctx_data_dir], xdg.BaseDirectory.xdg_data_dirs): | ||
990 | 243 | # FIXME: base may have magic chars. assert not glob.has_magic(base) ? | ||
991 | 229 | db_spec = os.path.join( | 244 | db_spec = os.path.join( |
992 | 230 | base, "desktop-couch", "databases", "*", "database.cfg") | 245 | base, "desktop-couch", "databases", "*", "database.cfg") |
993 | 231 | for database_path in glob.glob(db_spec): | 246 | for database_path in glob.glob(db_spec): |
994 | 232 | database_root = os.path.split(database_path)[0] | 247 | database_root = os.path.split(database_path)[0] |
995 | 233 | database_name = os.path.split(database_root)[1] | 248 | database_name = os.path.split(database_root)[1] |
996 | 234 | # Just the presence of database.cfg is enough to create the database | 249 | # Just the presence of database.cfg is enough to create the database |
998 | 235 | db = CouchDatabase(database_name, create=True) | 250 | db = CouchDatabase(database_name, create=True, ctx=ctx) |
999 | 236 | # look for design documents | 251 | # look for design documents |
1000 | 237 | dd_spec = os.path.join( | 252 | dd_spec = os.path.join( |
1001 | 238 | database_root, "_design", "*", "views", "*", "map.js") | 253 | database_root, "_design", "*", "views", "*", "map.js") |
1002 | 254 | # FIXME: dd_path may have magic chars. | ||
1003 | 239 | for dd_path in glob.glob(dd_spec): | 255 | for dd_path in glob.glob(dd_spec): |
1004 | 240 | view_root = os.path.split(dd_path)[0] | 256 | view_root = os.path.split(dd_path)[0] |
1005 | 241 | view_name = os.path.split(view_root)[1] | 257 | view_name = os.path.split(view_root)[1] |
1006 | @@ -258,9 +274,9 @@ | |||
1007 | 258 | # than inefficiently just overwriting it regardless | 274 | # than inefficiently just overwriting it regardless |
1008 | 259 | db.add_view(view_name, mapjs, reducejs, dd_name) | 275 | db.add_view(view_name, mapjs, reducejs, dd_name) |
1009 | 260 | 276 | ||
1011 | 261 | def write_bookmark_file(username, password, pid): | 277 | def write_bookmark_file(username, password, pid, ctx=local_files.DEFAULT_CONTEXT): |
1012 | 262 | """Write out an HTML document that the user can bookmark to find their DB""" | 278 | """Write out an HTML document that the user can bookmark to find their DB""" |
1014 | 263 | bookmark_file = os.path.join(local_files.DIR_DB, "couchdb.html") | 279 | bookmark_file = os.path.join(ctx.db_dir, "couchdb.html") |
1015 | 264 | 280 | ||
1016 | 265 | if os.path.exists( | 281 | if os.path.exists( |
1017 | 266 | os.path.join(os.path.split(__file__)[0], "../data/couchdb.tmpl")): | 282 | os.path.join(os.path.split(__file__)[0], "../data/couchdb.tmpl")): |
1018 | @@ -292,16 +308,16 @@ | |||
1019 | 292 | finally: | 308 | finally: |
1020 | 293 | fp.close() | 309 | fp.close() |
1021 | 294 | 310 | ||
1023 | 295 | def start_couchdb(): | 311 | def start_couchdb(ctx=local_files.DEFAULT_CONTEXT): |
1024 | 296 | """Execute each step to start a desktop CouchDB.""" | 312 | """Execute each step to start a desktop CouchDB.""" |
1027 | 297 | username, password = create_ini_file() | 313 | username, password = create_ini_file(ctx=ctx) |
1028 | 298 | pid = run_couchdb() | 314 | pid = run_couchdb(ctx=ctx) |
1029 | 299 | # Note that we do not call update_design_documents here. This is because | 315 | # Note that we do not call update_design_documents here. This is because |
1030 | 300 | # Couch won't actually have started yet, so when update_design_documents | 316 | # Couch won't actually have started yet, so when update_design_documents |
1031 | 301 | # calls the Records API, that will call back into get_port and we end up | 317 | # calls the Records API, that will call back into get_port and we end up |
1032 | 302 | # starting Couch again. Instead, get_port calls update_design_documents | 318 | # starting Couch again. Instead, get_port calls update_design_documents |
1033 | 303 | # *after* Couch startup has occurred. | 319 | # *after* Couch startup has occurred. |
1035 | 304 | write_bookmark_file(username, password, pid) | 320 | write_bookmark_file(username, password, pid, ctx=ctx) |
1036 | 305 | return pid | 321 | return pid |
1037 | 306 | 322 | ||
1038 | 307 | 323 | ||
1039 | 308 | 324 | ||
1040 | === modified file 'desktopcouch/tests/__init__.py' | |||
1041 | --- desktopcouch/tests/__init__.py 2009-09-14 15:56:42 +0000 | |||
1042 | +++ desktopcouch/tests/__init__.py 2009-11-18 18:51:14 +0000 | |||
1043 | @@ -1,47 +1,48 @@ | |||
1044 | 1 | """Tests for Desktop CouchDB""" | 1 | """Tests for Desktop CouchDB""" |
1045 | 2 | 2 | ||
1046 | 3 | import os, tempfile, atexit, shutil | 3 | import os, tempfile, atexit, shutil |
1047 | 4 | from desktopcouch.start_local_couchdb import start_couchdb, read_pidfile | ||
1048 | 4 | from desktopcouch.stop_local_couchdb import stop_couchdb | 5 | from desktopcouch.stop_local_couchdb import stop_couchdb |
1084 | 5 | 6 | from desktopcouch import local_files | |
1085 | 6 | def stop_test_couch(): | 7 | |
1086 | 7 | from desktopcouch.start_local_couchdb import read_pidfile | 8 | import gobject |
1087 | 8 | pid = read_pidfile() | 9 | gobject.set_application_name("desktopcouch testing") |
1088 | 9 | stop_couchdb(pid=pid) | 10 | |
1089 | 10 | shutil.rmtree(basedir) | 11 | |
1090 | 11 | 12 | def create_new_test_environment(): | |
1091 | 12 | atexit.register(stop_test_couch) | 13 | |
1092 | 13 | 14 | basedir = tempfile.mkdtemp() | |
1093 | 14 | basedir = tempfile.mkdtemp() | 15 | if not os.path.exists(basedir): |
1094 | 15 | if not os.path.exists(basedir): | 16 | os.mkdir(basedir) |
1095 | 16 | os.mkdir(basedir) | 17 | |
1096 | 17 | 18 | cache = os.path.join(basedir, 'cache') | |
1097 | 18 | xdg_cache = os.path.join(basedir, 'xdg_cache') | 19 | data = os.path.join(basedir, 'data') |
1098 | 19 | if not os.path.exists(xdg_cache): | 20 | config = os.path.join(basedir, 'config') |
1099 | 20 | os.mkdir(xdg_cache) | 21 | new_context = local_files.Context(cache, data, config) |
1100 | 21 | 22 | new_context.couchdb_log_level = 'debug' | |
1101 | 22 | xdg_data = os.path.join(basedir, 'xdg_data') | 23 | |
1102 | 23 | if not os.path.exists(xdg_data): | 24 | # Add etc folder to config |
1103 | 24 | os.mkdir(xdg_data) | 25 | SOURCE_TREE_ETC_FOLDER = os.path.realpath( |
1104 | 25 | 26 | os.path.join(os.path.split(__file__)[0], "..", "..", "config") | |
1105 | 26 | xdg_config = os.path.join(basedir, 'xdg_config') | 27 | ) |
1106 | 27 | if not os.path.exists(xdg_config): | 28 | if os.path.isdir(SOURCE_TREE_ETC_FOLDER): |
1107 | 28 | os.mkdir(xdg_config) | 29 | os.environ["XDG_CONFIG_DIRS"] = SOURCE_TREE_ETC_FOLDER |
1108 | 29 | 30 | ||
1109 | 30 | # Add etc folder to config | 31 | def stop_test_couch(temp_dir, ctx): |
1110 | 31 | SOURCE_TREE_ETC_FOLDER = os.path.realpath( | 32 | pid = read_pidfile(ctx) |
1111 | 32 | os.path.join(os.path.split(__file__)[0], "..", "..", "config") | 33 | stop_couchdb(pid=pid) |
1112 | 33 | ) | 34 | shutil.rmtree(temp_dir) |
1113 | 34 | if os.path.isdir(SOURCE_TREE_ETC_FOLDER): | 35 | |
1114 | 35 | os.environ["XDG_CONFIG_DIRS"] = SOURCE_TREE_ETC_FOLDER | 36 | start_couchdb(ctx=new_context) |
1115 | 36 | 37 | atexit.register(stop_test_couch, basedir, new_context) | |
1116 | 37 | os.environ['XDG_CACHE_HOME'] = xdg_cache | 38 | |
1117 | 38 | os.environ['XDG_DATA_HOME'] = xdg_data | 39 | return new_context |
1118 | 39 | os.environ['XDG_CONFIG_HOME'] = xdg_config | 40 | |
1119 | 41 | |||
1120 | 42 | # TODO: Remove these after you're sure nothing we care about uses these env. | ||
1121 | 43 | os.environ['XDG_CACHE_HOME'] = "/cachehome" | ||
1122 | 44 | os.environ['XDG_DATA_HOME'] = "/datahome" | ||
1123 | 45 | os.environ['XDG_CONFIG_HOME'] = "/confighome" | ||
1124 | 40 | os.environ['XDG_DATA_DIRS'] = '' | 46 | os.environ['XDG_DATA_DIRS'] = '' |
1125 | 41 | 47 | ||
1132 | 42 | # Force reload packages, so that the correct dekstopcouch will be | 48 | test_context = create_new_test_environment() |
1127 | 43 | # started. | ||
1128 | 44 | import xdg.BaseDirectory | ||
1129 | 45 | reload(xdg.BaseDirectory) | ||
1130 | 46 | from desktopcouch import local_files | ||
1131 | 47 | reload(local_files) | ||
1133 | 48 | 49 | ||
1134 | === modified file 'desktopcouch/tests/test_local_files.py' | |||
1135 | --- desktopcouch/tests/test_local_files.py 2009-11-17 22:03:11 +0000 | |||
1136 | +++ desktopcouch/tests/test_local_files.py 2009-11-18 18:51:14 +0000 | |||
1137 | @@ -1,7 +1,7 @@ | |||
1138 | 1 | """testing desktopcouch/local_files.py module""" | 1 | """testing desktopcouch/local_files.py module""" |
1139 | 2 | 2 | ||
1140 | 3 | import testtools | 3 | import testtools |
1142 | 4 | import desktopcouch.tests | 4 | import desktopcouch.tests as test_environment |
1143 | 5 | import desktopcouch | 5 | import desktopcouch |
1144 | 6 | import os | 6 | import os |
1145 | 7 | 7 | ||
1146 | @@ -11,19 +11,21 @@ | |||
1147 | 11 | "Does local_files list all the files that it needs to?" | 11 | "Does local_files list all the files that it needs to?" |
1148 | 12 | import desktopcouch.local_files | 12 | import desktopcouch.local_files |
1149 | 13 | for required in [ | 13 | for required in [ |
1153 | 14 | "FILE_LOG", "FILE_INI", "FILE_PID", "FILE_STDOUT", | 14 | "file_log", "file_ini", "file_pid", "file_stdout", |
1154 | 15 | "FILE_STDERR", "DIR_DB", "COUCH_EXE", "COUCH_EXEC_COMMAND"]: | 15 | "file_stderr", "db_dir"]: |
1155 | 16 | self.assertTrue(required in dir(desktopcouch.local_files)) | 16 | #"couch_exe", "couch_exec_command" |
1156 | 17 | self.assertTrue(required in dir(test_environment.test_context)) | ||
1157 | 17 | 18 | ||
1158 | 18 | def test_xdg_overwrite_works(self): | 19 | def test_xdg_overwrite_works(self): |
1159 | 19 | # this should really check that it's in os.environ["TMP"] | 20 | # this should really check that it's in os.environ["TMP"] |
1161 | 20 | self.assertTrue(desktopcouch.local_files.FILE_INI.startswith("/tmp")) | 21 | self.assertTrue(test_environment.test_context.file_ini.startswith("/tmp")) |
1162 | 21 | 22 | ||
1163 | 22 | def test_couch_chain_ini_files(self): | 23 | def test_couch_chain_ini_files(self): |
1164 | 23 | "Is compulsory-auth.ini picked up by the ini file finder?" | 24 | "Is compulsory-auth.ini picked up by the ini file finder?" |
1165 | 24 | import desktopcouch.local_files | 25 | import desktopcouch.local_files |
1168 | 25 | ok = [x for x in desktopcouch.local_files.couch_chain_ini_files().split() | 26 | ok = [x for x |
1169 | 26 | if x.endswith("compulsory-auth.ini")] | 27 | in test_environment.test_context.couch_chain_ini_files().split() |
1170 | 28 | if x.endswith("compulsory-auth.ini")] | ||
1171 | 27 | self.assertTrue(len(ok) > 0) | 29 | self.assertTrue(len(ok) > 0) |
1172 | 28 | 30 | ||
1173 | 29 | def test_bind_address(self): | 31 | def test_bind_address(self): |
1174 | 30 | 32 | ||
1175 | === added file 'desktopcouch/tests/test_replication.py' | |||
1176 | --- desktopcouch/tests/test_replication.py 1970-01-01 00:00:00 +0000 | |||
1177 | +++ desktopcouch/tests/test_replication.py 2009-11-18 18:51:14 +0000 | |||
1178 | @@ -0,0 +1,21 @@ | |||
1179 | 1 | """testing desktopcouch/start_local_couchdb.py module""" | ||
1180 | 2 | |||
1181 | 3 | import testtools | ||
1182 | 4 | import os, sys | ||
1183 | 5 | import desktopcouch.tests as test_environment | ||
1184 | 6 | import desktopcouch | ||
1185 | 7 | sys.path.append( | ||
1186 | 8 | os.path.join(os.path.split(desktopcouch.__file__)[0], "..", "contrib")) | ||
1187 | 9 | |||
1188 | 10 | class TestReplication(testtools.TestCase): | ||
1189 | 11 | """Testing that the database/designdoc filesystem loader works""" | ||
1190 | 12 | |||
1191 | 13 | def setUp(self): | ||
1192 | 14 | self.db_apple = desktopcouch.records.server.CouchDatabase("apple", | ||
1193 | 15 | create=True, ctx=test_environment.test_context) | ||
1194 | 16 | banana = test_environment.create_new_test_environment() | ||
1195 | 17 | self.db_banana = desktopcouch.records.server.CouchDatabase("banana", | ||
1196 | 18 | create=True, ctx=banana) | ||
1197 | 19 | |||
1198 | 20 | def test_creation(self): | ||
1199 | 21 | pass | ||
1200 | 0 | 22 | ||
1201 | === modified file 'desktopcouch/tests/test_start_local_couchdb.py' | |||
1202 | --- desktopcouch/tests/test_start_local_couchdb.py 2009-09-14 15:56:42 +0000 | |||
1203 | +++ desktopcouch/tests/test_start_local_couchdb.py 2009-11-18 18:51:14 +0000 | |||
1204 | @@ -2,7 +2,7 @@ | |||
1205 | 2 | 2 | ||
1206 | 3 | import testtools | 3 | import testtools |
1207 | 4 | import os, sys | 4 | import os, sys |
1209 | 5 | from desktopcouch.tests import xdg_data | 5 | import desktopcouch.tests as test_environment |
1210 | 6 | import desktopcouch | 6 | import desktopcouch |
1211 | 7 | sys.path.append( | 7 | sys.path.append( |
1212 | 8 | os.path.join(os.path.split(desktopcouch.__file__)[0], "..", "contrib")) | 8 | os.path.join(os.path.split(desktopcouch.__file__)[0], "..", "contrib")) |
1213 | @@ -71,6 +71,7 @@ | |||
1214 | 71 | 71 | ||
1215 | 72 | def setUp(self): | 72 | def setUp(self): |
1216 | 73 | # create temp folder with databases and design documents in | 73 | # create temp folder with databases and design documents in |
1217 | 74 | xdg_data = os.path.split(test_environment.test_context.db_dir)[0] | ||
1218 | 74 | try: | 75 | try: |
1219 | 75 | os.mkdir(os.path.join(xdg_data, "desktop-couch")) | 76 | os.mkdir(os.path.join(xdg_data, "desktop-couch")) |
1220 | 76 | except OSError: | 77 | except OSError: |
1221 | @@ -90,21 +91,22 @@ | |||
1222 | 90 | couchdb = mocker.replace("desktopcouch.records.server.CouchDatabase") | 91 | couchdb = mocker.replace("desktopcouch.records.server.CouchDatabase") |
1223 | 91 | 92 | ||
1224 | 92 | # databases that should be created | 93 | # databases that should be created |
1230 | 93 | couchdb("cfg", create=True) | 94 | couchdb("cfg", create=True, ctx=test_environment.test_context) |
1231 | 94 | couchdb("cfg_and_empty_design", create=True) | 95 | couchdb("cfg_and_empty_design", create=True, ctx=test_environment.test_context) |
1232 | 95 | couchdb("cfg_and_design_no_views", create=True) | 96 | couchdb("cfg_and_design_no_views", create=True, ctx=test_environment.test_context) |
1233 | 96 | couchdb("cfg_and_design_one_view_no_map", create=True) | 97 | couchdb("cfg_and_design_one_view_no_map", create=True, ctx=test_environment.test_context) |
1234 | 97 | couchdb("cfg_and_design_one_view_map_no_reduce", create=True) | 98 | couchdb("cfg_and_design_one_view_map_no_reduce", create=True, ctx=test_environment.test_context) |
1235 | 99 | |||
1236 | 98 | dbmock1 = mocker.mock() | 100 | dbmock1 = mocker.mock() |
1237 | 99 | mocker.result(dbmock1) | 101 | mocker.result(dbmock1) |
1238 | 100 | dbmock1.add_view("view1", "cfg_and_design_one_view_map_no_reduce:map", | 102 | dbmock1.add_view("view1", "cfg_and_design_one_view_map_no_reduce:map", |
1239 | 101 | None, "doc1") | 103 | None, "doc1") |
1241 | 102 | couchdb("cfg_and_design_one_view_map_reduce", create=True) | 104 | couchdb("cfg_and_design_one_view_map_reduce", create=True, ctx=test_environment.test_context) |
1242 | 103 | dbmock2 = mocker.mock() | 105 | dbmock2 = mocker.mock() |
1243 | 104 | mocker.result(dbmock2) | 106 | mocker.result(dbmock2) |
1244 | 105 | dbmock2.add_view("view1", "cfg_and_design_one_view_map_reduce:map", | 107 | dbmock2.add_view("view1", "cfg_and_design_one_view_map_reduce:map", |
1245 | 106 | "cfg_and_design_one_view_map_reduce:reduce", "doc1") | 108 | "cfg_and_design_one_view_map_reduce:reduce", "doc1") |
1247 | 107 | couchdb("cfg_and_design_two_views_map_reduce", create=True) | 109 | couchdb("cfg_and_design_two_views_map_reduce", create=True, ctx=test_environment.test_context) |
1248 | 108 | dbmock3 = mocker.mock() | 110 | dbmock3 = mocker.mock() |
1249 | 109 | mocker.result(dbmock3) | 111 | mocker.result(dbmock3) |
1250 | 110 | dbmock3.add_view("view1", "cfg_and_design_two_views_map_reduce:map1", | 112 | dbmock3.add_view("view1", "cfg_and_design_two_views_map_reduce:map1", |
1251 | @@ -116,7 +118,7 @@ | |||
1252 | 116 | # all the right things | 118 | # all the right things |
1253 | 117 | mocker.replay() | 119 | mocker.replay() |
1254 | 118 | from desktopcouch.start_local_couchdb import update_design_documents | 120 | from desktopcouch.start_local_couchdb import update_design_documents |
1256 | 119 | update_design_documents() | 121 | update_design_documents(ctx=test_environment.test_context) |
1257 | 120 | 122 | ||
1258 | 121 | mocker.restore() | 123 | mocker.restore() |
1259 | 122 | mocker.verify() | 124 | mocker.verify() |
looks good, all tests [OK]