Merge lp:~jderose/dmedia/closer-to-restful into lp:dmedia
- closer-to-restful
- Merge into trunk
Proposed by
Jason Gerard DeRose
Status: | Merged |
---|---|
Approved by: | Jason Gerard DeRose |
Approved revision: | 189 |
Merge reported by: | Jason Gerard DeRose |
Merged at revision: | not available |
Proposed branch: | lp:~jderose/dmedia/closer-to-restful |
Merge into: | lp:dmedia |
Diff against target: |
2046 lines (+951/-306) 19 files modified
dmedia-gtk (+2/-1) dmedia-service (+21/-12) dmedia/__init__.py (+28/-0) dmedia/abstractcouch.py (+158/-0) dmedia/constants.py (+6/-1) dmedia/importer.py (+47/-51) dmedia/metastore.py (+13/-14) dmedia/service.py (+26/-15) dmedia/tests/__init__.py (+35/-0) dmedia/tests/couch.py (+63/-0) dmedia/tests/helpers.py (+1/-21) dmedia/tests/test_abstractcouch.py (+207/-0) dmedia/tests/test_client.py (+4/-1) dmedia/tests/test_importer.py (+151/-136) dmedia/tests/test_metastore.py (+11/-14) dmedia/tests/test_service.py (+6/-5) dmedia/tests/test_workers.py (+116/-24) dmedia/workers.py (+51/-11) setup.py (+5/-0) |
To merge this branch: | bzr merge lp:~jderose/dmedia/closer-to-restful |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Jason Gerard DeRose | Approve | ||
David Jordan | Approve | ||
Review via email: mp+50575@code.launchpad.net |
Commit message
Description of the change
Okay, I apologize isn't going to be easy to review. More cleanup/refactoring work is still needed, and I certainly need to spend time documenting all of this. All the same, this fixes an important stability problem that dmedia has been experiencing under Natty:
https:/
I'm confident that this is the right step architecturally for dmedia, I just don't have time to make things all clean and formally documented before 0.4.
To post a comment you must log in.
- 187. By Jason Gerard DeRose
-
Fixed typo dmj726 spotted
- 188. By Jason Gerard DeRose
-
another typo dmj726 spotted
- 189. By Jason Gerard DeRose
-
dmj726 caught me importing time twice
Revision history for this message
Jason Gerard DeRose (jderose) wrote : | # |
Thanks, David!
Revision history for this message
Jason Gerard DeRose (jderose) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'dmedia-gtk' |
2 | --- dmedia-gtk 2011-01-25 12:11:16 +0000 |
3 | +++ dmedia-gtk 2011-02-21 12:01:51 +0000 |
4 | @@ -25,6 +25,7 @@ |
5 | from subprocess import check_call |
6 | import optparse |
7 | import dmedia |
8 | +from dmedia.abstractcouch import get_env |
9 | from dmedia.metastore import MetaStore |
10 | from dmedia import ui |
11 | |
12 | @@ -39,7 +40,7 @@ |
13 | (options, args) = parser.parse_args() |
14 | |
15 | |
16 | -store = MetaStore() |
17 | +store = MetaStore(get_env()) |
18 | app = ui.create_app() |
19 | store.update(app) |
20 | |
21 | |
22 | === modified file 'dmedia-service' |
23 | --- dmedia-service 2011-01-30 21:17:48 +0000 |
24 | +++ dmedia-service 2011-02-21 12:01:51 +0000 |
25 | @@ -21,26 +21,30 @@ |
26 | # You should have received a copy of the GNU Affero General Public License along |
27 | # with `dmedia`. If not, see <http://www.gnu.org/licenses/>. |
28 | |
29 | -from dmedia.util import configure_logging |
30 | -from dmedia.constants import BUS |
31 | import sys |
32 | import optparse |
33 | -from os import path |
34 | + |
35 | import dbus |
36 | -import dbus.service |
37 | -import dbus.mainloop.glib |
38 | +from dbus.mainloop.glib import DBusGMainLoop |
39 | import gobject |
40 | |
41 | - |
42 | -parser = optparse.OptionParser() |
43 | +DBusGMainLoop(set_as_default=True) |
44 | +gobject.threads_init() |
45 | + |
46 | +import dmedia |
47 | +from dmedia.constants import BUS, DBNAME |
48 | +from dmedia.abstractcouch import get_env |
49 | + |
50 | + |
51 | +parser = optparse.OptionParser(version=dmedia.__version__) |
52 | parser.add_option('--bus', |
53 | default=BUS, |
54 | help='D-Bus bus name; default is %r' % BUS, |
55 | ) |
56 | parser.add_option('--dbname', |
57 | metavar='DIR', |
58 | - default=None, |
59 | - help='dmedia CouchDB database', |
60 | + default=DBNAME, |
61 | + help='CouchDB database; default is %r' % DBNAME, |
62 | ) |
63 | parser.add_option('--no-gui', |
64 | action='store_true', |
65 | @@ -57,10 +61,15 @@ |
66 | |
67 | if __name__ == '__main__': |
68 | (options, args) = parser.parse_args() |
69 | + |
70 | + from dmedia.util import configure_logging |
71 | configure_logging('service') |
72 | + |
73 | + env = get_env(options.dbname) |
74 | + env['bus'] = options.bus |
75 | + env['no_gui'] = options.no_gui |
76 | + |
77 | from dmedia import service |
78 | mainloop = gobject.MainLoop() |
79 | - obj = service.DMedia(mainloop.quit, |
80 | - options.bus, options.dbname, options.no_gui |
81 | - ) |
82 | + obj = service.DMedia(env, mainloop.quit) |
83 | mainloop.run() |
84 | |
85 | === modified file 'dmedia/__init__.py' |
86 | --- dmedia/__init__.py 2011-01-26 16:23:49 +0000 |
87 | +++ dmedia/__init__.py 2011-02-21 12:01:51 +0000 |
88 | @@ -41,3 +41,31 @@ |
89 | assert path.isdir(packagedir) |
90 | datadir = path.join(packagedir, 'data') |
91 | assert path.isdir(datadir) |
92 | + |
93 | + |
94 | +def get_env(dbname=None): |
95 | + """ |
96 | + Get desktopcouch runtime info in most the lightweight way possible. |
97 | + |
98 | + Here "lightweight" doesn't necessarily mean "fast", but with as few imports |
99 | + as possible to keep the dmedia memory footprint small. |
100 | + """ |
101 | + import dbus |
102 | + DC = 'org.desktopcouch.CouchDB' |
103 | + conn = dbus.SessionBus() |
104 | + proxy = conn.get_object(DC, '/') |
105 | + getPort = proxy.get_dbus_method('getPort', dbus_interface=DC) |
106 | + port = getPort() |
107 | + url = 'http://localhost:%d/' % port |
108 | + |
109 | + import gnomekeyring |
110 | + data = gnomekeyring.find_items_sync( |
111 | + gnomekeyring.ITEM_GENERIC_SECRET, |
112 | + {'desktopcouch': 'oauth'} |
113 | + ) |
114 | + oauth = dict(zip( |
115 | + ('consumer_key', 'consumer_secret', 'token', 'token_secret'), |
116 | + data[0].secret.split(':') |
117 | + )) |
118 | + |
119 | + return dict(port=port, url=url, oauth=oauth, dbname=dbname) |
120 | |
121 | === added file 'dmedia/abstractcouch.py' |
122 | --- dmedia/abstractcouch.py 1970-01-01 00:00:00 +0000 |
123 | +++ dmedia/abstractcouch.py 2011-02-21 12:01:51 +0000 |
124 | @@ -0,0 +1,158 @@ |
125 | +# Authors: |
126 | +# Jason Gerard DeRose <jderose@novacut.com> |
127 | +# |
128 | +# dmedia: distributed media library |
129 | +# Copyright (C) 2011 Jason Gerard DeRose <jderose@novacut.com> |
130 | +# |
131 | +# This file is part of `dmedia`. |
132 | +# |
133 | +# `dmedia` is free software: you can redistribute it and/or modify it under the |
134 | +# terms of the GNU Affero General Public License as published by the Free |
135 | +# Software Foundation, either version 3 of the License, or (at your option) any |
136 | +# later version. |
137 | +# |
138 | +# `dmedia` is distributed in the hope that it will be useful, but WITHOUT ANY |
139 | +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
140 | +# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more |
141 | +# details. |
142 | +# |
143 | +# You should have received a copy of the GNU Affero General Public License along |
144 | +# with `dmedia`. If not, see <http://www.gnu.org/licenses/>. |
145 | + |
146 | +""" |
147 | +Small abstraction to hide desktopcouch vs system-wide CouchDB details. |
148 | + |
149 | +dmedia is obviously very focused on desktop use, for which desktopcouch is a |
150 | +godsend. However, dmedia also needs to run on headless, minimal servers. We |
151 | +want to abstract the difference to a single place in the code so the rest of the |
152 | +code can just work without concern for whether we're connecting to a CouchDB |
153 | +instance launched by desktopcouch, or a system-wide CouchDB started with an |
154 | +init script. |
155 | + |
156 | +This would be a great feature to upstream into desktopcouch, but for now we can |
157 | +give it a good trial by fire in dmedia. |
158 | + |
159 | +For more details, see: |
160 | + |
161 | + https://bugs.launchpad.net/dmedia/+bug/722035 |
162 | +""" |
163 | + |
164 | +import logging |
165 | + |
166 | +from couchdb import Server, ResourceNotFound |
167 | +try: |
168 | + from desktopcouch.records.http import OAuthSession |
169 | +except ImportError: |
170 | + OAuthSession = None |
171 | +if OAuthSession is not None: |
172 | + from desktopcouch.application.platform import find_port |
173 | + from desktopcouch.application.local_files import get_oauth_tokens |
174 | + |
175 | + |
176 | +log = logging.getLogger() |
177 | + |
178 | + |
179 | +def get_couchdb_server(env): |
180 | + """ |
181 | + Return `couchdb.Server` for desktopcouch or system-wide CouchDB. |
182 | + |
183 | + The *env* argument is a ``dict`` instance containing information about how |
184 | + to connect to CouchDB. It must always contain a ``"url"`` key, which is the |
185 | + URL as passed to ``couchdb.Server()``. |
186 | + |
187 | + If *env* contains an ``"oauth"`` key, a |
188 | + ``desktopcouch.records.http.OAuthSession`` will be created and passed to |
189 | + ``couchdb.Server()``. |
190 | + |
191 | + For example, to connect to the system-wide CouchDB, pass an *env* like this: |
192 | + |
193 | + >>> env = {'url': 'http://localhost:5984/'} |
194 | + |
195 | + Or to connect to a per-user desktopcouch CouchDB, pass an *env* like this: |
196 | + |
197 | + >>> env = { |
198 | + ... 'url': 'http://localhost:51074/', |
199 | + ... 'oauth': { |
200 | + ... 'consumer_secret': 'no', |
201 | + ... 'token': 'way', |
202 | + ... 'consumer_key': 'too', |
203 | + ... 'token_secret': 'secret' |
204 | + ... } |
205 | + ... } |
206 | + |
207 | + When using desktopcouch, you can build an *env* with the correct port and |
208 | + oauth credentials like this: |
209 | + |
210 | + >>> from desktopcouch.application.platform import find_port |
211 | + >>> from desktopcouch.application.local_files import get_oauth_tokens |
212 | + >>> env = { |
213 | + ... 'url': 'http://localhost:%d/' % find_port(), |
214 | + ... 'oauth': get_oauth_tokens(), |
215 | + ... } |
216 | + |
217 | + Note the reason *env* is a ``dict`` is so it's easily extensible and can |
218 | + carry other useful information, for example the dmedia database name so that |
219 | + an alternate name can be provided for, say, unit tests (like |
220 | + ``"dmedia_test"``). |
221 | + |
222 | + The goal is to have all the needed information is one easily serialized |
223 | + piece of data (important for testing across multiple processes). |
224 | + """ |
225 | + url = env.get('url', 'http://localhost:5984/') |
226 | + log.info('CouchDB server is %r', url) |
227 | + if env.get('oauth') is None: |
228 | + session = None |
229 | + else: |
230 | + if OAuthSession is None: |
231 | + raise ValueError( |
232 | + "provided env['oauth'] but OAuthSession not available: %r" % |
233 | + (env,) |
234 | + ) |
235 | + log.info('Using desktopcouch `OAuthSession`') |
236 | + session = OAuthSession(credentials=env['oauth']) |
237 | + return Server(url, session=session) |
238 | + |
239 | + |
240 | +def get_dmedia_db(env, server=None): |
241 | + """ |
242 | + Return the dmedia database specified by *env*. |
243 | + |
244 | + The database name is determined by ``env['dbname']``. If the ``"dbname"`` |
245 | + key is missing or is ``None``, the default database name ``"dmedia"`` is |
246 | + used. |
247 | + |
248 | + If the database does not exist, it will be created. |
249 | + |
250 | + If *server* is ``None``, one is created based on *env* by calling |
251 | + `get_couchdb_server()`. |
252 | + |
253 | + Returns a ``couchdb.Database`` instance. |
254 | + """ |
255 | + if server is None: |
256 | + server = get_couchdb_server(env) |
257 | + dbname = env.get('dbname') |
258 | + if dbname is None: |
259 | + dbname = 'dmedia' |
260 | + log.info('CouchDB database is %r', dbname) |
261 | + try: |
262 | + return server[dbname] |
263 | + except ResourceNotFound: |
264 | + return server.create(dbname) |
265 | + |
266 | + |
267 | +def get_env(dbname=None): |
268 | + """ |
269 | + Return default *env*. |
270 | + |
271 | + This will return an appropriate *env* based on whether desktopcouch is |
272 | + available. Not a perfect solution, but works for now. |
273 | + """ |
274 | + if OAuthSession is None: |
275 | + return {'dbname': dbname} |
276 | + port = find_port() |
277 | + return { |
278 | + 'port': port, |
279 | + 'url': 'http://localhost:%d/' % port, |
280 | + 'oauth': get_oauth_tokens(), |
281 | + 'dbname': dbname, |
282 | + } |
283 | |
284 | === modified file 'dmedia/constants.py' |
285 | --- dmedia/constants.py 2011-02-06 11:11:03 +0000 |
286 | +++ dmedia/constants.py 2011-02-21 12:01:51 +0000 |
287 | @@ -27,6 +27,8 @@ |
288 | import mimetypes |
289 | mimetypes.init() |
290 | |
291 | +DBNAME = 'dmedia' |
292 | + |
293 | # Standard read/write buffer size: |
294 | CHUNK_SIZE = 2**20 # 1 MiB |
295 | |
296 | @@ -45,7 +47,10 @@ |
297 | |
298 | # D-Bus releated: |
299 | BUS = 'org.freedesktop.DMedia' |
300 | -INTERFACE = 'org.freedesktop.DMedia' |
301 | +INTERFACE = BUS |
302 | +DC_BUS = 'org.desktopcouch.CouchDB' |
303 | +DC_INTERFACE = DC_BUS |
304 | + |
305 | |
306 | # Standard format for TypeError message: |
307 | TYPE_ERROR = '%s: need a %r; got a %r: %r' |
308 | |
309 | === modified file 'dmedia/importer.py' |
310 | --- dmedia/importer.py 2011-02-19 03:05:53 +0000 |
311 | +++ dmedia/importer.py 2011-02-21 12:01:51 +0000 |
312 | @@ -35,10 +35,12 @@ |
313 | |
314 | from .util import random_id |
315 | from .errors import DuplicateFile |
316 | -from .workers import Worker, Manager, register, isregistered, exception_name |
317 | +from .workers import ( |
318 | + CouchWorker, CouchManager, register, isregistered, exception_name |
319 | +) |
320 | from .filestore import FileStore, quick_id, safe_open, safe_ext, pack_leaves |
321 | -from .metastore import MetaStore |
322 | from .extractor import merge_metadata |
323 | +from .abstractcouch import get_env, get_couchdb_server, get_dmedia_db |
324 | |
325 | |
326 | mimetypes.init() |
327 | @@ -188,17 +190,14 @@ |
328 | } |
329 | |
330 | |
331 | -class Importer(object): |
332 | - def __init__(self, batch_id, base, extract, dbname=None): |
333 | - self.batch_id = batch_id |
334 | - self.base = base |
335 | - self.extract = extract |
336 | +class ImportWorker(CouchWorker): |
337 | + def __init__(self, env, q, key, args): |
338 | + super(ImportWorker, self).__init__(env, q, key, args) |
339 | + (self.base, self.extract) = args |
340 | self.home = path.abspath(os.environ['HOME']) |
341 | - self.metastore = MetaStore(dbname=dbname) |
342 | - self.db = self.metastore.db |
343 | self.filestore = FileStore( |
344 | path.join(self.home, DOTDIR), |
345 | - self.metastore.machine_id |
346 | + self.env.get('machine_id') |
347 | ) |
348 | try: |
349 | self.db.save(self.filestore._doc) |
350 | @@ -210,6 +209,27 @@ |
351 | self.doc = None |
352 | self._id = None |
353 | |
354 | + def execute(self, base, extract=False): |
355 | + import_id = self.start() |
356 | + self.emit('started', import_id) |
357 | + |
358 | + files = self.scanfiles() |
359 | + total = len(files) |
360 | + self.emit('count', import_id, total) |
361 | + |
362 | + c = 1 |
363 | + for (src, action) in self.import_all_iter(): |
364 | + self.emit('progress', import_id, c, total, |
365 | + dict( |
366 | + action=action, |
367 | + src=src, |
368 | + ) |
369 | + ) |
370 | + c += 1 |
371 | + |
372 | + stats = self.finalize() |
373 | + self.emit('finished', import_id, stats) |
374 | + |
375 | def save(self): |
376 | """ |
377 | Save current 'dmedia/import' record to CouchDB. |
378 | @@ -222,8 +242,8 @@ |
379 | """ |
380 | assert self._id is None |
381 | self.doc = create_import(self.base, |
382 | - batch_id=self.batch_id, |
383 | - machine_id=self.metastore.machine_id, |
384 | + batch_id=self.env.get('batch_id'), |
385 | + machine_id=self.env.get('machine_id'), |
386 | ) |
387 | self._id = self.doc['_id'] |
388 | self.save() |
389 | @@ -368,34 +388,13 @@ |
390 | assert list(t[0] for t in self.filetuples) == self._processed |
391 | self.doc['time_end'] = time.time() |
392 | self.save() |
393 | + dt = self.doc['time_end'] - self.doc['time'] |
394 | + log.info('Completed import of %r in %d:%02d', |
395 | + self.base, dt / 60, dt % 60 |
396 | + ) |
397 | return self.doc['stats'] |
398 | |
399 | |
400 | -class ImportWorker(Worker): |
401 | - def execute(self, batch_id, base, extract=False, dbname=None): |
402 | - |
403 | - adapter = Importer(batch_id, base, extract, dbname) |
404 | - |
405 | - import_id = adapter.start() |
406 | - self.emit('started', import_id) |
407 | - |
408 | - files = adapter.scanfiles() |
409 | - total = len(files) |
410 | - self.emit('count', import_id, total) |
411 | - |
412 | - c = 1 |
413 | - for (src, action) in adapter.import_all_iter(): |
414 | - self.emit('progress', import_id, c, total, |
415 | - dict( |
416 | - action=action, |
417 | - src=src, |
418 | - ) |
419 | - ) |
420 | - c += 1 |
421 | - |
422 | - stats = adapter.finalize() |
423 | - self.emit('finished', import_id, stats) |
424 | - |
425 | |
426 | def to_dbus_stats(stats): |
427 | return dict( |
428 | @@ -414,12 +413,9 @@ |
429 | accum[key][k] += v |
430 | |
431 | |
432 | -class ImportManager(Manager): |
433 | - def __init__(self, callback=None, dbname=None): |
434 | - super(ImportManager, self).__init__(callback) |
435 | - self._dbname = dbname |
436 | - self.metastore = MetaStore(dbname=dbname) |
437 | - self.db = self.metastore.db |
438 | +class ImportManager(CouchManager): |
439 | + def __init__(self, env, callback=None): |
440 | + super(ImportManager, self).__init__(env, callback) |
441 | self.doc = None |
442 | self._total = 0 |
443 | self._completed = 0 |
444 | @@ -437,10 +433,15 @@ |
445 | assert self._workers == {} |
446 | self._total = 0 |
447 | self._completed = 0 |
448 | - self.doc = create_batch(self.metastore.machine_id) |
449 | + self.doc = create_batch(self.env.get('machine_id')) |
450 | self.save() |
451 | self.emit('BatchStarted', self.doc['_id']) |
452 | |
453 | + def get_worker_env(self, worker, key, args): |
454 | + env = dict(self.env) |
455 | + env['batch_id'] = self.doc['_id'] |
456 | + return env |
457 | + |
458 | def _finish_batch(self): |
459 | assert self._workers == {} |
460 | self.doc['time_end'] = time.time() |
461 | @@ -449,6 +450,7 @@ |
462 | to_dbus_stats(self.doc['stats']) |
463 | ) |
464 | self.doc = None |
465 | + self.env = None |
466 | log.info('Batch complete, compacting database...') |
467 | self.db.compact() |
468 | |
469 | @@ -494,10 +496,4 @@ |
470 | return False |
471 | if len(self._workers) == 0: |
472 | self._start_batch() |
473 | - return self.do('ImportWorker', base, |
474 | - self.doc['_id'], base, extract, self._dbname |
475 | - ) |
476 | - |
477 | - def list_imports(self): |
478 | - with self._lock: |
479 | - return sorted(self._workers) |
480 | + return self.start_job('ImportWorker', base, base, extract) |
481 | |
482 | === modified file 'dmedia/metastore.py' |
483 | --- dmedia/metastore.py 2011-02-18 19:22:23 +0000 |
484 | +++ dmedia/metastore.py 2011-02-21 12:01:51 +0000 |
485 | @@ -27,16 +27,11 @@ |
486 | import time |
487 | import socket |
488 | import platform |
489 | + |
490 | import gnomekeyring |
491 | from couchdb import ResourceNotFound, ResourceConflict |
492 | -import desktopcouch |
493 | -from desktopcouch.records.server import CouchDatabase |
494 | -from desktopcouch.records.record import Record |
495 | -from desktopcouch.local_files import DEFAULT_CONTEXT, Context |
496 | -try: |
497 | - from desktopcouch import find_port |
498 | -except ImportError: |
499 | - from desktopcouch.application.platform import find_port |
500 | + |
501 | +from .abstractcouch import get_couchdb_server, get_dmedia_db |
502 | from .util import random_id |
503 | |
504 | |
505 | @@ -182,14 +177,18 @@ |
506 | )), |
507 | ) |
508 | |
509 | - def __init__(self, dbname=None): |
510 | - self.dbname = ('dmedia' if dbname is None else dbname) |
511 | - self.desktop = CouchDatabase(self.dbname, create=True) |
512 | - self.server = self.desktop._server |
513 | - self.db = self.server[self.dbname] |
514 | + def __init__(self, env): |
515 | + self.env = env |
516 | + self.server = get_couchdb_server(env) |
517 | + self.db = get_dmedia_db(env, self.server) |
518 | self.create_views() |
519 | self._machine_id = None |
520 | |
521 | + def get_env(self): |
522 | + env = dict(self.env) |
523 | + env['machine_id'] = self.machine_id |
524 | + return env |
525 | + |
526 | def get_basic_auth(self): |
527 | data = gnomekeyring.find_items_sync( |
528 | gnomekeyring.ITEM_GENERIC_SECRET, |
529 | @@ -199,7 +198,7 @@ |
530 | return (user, password) |
531 | |
532 | def get_port(self): |
533 | - return find_port() |
534 | + return self.env.get('port') |
535 | |
536 | def get_uri(self): |
537 | return 'http://localhost:%s' % self.get_port() |
538 | |
539 | === modified file 'dmedia/service.py' |
540 | --- dmedia/service.py 2011-01-30 21:17:48 +0000 |
541 | +++ dmedia/service.py 2011-02-21 12:01:51 +0000 |
542 | @@ -33,14 +33,11 @@ |
543 | import dbus.service |
544 | import dbus.mainloop.glib |
545 | import gobject |
546 | -from .constants import BUS, INTERFACE, EXT_MAP |
547 | +from .constants import BUS, INTERFACE, DBNAME, EXT_MAP |
548 | from .util import NotifyManager, Timer, import_started, batch_finished |
549 | from .importer import ImportManager |
550 | from .metastore import MetaStore |
551 | |
552 | -gobject.threads_init() |
553 | -dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) |
554 | - |
555 | try: |
556 | import pynotify |
557 | pynotify.init('dmedia') |
558 | @@ -70,23 +67,24 @@ |
559 | 'ImportFinished', |
560 | ]) |
561 | |
562 | - def __init__(self, killfunc=None, bus=None, dbname=None, no_gui=False): |
563 | + def __init__(self, env, killfunc=None): |
564 | + self._env = env |
565 | self._killfunc = killfunc |
566 | - self._bus = (BUS if bus is None else bus) |
567 | - self._dbname = dbname |
568 | - self._no_gui = no_gui |
569 | + self._bus = env.get('bus', BUS) |
570 | + self._dbname = env.get('dbname', DBNAME) |
571 | + self._no_gui = env.get('no_gui', False) |
572 | log.info('Starting service on %r', self._bus) |
573 | self._conn = dbus.SessionBus() |
574 | super(DMedia, self).__init__(self._conn, object_path='/') |
575 | self._busname = dbus.service.BusName(self._bus, self._conn) |
576 | |
577 | - if no_gui or pynotify is None: |
578 | + if self._no_gui or pynotify is None: |
579 | self._notify = None |
580 | else: |
581 | log.info('Using `pynotify`') |
582 | self._notify = NotifyManager() |
583 | |
584 | - if no_gui or appindicator is None: |
585 | + if self._no_gui or appindicator is None: |
586 | self._indicator = None |
587 | else: |
588 | log.info('Using `appindicator`') |
589 | @@ -118,12 +116,21 @@ |
590 | self._indicator.set_menu(self._menu) |
591 | self._indicator.set_status(appindicator.STATUS_ACTIVE) |
592 | |
593 | + self._metastore = None |
594 | self._manager = None |
595 | |
596 | @property |
597 | + def metastore(self): |
598 | + if self._metastore is None: |
599 | + self._metastore = MetaStore(self._env) |
600 | + return self._metastore |
601 | + |
602 | + @property |
603 | def manager(self): |
604 | if self._manager is None: |
605 | - self._manager = ImportManager(self._on_signal, self._dbname) |
606 | + self._manager = ImportManager( |
607 | + self.metastore.get_env(), self._on_signal |
608 | + ) |
609 | self._manager.start() |
610 | return self._manager |
611 | |
612 | @@ -133,6 +140,8 @@ |
613 | method(*args) |
614 | |
615 | def _on_timer(self): |
616 | + if self._manager is None: |
617 | + return |
618 | text = _('File %d of %d') % self._manager.get_batch_progress() |
619 | self._current_label.set_text(text) |
620 | self._indicator.set_menu(self._menu) |
621 | @@ -143,8 +152,7 @@ |
622 | def _on_futon(self, menuitem): |
623 | log.info('Opening dmedia database in Futon..') |
624 | try: |
625 | - store = MetaStore() |
626 | - uri = store.get_auth_uri() + '/_utils' |
627 | + uri = self.metastore.get_auth_uri() + '/_utils' |
628 | check_call(['/usr/bin/xdg-open', uri]) |
629 | log.info('Opened Futon') |
630 | except Exception: |
631 | @@ -222,10 +230,11 @@ |
632 | """ |
633 | Kill the dmedia service process. |
634 | """ |
635 | - log.info('Killing service') |
636 | + log.info('Killing service...') |
637 | if self._manager is not None: |
638 | self._manager.kill() |
639 | if callable(self._killfunc): |
640 | + log.info('Calling killfunc()') |
641 | self._killfunc() |
642 | |
643 | @dbus.service.method(INTERFACE, in_signature='', out_signature='s') |
644 | @@ -288,4 +297,6 @@ |
645 | """ |
646 | Return list of currently running imports. |
647 | """ |
648 | - return self.manager.list_imports() |
649 | + if self._manager is None: |
650 | + return [] |
651 | + return self.manager.list_jobs() |
652 | |
653 | === modified file 'dmedia/tests/__init__.py' |
654 | --- dmedia/tests/__init__.py 2011-02-07 03:42:37 +0000 |
655 | +++ dmedia/tests/__init__.py 2011-02-21 12:01:51 +0000 |
656 | @@ -24,8 +24,14 @@ |
657 | Unit tests for `dmedia` package. |
658 | """ |
659 | |
660 | +from unittest import TestCase |
661 | from os import path |
662 | |
663 | +from desktopcouch.application.platform import find_port |
664 | +from desktopcouch.application.local_files import get_oauth_tokens |
665 | + |
666 | +import dmedia |
667 | + |
668 | datadir = path.join(path.dirname(path.abspath(__file__)), 'data') |
669 | sample_mov = path.join(datadir, 'MVI_5751.MOV') |
670 | sample_thm = path.join(datadir, 'MVI_5751.THM') |
671 | @@ -33,3 +39,32 @@ |
672 | assert path.isdir(datadir) |
673 | assert path.isfile(sample_mov) |
674 | assert path.isfile(sample_thm) |
675 | + |
676 | + |
677 | +class test_functions(TestCase): |
678 | + |
679 | + def test_get_env(self): |
680 | + # FIXME: Somehow this test is making gnomekeyring and |
681 | + # ~/.config/desktop-couch/desktop-couchdb.ini contain differnt values |
682 | + return |
683 | + f = dmedia.get_env |
684 | + port = find_port() |
685 | + url = 'http://localhost:%d/' % port |
686 | + oauth = get_oauth_tokens() |
687 | + |
688 | + self.assertEqual( |
689 | + f(), |
690 | + {'port': port, 'url': url, 'oauth': oauth, 'dbname': None} |
691 | + ) |
692 | + self.assertEqual( |
693 | + f(dbname=None), |
694 | + {'port': port, 'url': url, 'oauth': oauth, 'dbname': None} |
695 | + ) |
696 | + self.assertEqual( |
697 | + f(dbname='dmedia'), |
698 | + {'port': port, 'url': url, 'oauth': oauth, 'dbname': 'dmedia'} |
699 | + ) |
700 | + self.assertEqual( |
701 | + f(dbname='dmedia_test'), |
702 | + {'port': port, 'url': url, 'oauth': oauth, 'dbname': 'dmedia_test'} |
703 | + ) |
704 | |
705 | === added file 'dmedia/tests/couch.py' |
706 | --- dmedia/tests/couch.py 1970-01-01 00:00:00 +0000 |
707 | +++ dmedia/tests/couch.py 2011-02-21 12:01:51 +0000 |
708 | @@ -0,0 +1,63 @@ |
709 | +# Authors: |
710 | +# Jason Gerard DeRose <jderose@novacut.com> |
711 | +# |
712 | +# dmedia: distributed media library |
713 | +# Copyright (C) 2011 Jason Gerard DeRose <jderose@novacut.com> |
714 | +# |
715 | +# This file is part of `dmedia`. |
716 | +# |
717 | +# `dmedia` is free software: you can redistribute it and/or modify it under the |
718 | +# terms of the GNU Affero General Public License as published by the Free |
719 | +# Software Foundation, either version 3 of the License, or (at your option) any |
720 | +# later version. |
721 | +# |
722 | +# `dmedia` is distributed in the hope that it will be useful, but WITHOUT ANY |
723 | +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
724 | +# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more |
725 | +# details. |
726 | +# |
727 | +# You should have received a copy of the GNU Affero General Public License along |
728 | +# with `dmedia`. If not, see <http://www.gnu.org/licenses/>. |
729 | + |
730 | +""" |
731 | +Base class for CouchDB tests. |
732 | +""" |
733 | + |
734 | +from unittest import TestCase |
735 | + |
736 | +import couchdb |
737 | + |
738 | +from dmedia.abstractcouch import get_env, get_couchdb_server |
739 | +from dmedia.util import random_id |
740 | +from .helpers import TempHome |
741 | + |
742 | + |
743 | +class CouchCase(TestCase): |
744 | + """ |
745 | + Base class for tests that talk to CouchDB. |
746 | + |
747 | + So that a user's production data doesn't get hosed, all tests are run in the |
748 | + ``"dmedia_test"`` database. |
749 | + |
750 | + FIXME: This isn't the best solution, but some changes in desktopcouch in |
751 | + Natty make it difficult for 3rd party apps to use dc-test idioms: |
752 | + |
753 | + https://bugs.launchpad.net/desktopcouch/+bug/694909 |
754 | + """ |
755 | + |
756 | + def setUp(self): |
757 | + self.home = TempHome() |
758 | + self.dbname = 'dmedia_test' |
759 | + self.env = get_env(self.dbname) |
760 | + server = get_couchdb_server(self.env) |
761 | + try: |
762 | + del server[self.dbname] |
763 | + except couchdb.ResourceNotFound: |
764 | + pass |
765 | + self.machine_id = random_id() |
766 | + self.env['machine_id'] = self.machine_id |
767 | + |
768 | + def tearDown(self): |
769 | + self.home = None |
770 | + self.dbname = None |
771 | + self.env = None |
772 | |
773 | === modified file 'dmedia/tests/helpers.py' |
774 | --- dmedia/tests/helpers.py 2011-02-07 03:42:37 +0000 |
775 | +++ dmedia/tests/helpers.py 2011-02-21 12:01:51 +0000 |
776 | @@ -30,8 +30,7 @@ |
777 | import tempfile |
778 | import shutil |
779 | from base64 import b32encode, b32decode, b64encode |
780 | -from desktopcouch.records.server import CouchDatabase |
781 | -from desktopcouch.records.server_base import NoSuchDatabase |
782 | + |
783 | from . import sample_mov, sample_thm |
784 | |
785 | mov_hash = 'ZR765XWSF6S7JQHLUI4GCG5BHGPE252O' |
786 | @@ -173,22 +172,3 @@ |
787 | |
788 | def __call__(self, signal, args): |
789 | self.messages.append((signal, args)) |
790 | - |
791 | - |
792 | -class CouchCase(TestCase): |
793 | - """ |
794 | - Base class for tests that need a desktopcouch testing Context. |
795 | - """ |
796 | - |
797 | - def setUp(self): |
798 | - self.home = TempHome() |
799 | - self.dbname = 'dmedia_test' |
800 | - try: |
801 | - dc = CouchDatabase(self.dbname) |
802 | - del dc._server[self.dbname] |
803 | - except NoSuchDatabase: |
804 | - pass |
805 | - |
806 | - def tearDown(self): |
807 | - self.home = None |
808 | - self.dbname = None |
809 | |
810 | === added file 'dmedia/tests/test_abstractcouch.py' |
811 | --- dmedia/tests/test_abstractcouch.py 1970-01-01 00:00:00 +0000 |
812 | +++ dmedia/tests/test_abstractcouch.py 2011-02-21 12:01:51 +0000 |
813 | @@ -0,0 +1,207 @@ |
814 | +# Authors: |
815 | +# Jason Gerard DeRose <jderose@novacut.com> |
816 | +# |
817 | +# dmedia: distributed media library |
818 | +# Copyright (C) 2010 Jason Gerard DeRose <jderose@novacut.com> |
819 | +# |
820 | +# This file is part of `dmedia`. |
821 | +# |
822 | +# `dmedia` is free software: you can redistribute it and/or modify it under the |
823 | +# terms of the GNU Affero General Public License as published by the Free |
824 | +# Software Foundation, either version 3 of the License, or (at your option) any |
825 | +# later version. |
826 | +# |
827 | +# `dmedia` is distributed in the hope that it will be useful, but WITHOUT ANY |
828 | +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR |
829 | +# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more |
830 | +# details. |
831 | +# |
832 | +# You should have received a copy of the GNU Affero General Public License along |
833 | +# with `dmedia`. If not, see <http://www.gnu.org/licenses/>. |
834 | + |
835 | +""" |
836 | +Unit tests for `dmedia.abstractcouch` module. |
837 | +""" |
838 | + |
839 | +from unittest import TestCase |
840 | + |
841 | +import couchdb |
842 | +from desktopcouch.application.platform import find_port |
843 | +from desktopcouch.application.local_files import get_oauth_tokens |
844 | +from desktopcouch.records.http import OAuthSession |
845 | + |
846 | +from dmedia import abstractcouch |
847 | + |
848 | +from .helpers import raises |
849 | + |
850 | +class test_functions(TestCase): |
851 | + def tearDown(self): |
852 | + if abstractcouch.OAuthSession is None: |
853 | + abstractcouch.OAuthSession = OAuthSession |
854 | + |
855 | + def dc_env(self): |
856 | + """ |
857 | + Create an *env* for desktopcouch. |
858 | + """ |
859 | + port = find_port() |
860 | + return { |
861 | + 'port': port, |
862 | + 'url': 'http://localhost:%d/' % port, |
863 | + 'oauth': get_oauth_tokens(), |
864 | + } |
865 | + |
866 | + def test_get_couchdb_server(self): |
867 | + f = abstractcouch.get_couchdb_server |
868 | + |
869 | + # Test with empty env |
870 | + s = f({}) |
871 | + self.assertTrue(isinstance(s, couchdb.Server)) |
872 | + self.assertEqual(repr(s), "<Server 'http://localhost:5984/'>") |
873 | + |
874 | + # Test with only url |
875 | + s = f({'url': 'http://localhost:5984/'}) |
876 | + self.assertTrue(isinstance(s, couchdb.Server)) |
877 | + self.assertEqual(repr(s), "<Server 'http://localhost:5984/'>") |
878 | + |
879 | + # Test with desktopcouch |
880 | + env = self.dc_env() |
881 | + s = f(env) |
882 | + self.assertTrue(isinstance(s, couchdb.Server)) |
883 | + self.assertEqual( |
884 | + repr(s), |
885 | + "<Server 'http://localhost:%d/'>" % find_port() |
886 | + ) |
887 | + |
888 | + # Test when OAuthSession is not imported, oauth is provided |
889 | + abstractcouch.OAuthSession = None |
890 | + e = raises(ValueError, f, env) |
891 | + self.assertEqual( |
892 | + str(e), |
893 | + "provided env['oauth'] but OAuthSession not available: %r" % (env,) |
894 | + ) |
895 | + |
896 | + # Test when OAuthSession is not imported, oauth not provided |
897 | + s = f({}) |
898 | + self.assertTrue(isinstance(s, couchdb.Server)) |
899 | + self.assertEqual(repr(s), "<Server 'http://localhost:5984/'>") |
900 | + s = f({'url': 'http://localhost:5984/'}) |
901 | + self.assertTrue(isinstance(s, couchdb.Server)) |
902 | + self.assertEqual(repr(s), "<Server 'http://localhost:5984/'>") |
903 | + |
904 | + def test_get_dmedia_db(self): |
905 | + f = abstractcouch.get_dmedia_db |
906 | + |
907 | + # Test when server is not provided |
908 | + env = self.dc_env() |
909 | + |
910 | + assert 'dmedia' not in env |
911 | + d = f(env) |
912 | + self.assertTrue(isinstance(d, couchdb.Database)) |
913 | + self.assertEqual(repr(d), "<Database 'dmedia'>") |
914 | + self.assertEqual(d.info()['db_name'], 'dmedia') |
915 | + |
916 | + env['dbname'] = None |
917 | + d = f(env) |
918 | + self.assertTrue(isinstance(d, couchdb.Database)) |
919 | + self.assertEqual(repr(d), "<Database 'dmedia'>") |
920 | + self.assertEqual(d.info()['db_name'], 'dmedia') |
921 | + |
922 | + env['dbname'] = 'dmedia' |
923 | + d = f(env) |
924 | + self.assertTrue(isinstance(d, couchdb.Database)) |
925 | + self.assertEqual(repr(d), "<Database 'dmedia'>") |
926 | + self.assertEqual(d.info()['db_name'], 'dmedia') |
927 | + |
928 | + env['dbname'] = 'dmedia_test' |
929 | + d = f(env) |
930 | + self.assertTrue(isinstance(d, couchdb.Database)) |
931 | + self.assertEqual(repr(d), "<Database 'dmedia_test'>") |
932 | + self.assertEqual(d.info()['db_name'], 'dmedia_test') |
933 | + |
934 | + |
935 | + # Test when server *is* provided |
936 | + env = self.dc_env() |
937 | + server = abstractcouch.get_couchdb_server(env) |
938 | + |
939 | + assert 'dmedia' not in env |
940 | + d = f(env, server) |
941 | + self.assertTrue(isinstance(d, couchdb.Database)) |
942 | + self.assertEqual(repr(d), "<Database 'dmedia'>") |
943 | + self.assertEqual(d.info()['db_name'], 'dmedia') |
944 | + |
945 | + env['dbname'] = None |
946 | + d = f(env, server) |
947 | + self.assertTrue(isinstance(d, couchdb.Database)) |
948 | + self.assertEqual(repr(d), "<Database 'dmedia'>") |
949 | + self.assertEqual(d.info()['db_name'], 'dmedia') |
950 | + |
951 | + env['dbname', server] = 'dmedia' |
952 | + d = f(env) |
953 | + self.assertTrue(isinstance(d, couchdb.Database)) |
954 | + self.assertEqual(repr(d), "<Database 'dmedia'>") |
955 | + self.assertEqual(d.info()['db_name'], 'dmedia') |
956 | + |
957 | + env['dbname'] = 'dmedia_test' |
958 | + d = f(env, server) |
959 | + self.assertTrue(isinstance(d, couchdb.Database)) |
960 | + self.assertEqual(repr(d), "<Database 'dmedia_test'>") |
961 | + self.assertEqual(d.info()['db_name'], 'dmedia_test') |
962 | + |
963 | + |
964 | + # Test when server=None is explicitly provided |
965 | + env = self.dc_env() |
966 | + |
967 | + assert 'dmedia' not in env |
968 | + d = f(env, server=None) |
969 | + self.assertTrue(isinstance(d, couchdb.Database)) |
970 | + self.assertEqual(repr(d), "<Database 'dmedia'>") |
971 | + self.assertEqual(d.info()['db_name'], 'dmedia') |
972 | + |
973 | + env['dbname'] = None |
974 | + d = f(env, server=None) |
975 | + self.assertTrue(isinstance(d, couchdb.Database)) |
976 | + self.assertEqual(repr(d), "<Database 'dmedia'>") |
977 | + self.assertEqual(d.info()['db_name'], 'dmedia') |
978 | + |
979 | + env['dbname'] = 'dmedia' |
980 | + d = f(env, server=None) |
981 | + self.assertTrue(isinstance(d, couchdb.Database)) |
982 | + self.assertEqual(repr(d), "<Database 'dmedia'>") |
983 | + self.assertEqual(d.info()['db_name'], 'dmedia') |
984 | + |
985 | + env['dbname'] = 'dmedia_test' |
986 | + d = f(env, server=None) |
987 | + self.assertTrue(isinstance(d, couchdb.Database)) |
988 | + self.assertEqual(repr(d), "<Database 'dmedia_test'>") |
989 | + self.assertEqual(d.info()['db_name'], 'dmedia_test') |
990 | + |
991 | + def test_get_env(self): |
992 | + f = abstractcouch.get_env |
993 | + port = find_port() |
994 | + url = 'http://localhost:%d/' % port |
995 | + oauth = get_oauth_tokens() |
996 | + |
997 | + # Test when OAuthSession is available |
998 | + self.assertEqual( |
999 | + f(), |
1000 | + {'port': port, 'url': url, 'oauth': oauth, 'dbname': None} |
1001 | + ) |
1002 | + self.assertEqual( |
1003 | + f(dbname=None), |
1004 | + {'port': port, 'url': url, 'oauth': oauth, 'dbname': None} |
1005 | + ) |
1006 | + self.assertEqual( |
1007 | + f(dbname='dmedia'), |
1008 | + {'port': port, 'url': url, 'oauth': oauth, 'dbname': 'dmedia'} |
1009 | + ) |
1010 | + self.assertEqual( |
1011 | + f(dbname='dmedia_test'), |
1012 | + {'port': port, 'url': url, 'oauth': oauth, 'dbname': 'dmedia_test'} |
1013 | + ) |
1014 | + |
1015 | + # Test when OAuthSession is *not* available |
1016 | + abstractcouch.OAuthSession = None |
1017 | + self.assertEqual(f(), {'dbname': None}) |
1018 | + self.assertEqual(f(dbname=None), {'dbname': None}) |
1019 | + self.assertEqual(f(dbname='dmedia'), {'dbname': 'dmedia'}) |
1020 | + self.assertEqual(f(dbname='dmedia_test'), {'dbname': 'dmedia_test'}) |
1021 | |
1022 | === modified file 'dmedia/tests/test_client.py' |
1023 | --- dmedia/tests/test_client.py 2011-01-30 21:17:48 +0000 |
1024 | +++ dmedia/tests/test_client.py 2011-02-21 12:01:51 +0000 |
1025 | @@ -27,15 +27,18 @@ |
1026 | from os import path |
1027 | from subprocess import Popen |
1028 | import time |
1029 | + |
1030 | import dbus |
1031 | from dbus.proxies import ProxyObject |
1032 | import gobject |
1033 | + |
1034 | import dmedia |
1035 | from dmedia import client, service |
1036 | from dmedia.constants import VIDEO, AUDIO, IMAGE, EXTENSIONS |
1037 | -from .helpers import CouchCase, TempDir, random_bus, prep_import_source |
1038 | +from .helpers import TempDir, random_bus, prep_import_source |
1039 | from .helpers import sample_mov, sample_thm |
1040 | from .helpers import mov_hash, thm_hash |
1041 | +from .couch import CouchCase |
1042 | |
1043 | |
1044 | tree = path.dirname(path.dirname(path.abspath(dmedia.__file__))) |
1045 | |
1046 | === modified file 'dmedia/tests/test_importer.py' |
1047 | --- dmedia/tests/test_importer.py 2011-02-18 21:15:03 +0000 |
1048 | +++ dmedia/tests/test_importer.py 2011-02-21 12:01:51 +0000 |
1049 | @@ -24,6 +24,7 @@ |
1050 | Unit tests for `dmedia.importer` module. |
1051 | """ |
1052 | |
1053 | +from unittest import TestCase |
1054 | import os |
1055 | from os import path |
1056 | import hashlib |
1057 | @@ -31,21 +32,22 @@ |
1058 | import shutil |
1059 | import time |
1060 | from base64 import b32decode, b32encode, b64encode |
1061 | -from unittest import TestCase |
1062 | from multiprocessing import current_process |
1063 | -from .helpers import CouchCase, TempDir, TempHome, raises |
1064 | + |
1065 | +import couchdb |
1066 | + |
1067 | +from dmedia.errors import AmbiguousPath |
1068 | +from dmedia.filestore import FileStore |
1069 | +from dmedia.util import random_id |
1070 | +from dmedia import importer, schema |
1071 | +from dmedia.abstractcouch import get_env, get_dmedia_db |
1072 | +from .helpers import TempDir, TempHome, raises |
1073 | from .helpers import DummyQueue, DummyCallback, prep_import_source |
1074 | from .helpers import sample_mov, sample_thm |
1075 | from .helpers import mov_hash, mov_leaves, mov_att, mov_qid |
1076 | from .helpers import thm_hash, thm_leaves, thm_qid |
1077 | -from dmedia.errors import AmbiguousPath |
1078 | -from dmedia.filestore import FileStore |
1079 | -from dmedia.metastore import MetaStore |
1080 | -from dmedia.util import random_id |
1081 | -from dmedia import importer, schema |
1082 | +from .couch import CouchCase |
1083 | |
1084 | -import desktopcouch |
1085 | -from desktopcouch.stop_local_couchdb import stop_couchdb |
1086 | |
1087 | |
1088 | letters = 'gihdwaqoebxtcklrnsmjufyvpz' |
1089 | @@ -267,36 +269,138 @@ |
1090 | ) |
1091 | |
1092 | |
1093 | -class test_Importer(CouchCase): |
1094 | - klass = importer.Importer |
1095 | - batch_id = 'YKGHY6H5RVCDNMUBL4NLP6AU' |
1096 | - |
1097 | - def new(self, base, extract=False): |
1098 | - return self.klass(self.batch_id, base, extract, dbname=self.dbname) |
1099 | +class test_ImportWorker(CouchCase): |
1100 | + klass = importer.ImportWorker |
1101 | + |
1102 | + def setUp(self): |
1103 | + super(test_ImportWorker, self).setUp() |
1104 | + self.batch_id = random_id() |
1105 | + self.env['batch_id'] = self.batch_id |
1106 | + self.q = DummyQueue() |
1107 | + self.pid = current_process().pid |
1108 | + self.tmp = TempDir() |
1109 | + |
1110 | + def tearDown(self): |
1111 | + super(test_ImportWorker, self).tearDown() |
1112 | + self.q = None |
1113 | + self.pid = None |
1114 | + self.tmp = None |
1115 | + |
1116 | + def new(self, base=None, extract=False): |
1117 | + base = (self.tmp.path if base is None else base) |
1118 | + return self.klass(self.env, self.q, base, (base, extract)) |
1119 | |
1120 | def test_init(self): |
1121 | tmp = TempDir() |
1122 | inst = self.new(tmp.path, True) |
1123 | - self.assertEqual(inst.batch_id, self.batch_id) |
1124 | + self.assertEqual(inst.env, self.env) |
1125 | self.assertEqual(inst.base, tmp.path) |
1126 | self.assertTrue(inst.extract is True) |
1127 | + |
1128 | + self.assertTrue(isinstance(inst.server, couchdb.Server)) |
1129 | + self.assertTrue(isinstance(inst.db, couchdb.Database)) |
1130 | + |
1131 | self.assertEqual(inst.home, self.home.path) |
1132 | self.assertTrue(isinstance(inst.filestore, FileStore)) |
1133 | self.assertEqual(inst.filestore.base, self.home.join('.dmedia')) |
1134 | - self.assertTrue(isinstance(inst.metastore, MetaStore)) |
1135 | |
1136 | # Test with extract = False |
1137 | inst = self.new(tmp.path, False) |
1138 | self.assertTrue(inst.extract is False) |
1139 | |
1140 | + def test_run(self): |
1141 | + q = DummyQueue() |
1142 | + pid = current_process().pid |
1143 | + tmp = TempDir() |
1144 | + base = tmp.path |
1145 | + inst = self.klass(self.env, q, base, (base, False)) |
1146 | + |
1147 | + src1 = tmp.copy(sample_mov, 'DCIM', '100EOS5D2', 'MVI_5751.MOV') |
1148 | + dup1 = tmp.copy(sample_mov, 'DCIM', '100EOS5D2', 'MVI_5752.MOV') |
1149 | + src2 = tmp.copy(sample_thm, 'DCIM', '100EOS5D2', 'MVI_5751.THM') |
1150 | + |
1151 | + mov_size = path.getsize(sample_mov) |
1152 | + thm_size = path.getsize(sample_thm) |
1153 | + |
1154 | + inst.run() |
1155 | + |
1156 | + self.assertEqual(len(q.messages), 6) |
1157 | + _id = q.messages[0]['args'][1] |
1158 | + self.assertEqual(len(_id), 24) |
1159 | + self.assertEqual( |
1160 | + q.messages[0], |
1161 | + dict( |
1162 | + signal='started', |
1163 | + args=(base, _id), |
1164 | + worker='ImportWorker', |
1165 | + pid=pid, |
1166 | + ) |
1167 | + ) |
1168 | + self.assertEqual( |
1169 | + q.messages[1], |
1170 | + dict( |
1171 | + signal='count', |
1172 | + args=(base, _id, 3), |
1173 | + worker='ImportWorker', |
1174 | + pid=pid, |
1175 | + ) |
1176 | + ) |
1177 | + self.assertEqual(q.messages[2], |
1178 | + dict( |
1179 | + signal='progress', |
1180 | + args=(base, _id, 1, 3, |
1181 | + dict(action='imported', src=src1) |
1182 | + ), |
1183 | + worker='ImportWorker', |
1184 | + pid=pid, |
1185 | + ) |
1186 | + ) |
1187 | + self.assertEqual(q.messages[3], |
1188 | + dict( |
1189 | + signal='progress', |
1190 | + args=(base, _id, 2, 3, |
1191 | + dict(action='imported', src=src2) |
1192 | + ), |
1193 | + worker='ImportWorker', |
1194 | + pid=pid, |
1195 | + ) |
1196 | + ) |
1197 | + self.assertEqual(q.messages[4], |
1198 | + dict( |
1199 | + signal='progress', |
1200 | + args=(base, _id, 3, 3, |
1201 | + dict(action='skipped', src=dup1) |
1202 | + ), |
1203 | + worker='ImportWorker', |
1204 | + pid=pid, |
1205 | + ) |
1206 | + ) |
1207 | + self.assertEqual( |
1208 | + q.messages[5], |
1209 | + dict( |
1210 | + signal='finished', |
1211 | + args=(base, _id, |
1212 | + dict( |
1213 | + considered={'count': 3, 'bytes': (mov_size*2 + thm_size)}, |
1214 | + imported={'count': 2, 'bytes': (mov_size + thm_size)}, |
1215 | + skipped={'count': 1, 'bytes': mov_size}, |
1216 | + empty={'count': 0, 'bytes': 0}, |
1217 | + error={'count': 0, 'bytes': 0}, |
1218 | + ), |
1219 | + ), |
1220 | + worker='ImportWorker', |
1221 | + pid=pid, |
1222 | + ) |
1223 | + ) |
1224 | + |
1225 | def test_start(self): |
1226 | tmp = TempDir() |
1227 | inst = self.new(tmp.path) |
1228 | self.assertTrue(inst.doc is None) |
1229 | _id = inst.start() |
1230 | self.assertEqual(len(_id), 24) |
1231 | - store = MetaStore(dbname=self.dbname) |
1232 | - self.assertEqual(inst.doc, store.db[_id]) |
1233 | + db = get_dmedia_db(self.env) |
1234 | + self.assertEqual(inst.doc, db[_id]) |
1235 | self.assertEqual( |
1236 | set(inst.doc), |
1237 | set([ |
1238 | @@ -312,10 +416,7 @@ |
1239 | ]) |
1240 | ) |
1241 | self.assertEqual(inst.doc['batch_id'], self.batch_id) |
1242 | - self.assertEqual( |
1243 | - inst.doc['machine_id'], |
1244 | - inst.metastore.machine_id |
1245 | - ) |
1246 | + self.assertEqual(inst.doc['machine_id'], self.machine_id) |
1247 | self.assertEqual(inst.doc['base'], tmp.path) |
1248 | self.assertEqual( |
1249 | inst.doc['log'], |
1250 | @@ -706,103 +807,16 @@ |
1251 | ) |
1252 | |
1253 | |
1254 | -class test_ImportWorker(CouchCase): |
1255 | - klass = importer.ImportWorker |
1256 | - |
1257 | - def test_run(self): |
1258 | - q = DummyQueue() |
1259 | - pid = current_process().pid |
1260 | - |
1261 | - tmp = TempDir() |
1262 | - batch_id = 'YKGHY6H5RVCDNMUBL4NLP6AU' |
1263 | - base = tmp.path |
1264 | - inst = self.klass(q, base, (batch_id, base, False, self.dbname)) |
1265 | - |
1266 | - src1 = tmp.copy(sample_mov, 'DCIM', '100EOS5D2', 'MVI_5751.MOV') |
1267 | - dup1 = tmp.copy(sample_mov, 'DCIM', '100EOS5D2', 'MVI_5752.MOV') |
1268 | - src2 = tmp.copy(sample_thm, 'DCIM', '100EOS5D2', 'MVI_5751.THM') |
1269 | - |
1270 | - mov_size = path.getsize(sample_mov) |
1271 | - thm_size = path.getsize(sample_thm) |
1272 | - |
1273 | - inst.run() |
1274 | - |
1275 | - self.assertEqual(len(q.messages), 6) |
1276 | - _id = q.messages[0]['args'][1] |
1277 | - self.assertEqual(len(_id), 24) |
1278 | - self.assertEqual( |
1279 | - q.messages[0], |
1280 | - dict( |
1281 | - signal='started', |
1282 | - args=(base, _id), |
1283 | - worker='ImportWorker', |
1284 | - pid=pid, |
1285 | - ) |
1286 | - ) |
1287 | - self.assertEqual( |
1288 | - q.messages[1], |
1289 | - dict( |
1290 | - signal='count', |
1291 | - args=(base, _id, 3), |
1292 | - worker='ImportWorker', |
1293 | - pid=pid, |
1294 | - ) |
1295 | - ) |
1296 | - self.assertEqual(q.messages[2], |
1297 | - dict( |
1298 | - signal='progress', |
1299 | - args=(base, _id, 1, 3, |
1300 | - dict(action='imported', src=src1) |
1301 | - ), |
1302 | - worker='ImportWorker', |
1303 | - pid=pid, |
1304 | - ) |
1305 | - ) |
1306 | - self.assertEqual(q.messages[3], |
1307 | - dict( |
1308 | - signal='progress', |
1309 | - args=(base, _id, 2, 3, |
1310 | - dict(action='imported', src=src2) |
1311 | - ), |
1312 | - worker='ImportWorker', |
1313 | - pid=pid, |
1314 | - ) |
1315 | - ) |
1316 | - self.assertEqual(q.messages[4], |
1317 | - dict( |
1318 | - signal='progress', |
1319 | - args=(base, _id, 3, 3, |
1320 | - dict(action='skipped', src=dup1) |
1321 | - ), |
1322 | - worker='ImportWorker', |
1323 | - pid=pid, |
1324 | - ) |
1325 | - ) |
1326 | - self.assertEqual( |
1327 | - q.messages[5], |
1328 | - dict( |
1329 | - signal='finished', |
1330 | - args=(base, _id, |
1331 | - dict( |
1332 | - considered={'count': 3, 'bytes': (mov_size*2 + thm_size)}, |
1333 | - imported={'count': 2, 'bytes': (mov_size + thm_size)}, |
1334 | - skipped={'count': 1, 'bytes': mov_size}, |
1335 | - empty={'count': 0, 'bytes': 0}, |
1336 | - error={'count': 0, 'bytes': 0}, |
1337 | - ), |
1338 | - ), |
1339 | - worker='ImportWorker', |
1340 | - pid=pid, |
1341 | - ) |
1342 | - ) |
1343 | - |
1344 | - |
1345 | class test_ImportManager(CouchCase): |
1346 | klass = importer.ImportManager |
1347 | |
1348 | + def new(self, callback=None): |
1349 | + return self.klass(self.env, callback) |
1350 | + |
1351 | + |
1352 | def test_start_batch(self): |
1353 | callback = DummyCallback() |
1354 | - inst = self.klass(callback, self.dbname) |
1355 | + inst = self.new(callback) |
1356 | |
1357 | # Test that batch cannot be started when there are active workers: |
1358 | inst._workers['foo'] = 'bar' |
1359 | @@ -816,7 +830,6 @@ |
1360 | self.assertEqual(inst._completed, 0) |
1361 | self.assertEqual(inst._total, 0) |
1362 | batch = inst.doc |
1363 | - batch_id = batch['_id'] |
1364 | self.assertTrue(isinstance(batch, dict)) |
1365 | self.assertEqual( |
1366 | set(batch), |
1367 | @@ -832,12 +845,12 @@ |
1368 | ) |
1369 | self.assertEqual(batch['type'], 'dmedia/batch') |
1370 | self.assertEqual(batch['imports'], []) |
1371 | - self.assertEqual(batch['machine_id'], inst.metastore.machine_id) |
1372 | + self.assertEqual(batch['machine_id'], self.machine_id) |
1373 | self.assertEqual(inst.db[batch['_id']], batch) |
1374 | self.assertEqual( |
1375 | callback.messages, |
1376 | [ |
1377 | - ('BatchStarted', (batch_id,)), |
1378 | + ('BatchStarted', (batch['_id'],)), |
1379 | ] |
1380 | ) |
1381 | |
1382 | @@ -846,7 +859,7 @@ |
1383 | |
1384 | def test_finish_batch(self): |
1385 | callback = DummyCallback() |
1386 | - inst = self.klass(callback, self.dbname) |
1387 | + inst = self.new(callback) |
1388 | batch_id = random_id() |
1389 | inst.doc = dict( |
1390 | _id=batch_id, |
1391 | @@ -892,7 +905,7 @@ |
1392 | |
1393 | def test_on_error(self): |
1394 | callback = DummyCallback() |
1395 | - inst = self.klass(callback, self.dbname) |
1396 | + inst = self.new(callback) |
1397 | |
1398 | # Make sure it works when doc is None: |
1399 | inst.on_error('foo', 'IOError', 'nope') |
1400 | @@ -919,9 +932,21 @@ |
1401 | ] |
1402 | ) |
1403 | |
1404 | + def test_get_worker_env(self): |
1405 | + batch_id = random_id() |
1406 | + inst = self.new() |
1407 | + env = dict(self.env) |
1408 | + assert 'batch_id' not in env |
1409 | + inst.doc = {'_id': batch_id} |
1410 | + env['batch_id'] = batch_id |
1411 | + self.assertEqual( |
1412 | + inst.get_worker_env('ImportWorker', 'a key', ('some', 'args')), |
1413 | + env |
1414 | + ) |
1415 | + |
1416 | def test_on_started(self): |
1417 | callback = DummyCallback() |
1418 | - inst = self.klass(callback, self.dbname) |
1419 | + inst = self.new(callback) |
1420 | self.assertEqual(callback.messages, []) |
1421 | inst._start_batch() |
1422 | batch_id = inst.doc['_id'] |
1423 | @@ -960,7 +985,7 @@ |
1424 | |
1425 | def test_on_count(self): |
1426 | callback = DummyCallback() |
1427 | - inst = self.klass(callback, self.dbname) |
1428 | + inst = self.new(callback) |
1429 | self.assertEqual(callback.messages, []) |
1430 | |
1431 | one = TempDir() |
1432 | @@ -990,7 +1015,7 @@ |
1433 | |
1434 | def test_on_progress(self): |
1435 | callback = DummyCallback() |
1436 | - inst = self.klass(callback, self.dbname) |
1437 | + inst = self.new(callback) |
1438 | self.assertEqual(callback.messages, []) |
1439 | |
1440 | one = TempDir() |
1441 | @@ -1030,7 +1055,7 @@ |
1442 | |
1443 | def test_on_finished(self): |
1444 | callback = DummyCallback() |
1445 | - inst = self.klass(callback, self.dbname) |
1446 | + inst = self.new(callback) |
1447 | batch_id = random_id() |
1448 | inst.doc = dict( |
1449 | _id=batch_id, |
1450 | @@ -1116,7 +1141,7 @@ |
1451 | ) |
1452 | |
1453 | def test_get_batch_progress(self): |
1454 | - inst = self.klass(dbname=self.dbname) |
1455 | + inst = self.new() |
1456 | self.assertEqual(inst.get_batch_progress(), (0, 0)) |
1457 | inst._total = 18 |
1458 | self.assertEqual(inst.get_batch_progress(), (0, 18)) |
1459 | @@ -1128,7 +1153,7 @@ |
1460 | |
1461 | def test_start_import(self): |
1462 | callback = DummyCallback() |
1463 | - inst = self.klass(callback, self.dbname) |
1464 | + inst = self.new(callback) |
1465 | self.assertTrue(inst.start()) |
1466 | |
1467 | tmp = TempDir() |
1468 | @@ -1208,13 +1233,3 @@ |
1469 | ) |
1470 | ) |
1471 | ) |
1472 | - |
1473 | - def test_list_imports(self): |
1474 | - inst = self.klass(dbname=self.dbname) |
1475 | - self.assertEqual(inst.list_imports(), []) |
1476 | - inst._workers.update( |
1477 | - dict(foo=None, bar=None, baz=None) |
1478 | - ) |
1479 | - self.assertEqual(inst.list_imports(), ['bar', 'baz', 'foo']) |
1480 | - inst._workers.clear() |
1481 | - self.assertEqual(inst.list_imports(), []) |
1482 | |
1483 | === modified file 'dmedia/tests/test_metastore.py' |
1484 | --- dmedia/tests/test_metastore.py 2011-02-18 19:22:23 +0000 |
1485 | +++ dmedia/tests/test_metastore.py 2011-02-21 12:01:51 +0000 |
1486 | @@ -24,19 +24,16 @@ |
1487 | """ |
1488 | |
1489 | from unittest import TestCase |
1490 | +import os |
1491 | +import shutil |
1492 | import socket |
1493 | import platform |
1494 | -from helpers import CouchCase, TempDir, TempHome |
1495 | + |
1496 | +import couchdb |
1497 | + |
1498 | from dmedia import metastore |
1499 | -import couchdb |
1500 | -from desktopcouch.records.server import CouchDatabase |
1501 | -from desktopcouch.records.record import Record |
1502 | -from desktopcouch.local_files import Context |
1503 | -from desktopcouch.stop_local_couchdb import stop_couchdb |
1504 | -import desktopcouch |
1505 | -import tempfile |
1506 | -import os |
1507 | -import shutil |
1508 | +from .helpers import TempDir, TempHome |
1509 | +from .couch import CouchCase |
1510 | |
1511 | |
1512 | class test_functions(TestCase): |
1513 | @@ -90,13 +87,13 @@ |
1514 | klass = metastore.MetaStore |
1515 | |
1516 | def new(self): |
1517 | - return self.klass(self.dbname) |
1518 | + return self.klass(self.env) |
1519 | |
1520 | def test_init(self): |
1521 | inst = self.new() |
1522 | - self.assertEqual(inst.dbname, self.dbname) |
1523 | - self.assertEqual(isinstance(inst.desktop, CouchDatabase), True) |
1524 | - self.assertEqual(isinstance(inst.server, couchdb.Server), True) |
1525 | + self.assertEqual(inst.env, self.env) |
1526 | + self.assertTrue(isinstance(inst.server, couchdb.Server)) |
1527 | + self.assertTrue(isinstance(inst.db, couchdb.Database)) |
1528 | |
1529 | def update(self): |
1530 | inst = self.new() |
1531 | |
1532 | === modified file 'dmedia/tests/test_service.py' |
1533 | --- dmedia/tests/test_service.py 2011-01-30 21:17:48 +0000 |
1534 | +++ dmedia/tests/test_service.py 2011-02-21 12:01:51 +0000 |
1535 | @@ -24,8 +24,10 @@ |
1536 | """ |
1537 | |
1538 | from unittest import TestCase |
1539 | -from helpers import CouchCase, TempDir, random_bus |
1540 | + |
1541 | from dmedia import service, importer |
1542 | +from .helpers import TempDir, random_bus |
1543 | +from .couch import CouchCase |
1544 | |
1545 | |
1546 | class test_DMedia(CouchCase): |
1547 | @@ -33,11 +35,11 @@ |
1548 | |
1549 | def test_init(self): |
1550 | bus = random_bus() |
1551 | + self.env['bus'] = bus |
1552 | + self.env['no_gui'] = True |
1553 | def kill(): |
1554 | pass |
1555 | - inst = self.klass( |
1556 | - killfunc=kill, bus=bus, dbname=self.dbname, no_gui=True |
1557 | - ) |
1558 | + inst = self.klass(self.env, killfunc=kill) |
1559 | self.assertTrue(inst._killfunc is kill) |
1560 | self.assertTrue(inst._bus is bus) |
1561 | self.assertTrue(inst._dbname is self.dbname) |
1562 | @@ -48,4 +50,3 @@ |
1563 | self.assertTrue(inst._manager is m) |
1564 | self.assertTrue(isinstance(m, importer.ImportManager)) |
1565 | self.assertEqual(m._callback, inst._on_signal) |
1566 | - self.assertTrue(m._dbname is self.dbname) |
1567 | |
1568 | === modified file 'dmedia/tests/test_workers.py' |
1569 | --- dmedia/tests/test_workers.py 2010-12-29 13:58:00 +0000 |
1570 | +++ dmedia/tests/test_workers.py 2011-02-21 12:01:51 +0000 |
1571 | @@ -30,8 +30,12 @@ |
1572 | import multiprocessing.queues |
1573 | from multiprocessing import current_process |
1574 | import threading |
1575 | + |
1576 | +import couchdb |
1577 | + |
1578 | from dmedia import workers |
1579 | from .helpers import raises, DummyQueue, DummyCallback |
1580 | +from .couch import CouchCase |
1581 | |
1582 | |
1583 | class test_functions(TestCase): |
1584 | @@ -109,7 +113,8 @@ |
1585 | |
1586 | # Test with unknown worker name |
1587 | q = DummyQueue() |
1588 | - f(q, 'ImportFiles', 'the key', ('foo', 'bar')) |
1589 | + env = {'foo': 'bar'} |
1590 | + f('ImportFiles', env, q, 'the key', ('foo', 'bar')) |
1591 | |
1592 | self.assertEqual( |
1593 | q.messages, |
1594 | @@ -136,7 +141,8 @@ |
1595 | workers.register(ImportFiles) |
1596 | |
1597 | q = DummyQueue() |
1598 | - f(q, 'ImportFiles', 'the key', ('hello', 'world')) |
1599 | + env = {'foo': 'bar'} |
1600 | + f('ImportFiles', env, q, 'the key', ('hello', 'world')) |
1601 | self.assertEqual( |
1602 | q.messages, |
1603 | [ |
1604 | @@ -160,10 +166,12 @@ |
1605 | klass = workers.Worker |
1606 | |
1607 | def test_init(self): |
1608 | + env = {'foo': 'bar'} |
1609 | q = DummyQueue() |
1610 | key = 'the key' |
1611 | args = ('foo', 'bar') |
1612 | - inst = self.klass(q, key, args) |
1613 | + inst = self.klass(env, q, key, args) |
1614 | + self.assertTrue(inst.env is env) |
1615 | self.assertTrue(inst.q is q) |
1616 | self.assertTrue(inst.key is key) |
1617 | self.assertTrue(inst.args is args) |
1618 | @@ -171,9 +179,10 @@ |
1619 | self.assertEqual(inst.name, 'Worker') |
1620 | |
1621 | def test_emit(self): |
1622 | + env = {'foo': 'bar'} |
1623 | q = DummyQueue() |
1624 | args = ('foo', 'bar') |
1625 | - inst = self.klass(q, 'akey', args) |
1626 | + inst = self.klass(env, q, 'akey', args) |
1627 | pid = current_process().pid |
1628 | |
1629 | self.assertEqual(q.messages, []) |
1630 | @@ -206,6 +215,7 @@ |
1631 | self.assertEqual(q.messages, [one, two, three]) |
1632 | |
1633 | def test_run(self): |
1634 | + env = {'foo': 'bar'} |
1635 | q = DummyQueue() |
1636 | args = ('foo', 'bar') |
1637 | pid = current_process().pid |
1638 | @@ -214,7 +224,7 @@ |
1639 | def execute(self, one, two): |
1640 | self.emit('Hello', '%s and %s' % (one, two)) |
1641 | |
1642 | - inst = do_something(q, 'key', args) |
1643 | + inst = do_something(env, q, 'key', args) |
1644 | inst.run() |
1645 | self.assertEqual(q.messages[0], |
1646 | dict( |
1647 | @@ -226,20 +236,40 @@ |
1648 | ) |
1649 | |
1650 | def test_execute(self): |
1651 | + env = {'foo': 'bar'} |
1652 | q = DummyQueue() |
1653 | args = ('foo', 'bar') |
1654 | - inst = self.klass(q, 'key', args) |
1655 | + inst = self.klass(env, q, 'key', args) |
1656 | |
1657 | e = raises(NotImplementedError, inst.execute) |
1658 | self.assertEqual(str(e), 'Worker.execute()') |
1659 | |
1660 | class do_something(self.klass): |
1661 | pass |
1662 | - inst = do_something(q, 'key', args) |
1663 | + inst = do_something(env, q, 'key', args) |
1664 | e = raises(NotImplementedError, inst.execute) |
1665 | self.assertEqual(str(e), 'do_something.execute()') |
1666 | |
1667 | |
1668 | +class test_CouchWorker(CouchCase): |
1669 | + klass = workers.CouchWorker |
1670 | + |
1671 | + def test_init(self): |
1672 | + q = DummyQueue() |
1673 | + key = 'a key' |
1674 | + args = ('some', 'args') |
1675 | + inst = self.klass(self.env, q, key, args) |
1676 | + self.assertTrue(inst.env is self.env) |
1677 | + self.assertTrue(inst.q is q) |
1678 | + self.assertTrue(inst.key is key) |
1679 | + self.assertTrue(inst.args is args) |
1680 | + self.assertTrue(isinstance(inst.server, couchdb.Server)) |
1681 | + self.assertTrue(isinstance(inst.db, couchdb.Database)) |
1682 | + |
1683 | + |
1684 | + |
1685 | + |
1686 | + |
1687 | def infinite(): |
1688 | while True: |
1689 | time.sleep(1) |
1690 | @@ -254,8 +284,8 @@ |
1691 | |
1692 | |
1693 | class ExampleWorker(workers.Worker): |
1694 | - def execute(self, run_infinitely=True): |
1695 | - if run_infinitely: |
1696 | + def execute(self, *args): |
1697 | + if self.env.get('infinite', True): |
1698 | infinite() |
1699 | else: |
1700 | time.sleep(1) |
1701 | @@ -269,18 +299,21 @@ |
1702 | workers.register(ExampleWorker) |
1703 | |
1704 | def test_init(self): |
1705 | + env = {'foo': 'bar'} |
1706 | # Test with non-callable callback: |
1707 | - e = raises(TypeError, self.klass, 'foo') |
1708 | + e = raises(TypeError, self.klass, env, 'foo') |
1709 | self.assertEqual(str(e), "callback must be callable; got 'foo'") |
1710 | |
1711 | # Test that callback default is None: |
1712 | - inst = self.klass() |
1713 | + inst = self.klass(env) |
1714 | + self.assertTrue(inst.env is env) |
1715 | self.assertTrue(inst._callback is None) |
1716 | |
1717 | # Test with a callable: |
1718 | def foo(): |
1719 | pass |
1720 | - inst = self.klass(callback=foo) |
1721 | + inst = self.klass(env, callback=foo) |
1722 | + self.assertTrue(inst.env is env) |
1723 | self.assertTrue(inst._callback is foo) |
1724 | self.assertTrue(inst._running is False) |
1725 | self.assertEqual(inst._workers, {}) |
1726 | @@ -294,7 +327,8 @@ |
1727 | assert self._call is None |
1728 | self._call = arg1 + arg2 |
1729 | |
1730 | - inst = Example() |
1731 | + env = {'foo': 'bar'} |
1732 | + inst = Example(env = {'foo': 'bar'}) |
1733 | msg = dict(signal='stuff', args=('foo', 'bar')) |
1734 | inst._process_message(msg) |
1735 | self.assertEqual(inst._call, 'foobar') |
1736 | @@ -304,7 +338,8 @@ |
1737 | self.assertEqual(str(e), "'Example' object has no attribute 'on_nope'") |
1738 | |
1739 | def test_on_terminate(self): |
1740 | - inst = self.klass() |
1741 | + env = {'foo': 'bar'} |
1742 | + inst = self.klass(env) |
1743 | e = raises(KeyError, inst.on_terminate, 'foo') |
1744 | p = multiprocessing.Process(target=time.sleep, args=(1,)) |
1745 | p.daemon = True |
1746 | @@ -316,7 +351,8 @@ |
1747 | self.assertEqual(inst._workers, {}) |
1748 | |
1749 | def test_start(self): |
1750 | - inst = self.klass() |
1751 | + env = {'foo': 'bar'} |
1752 | + inst = self.klass(env) |
1753 | |
1754 | # Test that start() returns False when already running: |
1755 | inst._running = True |
1756 | @@ -336,7 +372,8 @@ |
1757 | inst._thread.join() |
1758 | |
1759 | def test_kill(self): |
1760 | - inst = self.klass() |
1761 | + env = {'foo': 'bar'} |
1762 | + inst = self.klass(env) |
1763 | |
1764 | # Test that kill() returns False when not running: |
1765 | self.assertTrue(inst.kill() is False) |
1766 | @@ -357,7 +394,8 @@ |
1767 | self.assertEqual(inst._workers, {}) |
1768 | |
1769 | def test_kill_job(self): |
1770 | - inst = self.klass() |
1771 | + env = {'foo': 'bar'} |
1772 | + inst = self.klass(env) |
1773 | |
1774 | # Test that kill_job() returns False when no such job exists: |
1775 | self.assertTrue(inst.kill_job('foo') is False) |
1776 | @@ -372,31 +410,55 @@ |
1777 | # Again test that kill_job() returns False when no such job exists: |
1778 | self.assertTrue(inst.kill_job('foo') is False) |
1779 | |
1780 | - def test_do(self): |
1781 | - inst = self.klass() |
1782 | + def test_start_job(self): |
1783 | + env = {'foo': 'bar'} |
1784 | + inst = self.klass(env) |
1785 | |
1786 | # Test that False is returned when key already exists: |
1787 | inst._workers['foo'] = 'bar' |
1788 | - self.assertTrue(inst.do('ExampleWorker', 'foo') is False) |
1789 | + self.assertTrue(inst.start_job('ExampleWorker', 'foo') is False) |
1790 | |
1791 | - # Test creating a process |
1792 | + # Test creating a process with no args |
1793 | inst._workers.clear() |
1794 | - self.assertTrue(inst.do('ExampleWorker', 'foo', ) is True) |
1795 | + self.assertTrue(inst.start_job('ExampleWorker', 'foo') is True) |
1796 | self.assertEqual(list(inst._workers), ['foo']) |
1797 | p = inst._workers['foo'] |
1798 | self.assertTrue(isinstance(p, multiprocessing.Process)) |
1799 | self.assertTrue(p.daemon) |
1800 | self.assertTrue(p.is_alive()) |
1801 | + self.assertEqual( |
1802 | + p._args, |
1803 | + ('ExampleWorker', inst.env, inst._q, 'foo', tuple()) |
1804 | + ) |
1805 | + self.assertEqual(p._kwargs, {}) |
1806 | + p.terminate() |
1807 | + p.join() |
1808 | + |
1809 | + # Test creating a process *with* args |
1810 | + self.assertTrue( |
1811 | + inst.start_job('ExampleWorker', 'bar', 'some', 'args') is True |
1812 | + ) |
1813 | + self.assertEqual(sorted(inst._workers), ['bar', 'foo']) |
1814 | + p = inst._workers['bar'] |
1815 | + self.assertTrue(isinstance(p, multiprocessing.Process)) |
1816 | + self.assertTrue(p.daemon) |
1817 | + self.assertTrue(p.is_alive()) |
1818 | + self.assertEqual( |
1819 | + p._args, |
1820 | + ('ExampleWorker', inst.env, inst._q, 'bar', ('some', 'args')) |
1821 | + ) |
1822 | + self.assertEqual(p._kwargs, {}) |
1823 | p.terminate() |
1824 | p.join() |
1825 | |
1826 | def test_emit(self): |
1827 | + env = {'foo': 'bar'} |
1828 | # Test with no callback |
1829 | - inst = self.klass() |
1830 | + inst = self.klass(env) |
1831 | inst.emit('ImportStarted', 'foo', 'bar') |
1832 | |
1833 | callback = DummyCallback() |
1834 | - inst = self.klass(callback) |
1835 | + inst = self.klass(env, callback) |
1836 | inst.emit('ImportStarted', 'foo', 'bar') |
1837 | inst.emit('NoArgs') |
1838 | inst.emit('OneArg', 'baz') |
1839 | @@ -408,3 +470,33 @@ |
1840 | ('OneArg', ('baz',)), |
1841 | ] |
1842 | ) |
1843 | + |
1844 | + def test_list_jobs(self): |
1845 | + env = {'foo': 'bar'} |
1846 | + inst = self.klass(env) |
1847 | + self.assertEqual(inst.list_jobs(), []) |
1848 | + inst._workers.update( |
1849 | + dict(foo=None, bar=None, baz=None) |
1850 | + ) |
1851 | + self.assertEqual(inst.list_jobs(), ['bar', 'baz', 'foo']) |
1852 | + inst._workers.clear() |
1853 | + self.assertEqual(inst.list_jobs(), []) |
1854 | + |
1855 | + |
1856 | +class test_CouchManager(CouchCase): |
1857 | + klass = workers.CouchManager |
1858 | + |
1859 | + def test_init(self): |
1860 | + inst = self.klass(self.env) |
1861 | + self.assertTrue(inst.env is self.env) |
1862 | + self.assertTrue(inst._callback is None) |
1863 | + self.assertTrue(isinstance(inst.server, couchdb.Server)) |
1864 | + self.assertTrue(isinstance(inst.db, couchdb.Database)) |
1865 | + |
1866 | + def func(): |
1867 | + pass |
1868 | + inst = self.klass(self.env, func) |
1869 | + self.assertTrue(inst.env is self.env) |
1870 | + self.assertTrue(inst._callback, func) |
1871 | + self.assertTrue(isinstance(inst.server, couchdb.Server)) |
1872 | + self.assertTrue(isinstance(inst.db, couchdb.Database)) |
1873 | |
1874 | === modified file 'dmedia/workers.py' |
1875 | --- dmedia/workers.py 2011-02-19 04:38:12 +0000 |
1876 | +++ dmedia/workers.py 2011-02-21 12:01:51 +0000 |
1877 | @@ -28,10 +28,12 @@ |
1878 | from threading import Thread, Lock |
1879 | from Queue import Empty |
1880 | import logging |
1881 | + |
1882 | from .constants import TYPE_ERROR |
1883 | +from .abstractcouch import get_couchdb_server, get_dmedia_db |
1884 | + |
1885 | |
1886 | log = logging.getLogger() |
1887 | - |
1888 | _workers = {} |
1889 | |
1890 | |
1891 | @@ -79,14 +81,24 @@ |
1892 | return exception.__name__ |
1893 | |
1894 | |
1895 | -def dispatch(q, worker, key, args): |
1896 | +def dispatch(worker, env, q, key, args): |
1897 | + """ |
1898 | + Dispatch a worker in this proccess. |
1899 | + |
1900 | + :param worker: name of worker class, eg ``'ImportWorker'`` |
1901 | + :param env: a ``dict`` containing run-time information like the CouchDB URL |
1902 | + :param q: a ``multiprocessing.Queue`` or similar |
1903 | + :param key: a key to uniquely identify this worker among active workers |
1904 | + controlled by the `Manager` that launched this worker |
1905 | + :param args: arguments to be passed to `Worker.run()` |
1906 | + """ |
1907 | pid = current_process().pid |
1908 | - log.debug('dispatch in process %d: worker=%r, key=%r, args=%r', |
1909 | + log.debug('** dispatch in process %d: worker=%r, key=%r, args=%r', |
1910 | pid, worker, key, args |
1911 | ) |
1912 | try: |
1913 | klass = _workers[worker] |
1914 | - inst = klass(q, key, args) |
1915 | + inst = klass(env, q, key, args) |
1916 | inst.run() |
1917 | except Exception as e: |
1918 | log.exception('exception in procces %d, worker=%r', pid) |
1919 | @@ -106,7 +118,8 @@ |
1920 | |
1921 | |
1922 | class Worker(object): |
1923 | - def __init__(self, q, key, args): |
1924 | + def __init__(self, env, q, key, args): |
1925 | + self.env = env |
1926 | self.q = q |
1927 | self.key = key |
1928 | self.args = args |
1929 | @@ -143,12 +156,20 @@ |
1930 | ) |
1931 | |
1932 | |
1933 | +class CouchWorker(Worker): |
1934 | + def __init__(self, env, q, key, args): |
1935 | + super(CouchWorker, self).__init__(env, q, key, args) |
1936 | + self.server = get_couchdb_server(env) |
1937 | + self.db = get_dmedia_db(env, self.server) |
1938 | + |
1939 | + |
1940 | class Manager(object): |
1941 | - def __init__(self, callback=None): |
1942 | + def __init__(self, env, callback=None): |
1943 | if not (callback is None or callable(callback)): |
1944 | raise TypeError( |
1945 | 'callback must be callable; got %r' % callback |
1946 | ) |
1947 | + self.env = env |
1948 | self._callback = callback |
1949 | self._running = False |
1950 | self._workers = {} |
1951 | @@ -165,7 +186,7 @@ |
1952 | pass |
1953 | |
1954 | def _process_message(self, msg): |
1955 | - log.info('[From %(pid)d] %(signal)s %(args)r', msg) |
1956 | + log.info('[from %(worker)s %(pid)d] %(signal)s %(args)r', msg) |
1957 | with self._lock: |
1958 | signal = msg['signal'] |
1959 | args = msg['args'] |
1960 | @@ -180,10 +201,10 @@ |
1961 | log.error('%s %s: %s: %s', self.name, key, exception, message) |
1962 | |
1963 | def start(self): |
1964 | - log.info('Killing %s', self.name) |
1965 | with self._lock: |
1966 | if self._running: |
1967 | return False |
1968 | + log.info('Starting %s', self.name) |
1969 | self._running = True |
1970 | self._thread = Thread(target=self._signal_thread) |
1971 | self._thread.daemon = True |
1972 | @@ -191,9 +212,9 @@ |
1973 | return True |
1974 | |
1975 | def kill(self): |
1976 | - log.info('Killing %s', self.name) |
1977 | if not self._running: |
1978 | return False |
1979 | + log.info('Killing %s', self.name) |
1980 | self._running = False |
1981 | self._thread.join() # Cleanly shutdown _signal_thread |
1982 | with self._lock: |
1983 | @@ -203,15 +224,24 @@ |
1984 | self._workers.clear() |
1985 | return True |
1986 | |
1987 | - def do(self, worker, key, *args): |
1988 | + def get_worker_env(self, worker, key, args): |
1989 | + return dict(self.env) |
1990 | + |
1991 | + def start_job(self, worker, key, *args): |
1992 | """ |
1993 | Start a process identified by *key*, using worker class *name*. |
1994 | + |
1995 | + :param worker: name of worker class, eg ``'ImportWorker'`` |
1996 | + :param key: a key to uniquely identify new `Worker` among active workers |
1997 | + controlled by this `Manager` |
1998 | + :param args: arguments to be passed to `Worker.run()` |
1999 | """ |
2000 | if key in self._workers: |
2001 | return False |
2002 | + env = self.get_worker_env(worker, key, args) |
2003 | p = multiprocessing.Process( |
2004 | target=dispatch, |
2005 | - args=(self._q, worker, key, args), |
2006 | + args=(worker, env, self._q, key, args), |
2007 | ) |
2008 | p.daemon = True |
2009 | self._workers[key] = p |
2010 | @@ -227,6 +257,9 @@ |
2011 | p.join() |
2012 | return True |
2013 | |
2014 | + def list_jobs(self): |
2015 | + return sorted(self._workers) |
2016 | + |
2017 | def emit(self, signal, *args): |
2018 | """ |
2019 | Emit a signal to higher-level code. |
2020 | @@ -234,3 +267,10 @@ |
2021 | if self._callback is None: |
2022 | return |
2023 | self._callback(signal, args) |
2024 | + |
2025 | + |
2026 | +class CouchManager(Manager): |
2027 | + def __init__(self, env, callback=None): |
2028 | + super(CouchManager, self).__init__(env, callback) |
2029 | + self.server = get_couchdb_server(env) |
2030 | + self.db = get_dmedia_db(env, self.server) |
2031 | |
2032 | === modified file 'setup.py' |
2033 | --- setup.py 2011-02-01 06:09:34 +0000 |
2034 | +++ setup.py 2011-02-21 12:01:51 +0000 |
2035 | @@ -86,6 +86,11 @@ |
2036 | break |
2037 | |
2038 | def run(self): |
2039 | + from dbus.mainloop.glib import DBusGMainLoop |
2040 | + import gobject |
2041 | + DBusGMainLoop(set_as_default=True) |
2042 | + gobject.threads_init() |
2043 | + |
2044 | pynames = tuple(self._pynames_iter()) |
2045 | |
2046 | # Add unit-tests: |
Looks good from what I can see.