Merge lp:~jderose/dmedia/closer-to-restful into lp:dmedia

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
Reviewer Review Type Date Requested Status
Jason Gerard DeRose Approve
David Jordan Approve
Review via email: mp+50575@code.launchpad.net

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://bugs.launchpad.net/ubuntu/+source/desktopcouch/+bug/714406

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.
lp:~jderose/dmedia/closer-to-restful updated
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
David Jordan (dmj726) wrote :

Looks good from what I can see.

review: Approve
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:

Subscribers

People subscribed via source and target branches