# # # patch "setup.py" # from [e7a29a586a2957f991598466701a6dd698593c27] # to [ef7120cb5380665df5f61b47f1c1878b1c21252d] # # patch "tracmtn/backend.py" # from [7f2e7ecbe83151dd5bfa660f09311d0a361102ae] # to [ec5b7fe95eee2ec6e03af7761376d2c2aea9aaf5] # # patch "tracmtn/cache.py" # from [7d7e1ee4e93be0834249b25ca16ef6763719e9dd] # to [8b1a72f64749b55a4c7cc4fffbe908884a78da1f] # # patch "tracmtn/util.py" # from [51f096d91cd98e73bf75a5989420edbc091db097] # to [75f384b8a0ddd209d1c640ddee6e34775302bbe3] # ============================================================ --- setup.py e7a29a586a2957f991598466701a6dd698593c27 +++ setup.py ef7120cb5380665df5f61b47f1c1878b1c21252d @@ -29,7 +29,7 @@ setup( setup( name = 'TracMonotone', keywords = 'trac monotone scm plugin mtn', - version = '0.0.12', + version = '0.0.13', author = 'Thomas Moschny', author_email = 'address@hidden', packages = ['tracmtn'], ============================================================ --- tracmtn/backend.py 7f2e7ecbe83151dd5bfa660f09311d0a361102ae +++ tracmtn/backend.py ec5b7fe95eee2ec6e03af7761376d2c2aea9aaf5 @@ -22,23 +22,28 @@ USA }}} """ - -from cStringIO import StringIO from trac.versioncontrol.api import Repository, Node, Changeset, \ IRepositoryConnector, NoSuchNode, NoSuchChangeset from trac.wiki import IWikiSyntaxProvider from trac.util import shorten_line, escape +from trac.util.datefmt import format_datetime as format_datetime_trac from trac.core import Component, implements, TracError from trac.config import Option, ListOption -from time import strptime + from tracmtn.automate import MTN, AutomateException from tracmtn.util import get_oldpath, get_parent, Memoize -from tracmtn.cache import CacheManager -from trac.util.datefmt import format_datetime as format_datetime_trac +from tracmtn.cache import get_cache +from cStringIO import StringIO +from time import strptime import re try: + from threading import Lock +except ImportError: + from dummy_threading import Lock + +try: from trac.versioncontrol.web_ui import IPropertyRenderer except ImportError: IPropertyRenderer = None @@ -77,8 +82,9 @@ class MonotoneConnector(Component): # IRepositoryConnector methods def __init__(self): Component.__init__(self) - self.repos = {} + self.mtn_procs = {} self.version = None + self.lock = Lock() def get_supported_types(self): """ @@ -117,26 +123,35 @@ class MonotoneConnector(Component): Return a Repository instance for the given repository type and dir. """ - # note: we don't use type or authname, therefore we can always - # return the same Repository object for the same database path - if not path in self.repos: - self.repos[path] = MonotoneRepository(path, self.log, - self.mtn_binary, self.cachespec, - self.get_revprops()) - # this is the main entry point for users of this plugin, so let's set - # version information here - if not self.version: - interface = self.repos[path].get_interface_version() - binary = INTERFACE_VERSIONS.get(interface, None) - self.version = "interface: %s" % interface - if binary: - self.version += ", binary (guessed): %s" % binary + self.lock.acquire() + try: + # note: we don't use type or authname, therefore we can always + # return the same Repository object for the same database path try: - self.env.systeminfo.append(('Monotone', self.version)) - except AttributeError: - pass # no systeminfo in 0.10 - return self.repos[path] + mtn = self.mtn_procs[path] + except KeyError: + mtn = MTN(path, self.log, self.mtn_binary) + self.mtn_procs[path] = mtn + repos = MonotoneRepository( + mtn, path, self.log, self.cachespec, self.get_revprops()) + # this is the main entry point for users of this plugin, so let's set + # version information here + if not self.version: + interface = repos.get_interface_version() + binary = INTERFACE_VERSIONS.get(interface, None) + self.version = "interface: %s" % interface + if binary: + self.version += ", binary (guessed): %s" % binary + try: + self.env.systeminfo.append(('Monotone', self.version)) + except AttributeError: + pass # no systeminfo in 0.10 + + return repos + finally: + self.lock.release() + # IWikiSyntaxProvider methods def get_wiki_syntax(self): """ @@ -253,36 +268,45 @@ def dates(certvals): return result -class CachedMTN(MTN): +class CachedMTN(object): - def __init__(self, database, log, binary, cachespec): + def __init__(self, mtn, cachespec): + self.mtn = mtn self.cachespec = cachespec - MTN.__init__(self, database, log, binary) + self.parents = Memoize(self.mtn.parents, self._get_cache) + self.manifest = Memoize(self.mtn.manifest, self._get_cache) + self.certs = Memoize(self.mtn.certs, self._get_cache) + self.file_length = Memoize(self.mtn.file_length, self._get_cache) + self.changesets = Memoize(self.mtn.changesets, self._get_cache) - def get_cache(self, realm): - return CacheManager.get_cache(realm, self.cachespec) + def _get_cache(self, realm): + return get_cache(realm, self.cachespec) - parents = Memoize(MTN.parents, get_cache) - manifest = Memoize(MTN.manifest, get_cache) - certs = Memoize(MTN.certs, get_cache) - file_length = Memoize(MTN.file_length, get_cache) - changesets = Memoize(MTN.changesets, get_cache) + def close(self): + self.parents.close() + self.manifest.close() + self.certs.close() + self.file_length.close() + self.changesets.close() + def __getattr__(self, attr): + return getattr(self.mtn, attr) + class MonotoneRepository(Repository): """ Represents a Monotone repository. """ - def close(self): - """Close the connection to the repository.""" - pass - - def __init__(self, path, log, binary, cachespec, revpropspec = None): + def __init__(self, mtn, path, log, cachespec, revpropspec = None): Repository.__init__(self, 'mtn:%s' % path, None, log) - self.mtn = CachedMTN(path, log, binary, cachespec) + self.mtn = CachedMTN(mtn, cachespec) self.revpropspec = revpropspec or {} + def close(self): + """Close the connection to the repository.""" + self.mtn.close() + def get_changeset(self, rev): """ Retrieve a Changeset object that describes the changes made in ============================================================ --- tracmtn/cache.py 7d7e1ee4e93be0834249b25ca16ef6763719e9dd +++ tracmtn/cache.py 8b1a72f64749b55a4c7cc4fffbe908884a78da1f @@ -1,8 +1,8 @@ Trac Plugin for Monotone # -*- coding: utf-8 -*- """ Trac Plugin for Monotone -Copyright 2006, 2007 Thomas Moschny (address@hidden) +Copyright 2006-2008 Thomas Moschny (address@hidden) {{{ This program is free software; you can redistribute it and/or modify @@ -23,188 +23,101 @@ USA """ -try: - from threading import Lock -except ImportError: - from dummy_threading import Lock +backends = {} +def add_backend(name, factory): + backends[name] = factory -class Cache(object): - '''The (abstract) base class of the cache implementations.''' +def get_cache(realm, cachespec): + cachespec = cachespec.split(':') + backend = cachespec[0] + args = cachespec[1:] + return backends[backend](realm, *args) - backends = {} +# ---------------------------------------------------------------------- - @classmethod - def add_backend(self, name, factory): - """Add a backend.""" - self.backends[name] = factory +class LocalMem(object): + """A handle to a simple dictionary.""" - @classmethod - def get_backends(self): - """Return the names of all available backends.""" - for backend in self.backends: - yield backend - - @classmethod - def get_cache(self, realm, cachespec): - """ - Make a new cache (or cache connection, depending on the cache - backend.) The cachespec consists of name of the backend and - possibly arguments to be passed to the backend, separated by a - colon. - """ - cachespec = cachespec.split(':') - backend = cachespec[0] - args = cachespec[1:] - cache = self.backends[backend](realm, *args) - return cache - - # the cache interface - def set(self, key, value): - """Unconditionally set the key's value.""" - raise NotImplementedError - - def get(self, key): - """Return the key's value, or None.""" - raise NotImplementedError - - def delete(self, key): - """Delete the (key, value) pair from the cache. Fails silently - if key was not in the cache.""" - raise NotImplementedError - - def add(self, key, value): - """If key is not already in the cache, add the (key, value) - pair. Fails silently otherwise.""" - raise NotImplementedError - - def replace(self, key, value): - """If key is already in the cache, replace it's value. Fails - silently otherwise.""" - raise NotImplementedError - - def flush_all(self): - """Flush the cache.""" - raise NotImplementedError - - -class CacheManager(object): - """Keeps one cache per realm.""" - caches = {} - locks = {} - @classmethod - def get_cache(self, realm, cachespec): - """Return the cache for realm, creating a new cache if there - isn't already one.""" - self.locks.setdefault(realm, Lock()).acquire() - try: - cache = self.caches[realm] - except KeyError: - cache = Cache.get_cache(realm, cachespec) - self.caches[realm] = cache - self.locks[realm].release() - return cache + def __init__(self, realm, prefix): + name = prefix + realm + self.cache = self.caches.setdefault(name, {}) - -class LocalMem(Cache): - """A simple cache implementation using local memory only.""" - - def __init__(self, realm): - # realm is unused - self.cache = {} - self.lock = Lock() - - def set(self, key, value): - self.cache[key] = value - - def get(self, key): + def __getitem__(self, key): return self.cache[key] - def delete(self, key): - del self.cache[key] + def __setitem__(self, key, value): + self.cache[key] = value - def add(self, key, value): - self.lock.acquire() - if not key in self.cache: - self.cache[key] = value - self.lock.release() + def close(self): + pass - def replace(self, key, value): - self.lock.acquire() - if key in self.cache: - self.cache[key] = value - self.lock.release() +add_backend('localmem', LocalMem) - def flush_all(self): - self.cache = {} +# ---------------------------------------------------------------------- -Cache.add_backend('localmem', LocalMem) - - -# Unfortunately, dbash (i.e. bsddb) doesn't work reliably, at least on -# my system, so we can't simply use anydb, which prefers dbhash. - try: import shelve import dbm except ImportError: dbm = None -class DBMShelve(LocalMem): +class DBMShelve(object): """Using Python's shelve on a DBM database.""" def __init__(self, realm, prefix): self.dbname = prefix + realm self.cache = shelve.Shelf(dbm.open(self.dbname, 'c')) - self.lock = Lock() - def __del__(self): - self.cache.close() + def __getitem__(self, key): + return self.cache[key] - def flush_all(self): - self.cache = Shelf(dbm.open(self.dbname, 'n')) + def __setitem__(self, key, value): + self.cache[key] = value + def close(self): + self.cache.close() + if dbm: - Cache.add_backend('dbmshelve', DBMShelve) + add_backend('dbmshelve', DBMShelve) +# ---------------------------------------------------------------------- -has_bsddb = False try: from bsddb3 import dbshelve, db except ImportError: try: from bsddb import dbshelve, db except ImportError: - db = None + dbshelve, db = None, None -class BSDDBShelve(Cache): - """Using the bsddb3 interface to db.""" - dbenv = None +class BSDDBShelve(object): + """Using the bsddb3 interface to db.""" - @classmethod - def get_env(self, dir): - if not self.dbenv: - self.dbenv = db.DBEnv() - self.dbenv.open(dir, - db.DB_INIT_CDB|db.DB_INIT_MPOOL|db.DB_CREATE) - return self.dbenv + # fixme: using reference counting, and passing the DB_THREAD flag + # to bsddb, we could probably do with only one env and db handle + # for all threads. def __init__(self, realm, dir, prefix=''): - self.dbname = prefix + realm - self.cache = dbshelve.open(self.dbname, dbenv=self.get_env(dir)) + name = prefix + realm + self.dbenv = db.DBEnv() + self.dbenv.open( + dir, db.DB_INIT_CDB|db.DB_INIT_MPOOL| db.DB_CREATE) + self.cache = dbshelve.open( + name, flags=db.DB_CREATE, dbenv=self.dbenv) + + def __getitem__(self, key): + return self.cache[key] - def set(self, key, value): + def __setitem__(self, key, value): self.cache[key] = value - def add(self, key, value): - # FIXME wrong semantics - self.cache[key] = value + def close(self): + self.cache.close() + self.dbenv.close() - def get(self, key): - return self.cache[key] - if db: + add_backend('bsddb', BSDDBShelve) - Cache.add_backend('bsddb', BSDDBShelve) ============================================================ --- tracmtn/util.py 51f096d91cd98e73bf75a5989420edbc091db097 +++ tracmtn/util.py 75f384b8a0ddd209d1c640ddee6e34775302bbe3 @@ -1,8 +1,8 @@ Trac Plugin for Monotone # -*- coding: utf-8 -*- """ Trac Plugin for Monotone -Copyright 2006, 2007 Thomas Moschny (address@hidden) +Copyright 2006-2008 Thomas Moschny (address@hidden) {{{ This program is free software; you can redistribute it and/or modify @@ -90,25 +90,24 @@ class Memoize(object): self.method = method self.get_cache = get_cache self.realm = realm or method.__name__ - self.instance = None self.cache = None - def __get__(self, instance, clazz = None): # IGNORE:W0613 - # non-data descriptor - self.instance = instance - return self + def close(self): + if self.cache: + self.cache.close() + self.cache = None def __call__(self, arg): # Currently, we can only handle one single arg. Moreover, # shelve only supports keys of type integer or string + if self.cache: + try: + return self.cache[arg] + except KeyError: + # not yet in the cache + value = self.method(arg) + self.cache[arg] = value + return value + # oops, no cache yet, get one and restart + self.cache = self.get_cache(self.realm) + return self.__call__(arg) - try: - return self.cache.get(arg) - except KeyError: - # not yet in the cache - value = self.method(self.instance, arg) - self.cache.add(arg, value) - return value - except AttributeError: - # oops, no cache yet, get one and restart - self.cache = self.get_cache(self.instance, self.realm) - return self.__call__(arg)