Merge lp:~jderose/dmedia/simple-default into lp:dmedia
- simple-default
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 524 |
Proposed branch: | lp:~jderose/dmedia/simple-default |
Merge into: | lp:dmedia |
Diff against target: |
668 lines (+233/-250) 8 files modified
debian/dmedia.postinst (+0/-10) dmedia-cli (+3/-3) dmedia-service (+6/-14) dmedia/core.py (+66/-36) dmedia/tests/base.py (+5/-0) dmedia/tests/test_core.py (+118/-186) dmedia/tests/test_util.py (+26/-1) dmedia/util.py (+9/-0) |
To merge this branch: | bzr merge lp:~jderose/dmedia/simple-default |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
James Raymond | Approve | ||
Review via email: mp+136073@code.launchpad.net |
Commit message
Description of the change
For background, see this bug:
https:/
Changes include:
* Removed debian/
* Reworked Core so it always has a default file store, doesn't use the shared store in /home/.dmedia anymore
* Moved the private store from ~/.dmedia to ~/.local/
* Removed SetDefaultStore() DBus method
* Added Core.purge_store() implementation and test
* Added PurgeStore() DBus method
* Added quick and dirty migration functionality for the default store changes, which:
1) If ~/.dmedia exists, it is move to ~/.local/
2) If /home/.dmedia exists, all files there are moved (or copied if needed) into ~/.local/
3) Finally, calls Core.purge_store() to remove any references to copies in /home/.dmedia
James Raymond (jamesmr) : | # |
Preview Diff
1 | === removed file 'debian/dmedia.postinst' |
2 | --- debian/dmedia.postinst 2012-07-26 21:05:25 +0000 |
3 | +++ debian/dmedia.postinst 1970-01-01 00:00:00 +0000 |
4 | @@ -1,10 +0,0 @@ |
5 | -#!/bin/sh -e |
6 | - |
7 | -case $1 in |
8 | - configure) |
9 | - # Initalize shared file-store in /home |
10 | - /usr/lib/dmedia/init-filestore /home |
11 | -esac |
12 | - |
13 | - |
14 | -#DEBHELPER# |
15 | |
16 | === modified file 'dmedia-cli' |
17 | --- dmedia-cli 2012-11-01 11:06:53 +0000 |
18 | +++ dmedia-cli 2012-11-26 00:24:26 +0000 |
19 | @@ -134,10 +134,10 @@ |
20 | return [path.abspath(directory)] |
21 | |
22 | |
23 | -class SetDefaultStore(_Method): |
24 | - "Set default store to 'private', 'shared', or 'none'" |
25 | +class PurgeStore(_Method): |
26 | + 'Purge references to a store' |
27 | |
28 | - args = ['value'] |
29 | + args = ['store_id'] |
30 | |
31 | |
32 | class Resolve(_Method): |
33 | |
34 | === modified file 'dmedia-service' |
35 | --- dmedia-service 2012-11-25 07:25:50 +0000 |
36 | +++ dmedia-service 2012-11-26 00:24:26 +0000 |
37 | @@ -181,11 +181,6 @@ |
38 | self.snapshot_queue_in.put(dbname) |
39 | return True |
40 | |
41 | - @dbus.service.method(IFACE, in_signature='as', out_signature='') |
42 | - def SnapshotMany(self, names): |
43 | - for dbname in names: |
44 | - self.Snapshot(dbname) |
45 | - |
46 | @dbus.service.method(IFACE, in_signature='', out_signature='') |
47 | def SnapshotAll(self): |
48 | log.info('Dmedia.SnapshotAll()') |
49 | @@ -209,9 +204,7 @@ |
50 | log.info('Starting CouchDB took %.3f', time.time() - start) |
51 | self.core = Core(env) |
52 | self.core.load_identity(self.couch.machine, self.couch.user) |
53 | - self.core.init_default_store() |
54 | - if self.core.local.get('default_store') is None: |
55 | - self.core.set_default_store('shared') |
56 | + self.core.load_default_filestore(self.couch.basedir) |
57 | self.env_s = dumps(self.core.env, pretty=True) |
58 | log.info('Finished core startup in %.3f', time.time() - start) |
59 | GObject.timeout_add(250, self.on_idle1) |
60 | @@ -468,12 +461,11 @@ |
61 | self.core.create_filestore(parentdir) |
62 | return self.LocalDmedia() |
63 | |
64 | - @dbus.service.method(IFACE, in_signature='s', out_signature='s') |
65 | - def SetDefaultStore(self, value): |
66 | - value = str(value) |
67 | - log.info('Dmedia.SetDefaultStore(%r)', value) |
68 | - self.core.set_default_store(value) |
69 | - return self.LocalDmedia() |
70 | + @dbus.service.method(IFACE, in_signature='s', out_signature='') |
71 | + def PurgeStore(self, store_id): |
72 | + store_id = str(store_id) |
73 | + log.info('Dmedia.PurgeStore(%r)', store_id) |
74 | + start_thread(self.core.purge_store, store_id) |
75 | |
76 | @dbus.service.method(IFACE, in_signature='s', out_signature='s') |
77 | def Resolve(self, _id): |
78 | |
79 | === modified file 'dmedia/core.py' |
80 | --- dmedia/core.py 2012-11-18 11:57:51 +0000 |
81 | +++ dmedia/core.py 2012-11-26 00:24:26 +0000 |
82 | @@ -42,7 +42,7 @@ |
83 | from base64 import b64encode |
84 | |
85 | from microfiber import Server, Database, NotFound, Conflict, BulkConflict |
86 | -from filestore import FileStore, check_root_hash, check_id |
87 | +from filestore import FileStore, check_root_hash, check_id, DOTNAME |
88 | |
89 | import dmedia |
90 | from dmedia.parallel import start_thread, start_process |
91 | @@ -54,8 +54,10 @@ |
92 | |
93 | log = logging.getLogger() |
94 | LOCAL_ID = '_local/dmedia' |
95 | +HOME = path.abspath(os.environ['HOME']) |
96 | +if not path.isdir(HOME): |
97 | + raise Exception('$HOME is not a directory: {!r}'.format(HOME)) |
98 | SHARED = '/home' |
99 | -PRIVATE = path.abspath(os.environ['HOME']) |
100 | |
101 | |
102 | def start_httpd(couch_env, ssl_config): |
103 | @@ -185,18 +187,37 @@ |
104 | log.exception('Error updating project stats for %r', project_id) |
105 | |
106 | |
107 | +def migrate_shared(srcdir, dstdir): |
108 | + try: |
109 | + count = 0 |
110 | + src = FileStore(srcdir) |
111 | + dst = FileStore(dstdir) |
112 | + log.info('Migrating files from %r to %r', src, dst) |
113 | + for st in src: |
114 | + if dst.exists(st.id): |
115 | + continue |
116 | + log.info('Migrating %s %s', st.id, st.size) |
117 | + try: |
118 | + os.rename(st.name, dst.path(st.id)) |
119 | + except OSError: |
120 | + src.copy(st.id, dst) |
121 | + src.remove(st.id) |
122 | + count += 1 |
123 | + log.info('Migrating %d files from %r to %r', count, src, dst) |
124 | + except Exception: |
125 | + log.exception('Error migrating files from shared FileStore') |
126 | + return count |
127 | + |
128 | + |
129 | class Core: |
130 | - def __init__(self, env, private=None, shared=None): |
131 | + def __init__(self, env): |
132 | self.env = env |
133 | - self._private = (PRIVATE if private is None else private) |
134 | - self._shared = (SHARED if shared is None else shared) |
135 | self.db = util.get_db(env, init=True) |
136 | self.server = self.db.server() |
137 | self.ms = MetaStore(self.db) |
138 | self.stores = LocalStores() |
139 | self.queue = Queue() |
140 | self.thread = None |
141 | - self.default = None |
142 | try: |
143 | self.local = self.db.get(LOCAL_ID) |
144 | except NotFound: |
145 | @@ -230,23 +251,18 @@ |
146 | self.local['user_id'] = user['_id'] |
147 | self.save_local() |
148 | |
149 | - def init_default_store(self): |
150 | - value = self.local.get('default_store') |
151 | - if value not in ('private', 'shared'): |
152 | - log.info('no default FileStore') |
153 | - self.default = None |
154 | - self._sync_stores() |
155 | - return |
156 | - if value == 'shared' and not util.isfilestore(self._shared): |
157 | - log.warning('Switching to private, no shared FileStore at %r', self._shared) |
158 | - value = 'private' |
159 | - self.local['default_store'] = value |
160 | - self.db.save(self.local) |
161 | - parentdir = (self._private if value == 'private' else self._shared) |
162 | + def load_default_filestore(self, parentdir): |
163 | + if util.isfilestore(HOME) and not util.isfilestore(parentdir): |
164 | + src = FileStore(HOME) |
165 | + src.init_dirs() |
166 | + dstdir = path.join(parentdir, DOTNAME) |
167 | + log.info('Moving %r to %r', src.basedir, dstdir) |
168 | + os.rename(src.basedir, dstdir) |
169 | + self.parentdir = parentdir |
170 | (fs, doc) = util.init_filestore(parentdir) |
171 | - self.default = fs |
172 | - log.info('Connecting default FileStore %r at %r', fs.id, fs.parentdir) |
173 | + log.info('Default FileStore %s at %r', doc['_id'], parentdir) |
174 | self._add_filestore(fs, doc) |
175 | + return fs |
176 | |
177 | def _sync_stores(self): |
178 | self.local['stores'] = self.stores.local_stores() |
179 | @@ -270,6 +286,13 @@ |
180 | self._sync_stores() |
181 | |
182 | def _background_worker(self): |
183 | + if util.isfilestore(SHARED): |
184 | + log.info('Running migration...') |
185 | + process = start_process(migrate_shared, SHARED, self.parentdir) |
186 | + process.join() |
187 | + store_id = util.get_filestore_id(SHARED) |
188 | + if store_id is not None: |
189 | + self.purge_store(store_id) |
190 | log.info('Background worker listing to queue...') |
191 | while True: |
192 | try: |
193 | @@ -327,19 +350,6 @@ |
194 | def update_project(self, project_id): |
195 | update_project(self.db, project_id) |
196 | |
197 | - def set_default_store(self, value): |
198 | - if value not in ('private', 'shared', 'none'): |
199 | - raise ValueError( |
200 | - "need 'private', 'shared', or 'none'; got {!r}".format(value) |
201 | - ) |
202 | - if self.local.get('default_store') != value: |
203 | - self.local['default_store'] = value |
204 | - self.db.save(self.local) |
205 | - if self.default is not None: |
206 | - self.disconnect_filestore(self.default.parentdir, self.default.id) |
207 | - self.default = None |
208 | - self.init_default_store() |
209 | - |
210 | def create_filestore(self, parentdir): |
211 | """ |
212 | Create a new file-store in *parentdir*. |
213 | @@ -412,16 +422,36 @@ |
214 | * ``doc['partial'][store_id]`` |
215 | |
216 | Some scenarios in which you might want to do this: |
217 | - |
218 | + |
219 | 1. The HDD was run over by a bus, the data is gone. We need to |
220 | embrace reality, the sooner the better. |
221 | |
222 | 2. We're going to format or otherwise repurpose an HDD. Ideally, we |
223 | would have called `Core2.downgrade_store()` first. |
224 | - |
225 | + |
226 | Note that this method makes sense for remote cloud stores as well as for |
227 | local file-stores |
228 | """ |
229 | + log.info('Purging store %s', store_id) |
230 | + ids = [] |
231 | + while True: |
232 | + rows = self.db.view('file', 'stored', |
233 | + key=store_id, |
234 | + include_docs=True, |
235 | + limit=25, |
236 | + )['rows'] |
237 | + if not rows: |
238 | + break |
239 | + ids.extend(r['id'] for r in rows) |
240 | + docs = [r['doc'] for r in rows] |
241 | + for doc in docs: |
242 | + del doc['stored'][store_id] |
243 | + try: |
244 | + self.db.save_many(docs) |
245 | + except BulkConflict: |
246 | + log.exception('Conflict purging %s', store_id) |
247 | + log.info('Purged %d references to %s', len(ids), store_id) |
248 | + return ids |
249 | |
250 | def stat(self, _id): |
251 | doc = self.db.get(_id) |
252 | |
253 | === modified file 'dmedia/tests/base.py' |
254 | --- dmedia/tests/base.py 2012-05-06 22:10:00 +0000 |
255 | +++ dmedia/tests/base.py 2012-11-26 00:24:26 +0000 |
256 | @@ -33,6 +33,7 @@ |
257 | from random import SystemRandom |
258 | from zipfile import ZipFile |
259 | |
260 | +import filestore |
261 | from filestore import File, Leaf, ContentHash, Batch, Hasher, LEAF_SIZE |
262 | from filestore import scandir |
263 | from microfiber import random_id |
264 | @@ -42,6 +43,10 @@ |
265 | random = SystemRandom() |
266 | |
267 | |
268 | +def random_file_id(): |
269 | + return random_id(filestore.DIGEST_BYTES) |
270 | + |
271 | + |
272 | class DummyQueue(object): |
273 | def __init__(self): |
274 | self.items = [] |
275 | |
276 | === modified file 'dmedia/tests/test_core.py' |
277 | --- dmedia/tests/test_core.py 2012-10-31 20:42:27 +0000 |
278 | +++ dmedia/tests/test_core.py 2012-11-26 00:24:26 +0000 |
279 | @@ -41,7 +41,7 @@ |
280 | from dmedia import util, core |
281 | |
282 | from .couch import CouchCase |
283 | -from .base import TempDir |
284 | +from .base import TempDir, random_file_id |
285 | |
286 | |
287 | class TestFunctions(TestCase): |
288 | @@ -64,6 +64,32 @@ |
289 | } |
290 | ) |
291 | |
292 | + def test_migrate_shared(self): |
293 | + tmp1 = TempDir() |
294 | + tmp2 = TempDir() |
295 | + src = filestore.FileStore(tmp1.dir) |
296 | + dst = filestore.FileStore(tmp2.dir) |
297 | + st_list = [] |
298 | + for i in range(10): |
299 | + (file, ch) = tmp1.random_file() |
300 | + os.rename(file.name, src.path(ch.id)) |
301 | + st = src.stat(ch.id) |
302 | + assert st.size == ch.file_size |
303 | + st_list.append(st) |
304 | + st_list.sort(key=lambda st: st.id) |
305 | + self.assertEqual(list(src), st_list) |
306 | + self.assertEqual(list(dst), []) |
307 | + self.assertEqual(core.migrate_shared(tmp1.dir, tmp2.dir), 10) |
308 | + self.assertEqual(list(src), []) |
309 | + self.assertEqual( |
310 | + [st.id for st in dst], |
311 | + [st.id for st in st_list] |
312 | + ) |
313 | + for st in st_list: |
314 | + ch = dst.verify(st.id) |
315 | + self.assertEqual(st.size, ch.file_size) |
316 | + self.assertEqual(dst.stat(st.id).mtime, st.mtime) |
317 | + |
318 | |
319 | class TestCouchFunctions(CouchCase): |
320 | def test_projects_iter(self): |
321 | @@ -135,191 +161,6 @@ |
322 | } |
323 | ) |
324 | |
325 | - def test_init_default_store(self): |
326 | - private = TempDir() |
327 | - shared = TempDir() |
328 | - machine_id = random_id() |
329 | - |
330 | - # Test when default_store is missing |
331 | - inst = core.Core(self.env, private.dir, shared.dir) |
332 | - self.assertEqual(inst._private, private.dir) |
333 | - self.assertEqual(inst._shared, shared.dir) |
334 | - inst.init_default_store() |
335 | - self.assertIsNone(inst.default) |
336 | - self.assertEqual(inst.local, |
337 | - { |
338 | - '_id': '_local/dmedia', |
339 | - 'stores': {}, |
340 | - } |
341 | - ) |
342 | - |
343 | - # Test when default_store is 'private' |
344 | - inst = core.Core(self.env, private.dir, shared.dir) |
345 | - self.assertEqual(inst._private, private.dir) |
346 | - self.assertEqual(inst._shared, shared.dir) |
347 | - inst.local['default_store'] = 'private' |
348 | - self.assertFalse(util.isfilestore(private.dir)) |
349 | - inst.init_default_store() |
350 | - self.assertEqual( |
351 | - set(inst.local['stores']), |
352 | - set([private.dir]) |
353 | - ) |
354 | - store_id = inst.local['stores'][private.dir]['id'] |
355 | - (fs1, doc) = util.get_filestore(private.dir, store_id) |
356 | - self.assertEqual(inst.local, |
357 | - { |
358 | - '_id': '_local/dmedia', |
359 | - '_rev': '0-1', |
360 | - 'default_store': 'private', |
361 | - 'stores': { |
362 | - fs1.parentdir: { |
363 | - 'id': fs1.id, |
364 | - 'copies': fs1.copies, |
365 | - } |
366 | - } |
367 | - } |
368 | - ) |
369 | - |
370 | - # Again test when default_store is 'private' to make sure local isn't |
371 | - # updated needlessly |
372 | - inst = core.Core(self.env, private.dir, shared.dir) |
373 | - self.assertEqual(inst._private, private.dir) |
374 | - self.assertEqual(inst._shared, shared.dir) |
375 | - inst.init_default_store() |
376 | - self.assertEqual(inst.local, |
377 | - { |
378 | - '_id': '_local/dmedia', |
379 | - '_rev': '0-1', |
380 | - 'default_store': 'private', |
381 | - 'stores': { |
382 | - fs1.parentdir: { |
383 | - 'id': fs1.id, |
384 | - 'copies': fs1.copies, |
385 | - } |
386 | - } |
387 | - } |
388 | - ) |
389 | - |
390 | - # Test when default_store is 'shared' (which we're assuming exists) |
391 | - self.assertFalse(util.isfilestore(shared.dir)) |
392 | - (fs2, doc) = util.init_filestore(shared.dir) |
393 | - inst = core.Core(self.env, private.dir, shared.dir) |
394 | - self.assertEqual(inst._private, private.dir) |
395 | - self.assertEqual(inst._shared, shared.dir) |
396 | - inst.local['default_store'] = 'shared' |
397 | - inst.init_default_store() |
398 | - self.assertEqual(inst.local, |
399 | - { |
400 | - '_id': '_local/dmedia', |
401 | - '_rev': '0-2', |
402 | - 'default_store': 'shared', |
403 | - 'stores': { |
404 | - fs2.parentdir: { |
405 | - 'id': fs2.id, |
406 | - 'copies': fs2.copies, |
407 | - } |
408 | - } |
409 | - } |
410 | - ) |
411 | - |
412 | - # Test when default_store is 'shared' and it doesn't exist, in which |
413 | - # case it should automatically switch to 'private' instead |
414 | - nope = TempDir() |
415 | - inst = core.Core(self.env, private.dir, nope.dir) |
416 | - self.assertEqual(inst._private, private.dir) |
417 | - self.assertEqual(inst._shared, nope.dir) |
418 | - inst.local['default_store'] = 'shared' |
419 | - inst.init_default_store() |
420 | - self.assertEqual(inst.local, |
421 | - { |
422 | - '_id': '_local/dmedia', |
423 | - '_rev': '0-4', |
424 | - 'default_store': 'private', |
425 | - 'stores': { |
426 | - fs1.parentdir: { |
427 | - 'id': fs1.id, |
428 | - 'copies': fs1.copies, |
429 | - } |
430 | - } |
431 | - } |
432 | - ) |
433 | - |
434 | - def test_set_default_store(self): |
435 | - private = TempDir() |
436 | - shared = TempDir() |
437 | - (fs1, doc1) = util.init_filestore(private.dir) |
438 | - (fs2, doc2) = util.init_filestore(shared.dir) |
439 | - inst = core.Core(self.env, private.dir, shared.dir) |
440 | - self.assertEqual(inst._private, private.dir) |
441 | - self.assertEqual(inst._shared, shared.dir) |
442 | - |
443 | - with self.assertRaises(ValueError) as cm: |
444 | - inst.set_default_store('foobar') |
445 | - self.assertEqual( |
446 | - str(cm.exception), |
447 | - "need 'private', 'shared', or 'none'; got 'foobar'" |
448 | - ) |
449 | - self.assertEqual(inst.local, |
450 | - { |
451 | - '_id': '_local/dmedia', |
452 | - 'stores': {}, |
453 | - } |
454 | - ) |
455 | - |
456 | - # Test with 'private' |
457 | - inst.set_default_store('private') |
458 | - self.assertEqual(inst.local, |
459 | - { |
460 | - '_id': '_local/dmedia', |
461 | - '_rev': '0-2', |
462 | - 'default_store': 'private', |
463 | - 'stores': { |
464 | - fs1.parentdir: {'id': fs1.id, 'copies': 1}, |
465 | - }, |
466 | - } |
467 | - ) |
468 | - |
469 | - # Test with 'shared' |
470 | - inst.set_default_store('shared') |
471 | - self.assertEqual(inst.local, |
472 | - { |
473 | - '_id': '_local/dmedia', |
474 | - '_rev': '0-5', |
475 | - 'default_store': 'shared', |
476 | - 'stores': { |
477 | - fs2.parentdir: {'id': fs2.id, 'copies': 1}, |
478 | - }, |
479 | - } |
480 | - ) |
481 | - |
482 | - # Test with 'none' |
483 | - inst.set_default_store('none') |
484 | - self.assertEqual(inst.local, |
485 | - { |
486 | - '_id': '_local/dmedia', |
487 | - '_rev': '0-7', |
488 | - 'default_store': 'none', |
489 | - 'stores': {}, |
490 | - } |
491 | - ) |
492 | - |
493 | - # Test with 'shared' when it doesn't exist |
494 | - nope = TempDir() |
495 | - inst = core.Core(self.env, private.dir, nope.dir) |
496 | - self.assertEqual(inst._private, private.dir) |
497 | - self.assertEqual(inst._shared, nope.dir) |
498 | - inst.set_default_store('shared') |
499 | - self.assertEqual(inst.local, |
500 | - { |
501 | - '_id': '_local/dmedia', |
502 | - '_rev': '0-10', |
503 | - 'default_store': 'private', |
504 | - 'stores': { |
505 | - fs1.parentdir: {'id': fs1.id, 'copies': 1}, |
506 | - }, |
507 | - } |
508 | - ) |
509 | - |
510 | def test_create_filestore(self): |
511 | inst = core.Core(self.env) |
512 | |
513 | @@ -502,6 +343,97 @@ |
514 | inst.disconnect_filestore(fs1.parentdir, fs1.id) |
515 | self.assertEqual(str(cm.exception), repr(fs1.parentdir)) |
516 | |
517 | + def test_purge_store(self): |
518 | + store_id1 = random_id() |
519 | + store_id2 = random_id() |
520 | + store_id3 = random_id() |
521 | + inst = core.Core(self.env) |
522 | + db = inst.db |
523 | + |
524 | + # Test when empty |
525 | + self.assertEqual(inst.purge_store(store_id1), []) |
526 | + |
527 | + docs = [ |
528 | + { |
529 | + '_id': random_file_id(), |
530 | + 'type': 'dmedia/file', |
531 | + 'bytes': 1776, |
532 | + 'stored': { |
533 | + store_id1: { |
534 | + 'copies': 1, |
535 | + 'mtime': 1234567890, |
536 | + }, |
537 | + store_id2: { |
538 | + 'copies': 2, |
539 | + 'mtime': 1234567891, |
540 | + }, |
541 | + }, |
542 | + } |
543 | + for i in range(533) |
544 | + ] |
545 | + ids = [doc['_id'] for doc in docs] |
546 | + ids.sort() |
547 | + db.save_many(docs) |
548 | + |
549 | + # Test when store isn't present |
550 | + self.assertEqual(inst.purge_store(store_id3), []) |
551 | + for doc in docs: |
552 | + self.assertEqual(db.get(doc['_id']), doc) |
553 | + |
554 | + # Purge one of the stores, make sure the other remains |
555 | + self.assertEqual(inst.purge_store(store_id1), ids) |
556 | + for doc in db.get_many(ids): |
557 | + _id = doc['_id'] |
558 | + rev = doc.pop('_rev') |
559 | + self.assertTrue(rev.startswith('2-')) |
560 | + self.assertEqual( |
561 | + doc, |
562 | + { |
563 | + '_id': _id, |
564 | + 'type': 'dmedia/file', |
565 | + 'bytes': 1776, |
566 | + 'stored': { |
567 | + store_id2: { |
568 | + 'copies': 2, |
569 | + 'mtime': 1234567891, |
570 | + }, |
571 | + }, |
572 | + } |
573 | + ) |
574 | + |
575 | + # Purge the other store |
576 | + self.assertEqual(inst.purge_store(store_id2), ids) |
577 | + for doc in db.get_many(ids): |
578 | + _id = doc['_id'] |
579 | + rev = doc.pop('_rev') |
580 | + self.assertTrue(rev.startswith('3-')) |
581 | + self.assertEqual( |
582 | + doc, |
583 | + { |
584 | + '_id': _id, |
585 | + 'type': 'dmedia/file', |
586 | + 'bytes': 1776, |
587 | + 'stored': {}, |
588 | + } |
589 | + ) |
590 | + |
591 | + # Purge both again, make sure no doc changes result: |
592 | + self.assertEqual(inst.purge_store(store_id1), []) |
593 | + self.assertEqual(inst.purge_store(store_id2), []) |
594 | + for doc in db.get_many(ids): |
595 | + _id = doc['_id'] |
596 | + rev = doc.pop('_rev') |
597 | + self.assertTrue(rev.startswith('3-')) |
598 | + self.assertEqual( |
599 | + doc, |
600 | + { |
601 | + '_id': _id, |
602 | + 'type': 'dmedia/file', |
603 | + 'bytes': 1776, |
604 | + 'stored': {}, |
605 | + } |
606 | + ) |
607 | + |
608 | def test_update_atime(self): |
609 | inst = core.Core(self.env) |
610 | _id = random_id() |
611 | |
612 | === modified file 'dmedia/tests/test_util.py' |
613 | --- dmedia/tests/test_util.py 2012-07-09 14:45:38 +0000 |
614 | +++ dmedia/tests/test_util.py 2012-11-26 00:24:26 +0000 |
615 | @@ -51,7 +51,32 @@ |
616 | tmp.makedirs('.dmedia') |
617 | self.assertTrue(util.isfilestore(tmp.dir)) |
618 | |
619 | - def test_getfilestore(self): |
620 | + def test_get_filestore_id(self): |
621 | + tmp = TempDir() |
622 | + doc = schema.create_filestore(1) |
623 | + _id = doc['_id'] |
624 | + |
625 | + # Test when .dmedia/ doesn't exist |
626 | + self.assertIsNone(util.get_filestore_id(tmp.dir)) |
627 | + |
628 | + # Test when .dmedia/ exists, but store.json doesn't: |
629 | + tmp.makedirs('.dmedia') |
630 | + self.assertIsNone(util.get_filestore_id(tmp.dir)) |
631 | + |
632 | + # Test when .dmedia/store.json exists |
633 | + store = tmp.join('.dmedia', 'store.json') |
634 | + json.dump(doc, open(store, 'w')) |
635 | + self.assertEqual(util.get_filestore_id(tmp.dir), _id) |
636 | + |
637 | + # Test with bad JSON |
638 | + open(store, 'wb').write(b'bad JSON, no cookie for you') |
639 | + self.assertIsNone(util.get_filestore_id(tmp.dir)) |
640 | + |
641 | + # Test with non-dict |
642 | + json.dump('hello', open(store, 'w')) |
643 | + self.assertIsNone(util.get_filestore_id(tmp.dir)) |
644 | + |
645 | + def test_get_filestore(self): |
646 | tmp = TempDir() |
647 | doc = schema.create_filestore(1) |
648 | |
649 | |
650 | === modified file 'dmedia/util.py' |
651 | --- dmedia/util.py 2012-10-19 00:11:46 +0000 |
652 | +++ dmedia/util.py 2012-11-26 00:24:26 +0000 |
653 | @@ -43,6 +43,15 @@ |
654 | return path.isdir(path.join(parentdir, DOTNAME)) |
655 | |
656 | |
657 | +def get_filestore_id(parentdir): |
658 | + store = path.join(parentdir, DOTNAME, 'store.json') |
659 | + try: |
660 | + doc = json.load(open(store, 'r')) |
661 | + return doc['_id'] |
662 | + except Exception: |
663 | + pass |
664 | + |
665 | + |
666 | def get_filestore(parentdir, store_id, copies=None): |
667 | store = path.join(parentdir, DOTNAME, 'store.json') |
668 | doc = json.load(open(store, 'r')) |