* morituri/common/cache.py (added):

* morituri/test/cache (added):
	* morituri/test/cache/result (added):
	* morituri/test/cache/result/fe105a11.pickle (added):
	* morituri/test/test_common_cache.py (added):
	* morituri/common/Makefile.am:
	* morituri/common/program.py:
	* morituri/test/Makefile.am:
	  Extract ResultCache object into separate file.
This commit is contained in:
Thomas Vander Stichele
2012-12-02 16:38:03 +00:00
parent 3e0363de32
commit c634dd0e92
7 changed files with 216 additions and 24 deletions

View File

@@ -1,3 +1,15 @@
2012-12-02 Thomas Vander Stichele <thomas at apestaart dot org>
* morituri/common/cache.py (added):
* morituri/test/cache (added):
* morituri/test/cache/result (added):
* morituri/test/cache/result/fe105a11.pickle (added):
* morituri/test/test_common_cache.py (added):
* morituri/common/Makefile.am:
* morituri/common/program.py:
* morituri/test/Makefile.am:
Extract ResultCache object into separate file.
2012-12-02 Thomas Vander Stichele <thomas at apestaart dot org>
* morituri/rip/drive.py:

View File

@@ -6,6 +6,7 @@ morituri_PYTHON = \
__init__.py \
accurip.py \
checksum.py \
cache.py \
common.py \
config.py \
drive.py \

177
morituri/common/cache.py Normal file
View File

@@ -0,0 +1,177 @@
# -*- Mode: Python; test-case-name: morituri.test.test_common_cache -*-
# vi:si:et:sw=4:sts=4:ts=4
# Morituri - for those about to RIP
# Copyright (C) 2009 Thomas Vander Stichele
# This file is part of morituri.
#
# morituri is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# morituri is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with morituri. If not, see <http://www.gnu.org/licenses/>.
import os
import os.path
import tempfile
import shutil
from morituri.result import result
from morituri.extern.log import log
class Persister(object):
"""
I wrap an optional pickle to persist an object to disk.
Instantiate me with a path to automatically unpickle the object.
Call persist to store the object to disk; it will get stored if it
changed from the on-disk object.
@ivar object: the persistent object
"""
def __init__(self, path=None, default=None):
"""
If path is not given, the object will not be persisted.
This allows code to transparently deal with both persisted and
non-persisted objects, since the persist method will just end up
doing nothing.
"""
self._path = path
self.object = None
self._unpickle(default)
def persist(self, obj=None):
"""
Persist the given object, if we have a persistence path and the
object changed.
If object is not given, re-persist our object, always.
If object is given, only persist if it was changed.
"""
# don't pickle if it's already ok
if obj and obj == self.object:
return
# store the object on ourselves if not None
if obj is not None:
self.object = obj
# don't pickle if there is no path
if not self._path:
return
# default to pickling our object again
if obj is None:
obj = self.object
# pickle
self.object = obj
(fd, path) = tempfile.mkstemp(suffix='.morituri.pickle')
handle = os.fdopen(fd, 'wb')
import pickle
pickle.dump(obj, handle, 2)
handle.close()
# do an atomic move
shutil.move(path, self._path)
def _unpickle(self, default=None):
self.object = default
if not self._path:
return None
if not os.path.exists(self._path):
return None
handle = open(self._path)
import pickle
try:
self.object = pickle.load(handle)
except:
# can fail for various reasons; in that case, pretend we didn't
# load it
pass
def delete(self):
self.object = None
os.unlink(self._path)
class PersistedCache(object):
"""
I wrap a directory of persisted objects.
"""
path = None
def __init__(self, path):
self.path = path
try:
os.makedirs(self.path)
except OSError, e:
if e.errno != 17: # FIXME
raise
def _getPath(self, key):
return os.path.join(self.path, '%s.pickle' % key)
def get(self, key):
"""
Returns the persister for the given key.
"""
persister = Persister(self._getPath(key))
if persister.object:
if hasattr(persister.object, 'instanceVersion'):
o = persister.object
if o.instanceVersion < o.__class__.classVersion:
persister.delete()
return persister
class ResultCache(log.Loggable):
def __init__(self, path=None):
if not path:
path = self._getResultCachePath()
self._path = path
self._pcache = PersistedCache(self._path)
def _getResultCachePath(self):
path = os.path.join(os.path.expanduser('~'), '.morituri', 'cache',
'result')
return path
def getRipResult(self, cddbdiscid):
"""
Retrieve the persistable RipResult either from our cache (from a
previous, possibly aborted rip), or return a new one.
@rtype: L{Persistable} for L{result.RipResult}
"""
presult = self._pcache.get(cddbdiscid)
if not presult.object:
self.debug('result for cddbdiscid %r not in cache, creating',
cddbdiscid)
presult.object = result.RipResult()
presult.persist(self.result)
else:
self.debug('result for cddbdiscid %r found in cache, reusing',
cddbdiscid)
return presult

View File

@@ -27,8 +27,7 @@ Common functionality and class for all programs using morituri.
import os
import time
from morituri.common import common, log, musicbrainzngs
from morituri.result import result
from morituri.common import common, log, musicbrainzngs, cache
from morituri.program import cdrdao, cdparanoia
from morituri.image import image
@@ -59,16 +58,13 @@ class Program(log.Loggable):
@param record: whether to record results of API calls for playback.
"""
self._record = record
self._cache = cache.ResultCache()
def _getTableCachePath(self):
path = os.path.join(os.path.expanduser('~'), '.morituri', 'cache',
'table')
return path
def _getResultCachePath(self):
path = os.path.join(os.path.expanduser('~'), '.morituri', 'cache',
'result')
return path
def loadDevice(self, device):
"""
@@ -129,22 +125,8 @@ class Program(log.Loggable):
"""
assert self.result is None
path = self._getResultCachePath()
pcache = common.PersistedCache(path)
presult = pcache.get(cddbdiscid)
if not presult.object:
self.debug('result for cddbdiscid %r not in cache, creating',
cddbdiscid)
presult.object = result.RipResult()
presult.persist(self.result)
else:
self.debug('result for cddbdiscid %r found in cache, reusing',
cddbdiscid)
self.result = presult.object
self._presult = presult
self._presult = self._cache.getRipResult(cddbdiscid)
self.result = self._presult.object
return self.result

View File

@@ -4,6 +4,7 @@ EXTRA_DIST = \
__init__.py \
common.py \
test_common_accurip.py \
test_common_cache.py \
test_common_checksum.py \
test_common_common.py \
test_common_config.py \
@@ -45,7 +46,8 @@ EXTRA_DIST = \
cdparanoia.progress.error \
cdrdao.readtoc.progress \
silentalarm.result.pickle \
track.flac
track.flac \
cache/result/fe105a11.pickle
# re-generation of test files when needed
@@ -57,4 +59,3 @@ regenerate: track.flac
track.flac:
gst-launch audiotestsrc num-buffers=10 samplesperbuffer=588 ! audioconvert ! audio/x-raw-int,channels=2,width=16,height=16,rate=44100 ! flacenc ! filesink location=track.flac

Binary file not shown.

View File

@@ -0,0 +1,19 @@
# -*- Mode: Python; test-case-name: morituri.test.test_common_cache -*-
# vi:si:et:sw=4:sts=4:ts=4
import os
from morituri.common import cache
from morituri.test import common as tcommon
class ResultCacheTestCase(tcommon.TestCase):
def setUp(self):
self.cache = cache.ResultCache(
os.path.join(os.path.dirname(__file__), 'cache', 'result'))
def testGet(self):
result = self.cache.getRipResult('fe105a11')
self.assertEquals(result.object.title, "The Writing's on the Wall")