From c634dd0e9290005d3896506af490b4bfc9a12e83 Mon Sep 17 00:00:00 2001 From: Thomas Vander Stichele Date: Sun, 2 Dec 2012 16:38:03 +0000 Subject: [PATCH] * 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. --- ChangeLog | 12 ++ morituri/common/Makefile.am | 1 + morituri/common/cache.py | 177 +++++++++++++++++++++ morituri/common/program.py | 26 +-- morituri/test/Makefile.am | 5 +- morituri/test/cache/result/fe105a11.pickle | Bin 0 -> 4878 bytes morituri/test/test_common_cache.py | 19 +++ 7 files changed, 216 insertions(+), 24 deletions(-) create mode 100644 morituri/common/cache.py create mode 100644 morituri/test/cache/result/fe105a11.pickle create mode 100644 morituri/test/test_common_cache.py diff --git a/ChangeLog b/ChangeLog index 5d60e1e..277b19d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2012-12-02 Thomas Vander Stichele + + * 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 * morituri/rip/drive.py: diff --git a/morituri/common/Makefile.am b/morituri/common/Makefile.am index b3304f6..3d64742 100644 --- a/morituri/common/Makefile.am +++ b/morituri/common/Makefile.am @@ -6,6 +6,7 @@ morituri_PYTHON = \ __init__.py \ accurip.py \ checksum.py \ + cache.py \ common.py \ config.py \ drive.py \ diff --git a/morituri/common/cache.py b/morituri/common/cache.py new file mode 100644 index 0000000..3e372f4 --- /dev/null +++ b/morituri/common/cache.py @@ -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 . + +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 diff --git a/morituri/common/program.py b/morituri/common/program.py index 1c970f0..afdb5c9 100644 --- a/morituri/common/program.py +++ b/morituri/common/program.py @@ -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 diff --git a/morituri/test/Makefile.am b/morituri/test/Makefile.am index f620a36..deb8b9a 100644 --- a/morituri/test/Makefile.am +++ b/morituri/test/Makefile.am @@ -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 - diff --git a/morituri/test/cache/result/fe105a11.pickle b/morituri/test/cache/result/fe105a11.pickle new file mode 100644 index 0000000000000000000000000000000000000000..97c91cc0ea689f3d8d07a384b1ad324a8a2446e1 GIT binary patch literal 4878 zcmbu?*>@XP9l&v~k}dB^Q&K|91Oiq{tc;|Q#Rr(v@c#X+}TI zD6tB+q(IZKFD;O=w@ph6C8cHG17!_R%J#%R01q5^;-%+Mes|{1@yMFIsD81JB!A|6 z?=1J~TvxPEF>AVAuj#QGvFc@;|B9^W)fMN32y~dxNuevMgw7D7Xx5-x={vSGx3+q8 zMSvb9q}FWRvY}TQnI)F38;x;Gn9=ld5kg9@t=nY+VdaKZjR+_3P`a^!@0*5TvlrBI z8TyovT~iCEE$Cl|Nc5UXtL#Hw)PM& zpAP3o2^sZDp44D8+o|-G^fEEj3W2dTz_-2eAKVr%ObFLZJ}AaxLhI)j47+9u(QE$O z{#dE37GQ_cQzhzY*f~G-@5gRqg-WPeBO7WJuFG~P;euIhumSCI22{^SH5}~q6kZe6 zh8J&q?&5^DH8gEB+og@Ioj|0$fh2+kk~4*Q(=58VyS1%Q4Fk1ZP6Y3|fx>ICYe7n+ zlc|K1iKimXdEJ;-Xge<%lsB!H%hmzG|I1C?(~|1%;>E9{a3h<^-Ns9u&8a<@+WsXW z1*OiIMu}LKZW@*lEfLj@3Cp@=HH5ixSvPH)UlKR5@PGY{T?-w2j#(Zremw<&6<;dt zxu?0f7mH7}J-Ml%;_3Q^pbFEb=}zh1mML{Ic-b2$ypf&#L_(CMct*^`x1IeE2Hw$j zU^*ypl~k&>YU7l;h5K3x{W(1JO%&eDLX#OelaZy2xIHxdsxH$^Q0Ni!EIvY1TgYhy zA9?epLEPeTr}&X`I4}Hc`@48?l)`?eSWbzPa#~7?+orY;i%+#ZwUa@`Cp1zc<3-`5 zS;t&A_GUNsia&NMiO0T$!U1-LUiZq&+pf_5S9Jl%L6wI|Ls(LaI%U5-BMaPj9DqZ;G3uw>%-#=FKX zm0L>vSv>fy6yC-rv;Xmj*6FIa_VGZMU?vIR!lG?G$chQC;aLuWybT z!Kn4NqXJi&+*v~K;izxggz#tiRBIhe;dQrBIK=Ayd0}90bKNM`oo~A?aG5z;cQ$i} zD(bzsWfkUe^GRYccGDO2f@+hZV4K1rqgW%htqQBSC8?+eU(TnUVO+d|Mc#7Gn)xth zD9o~9yzub(x#nSv;V{~Mq{jmnp?REQzS*9x^9SafllQ1UkG0hELsAY2lvTH*)?vyCY8A+CsvXqcv zNyEMUvVU*iWU-xn1df<+l)|y7(xY3r|9!{WAUaAew;Y#cF)pT(u%h&;^`dS>z@51H z?y0J_26t&Y*fzXw*!ZPkJ=^IVt53~aX1Q(?IF;?F=Ph<+f^}@j)Uf^-AvV)dJk7m zQz(1t9#p%z`po*4TA^TUS8-lNFkQ8Wsa2)|S8Eh3PrV7%9 z=j*)`-WTA?@P5|~Gxq`Jp6Bj^6fSt~IJ#l(zWjjS{Q!jz2Dp>(A=m9=?uVKC5$=AJ z!pA)KW_0_wJM#~}`*8}N2yi9%r0e!G_aWv!%-v5>__XIvpxe*g!DN?j9zH|iVyl}< z!6U94VeV&{`#J7@p28PAS421B&F_7F_lp$16yQqmW!D{G?xW0ojJuChxa7G>bO*S5 z=|#W$1cfI9+%$Z}bqAUI6my^E?pG;%&2!V}4sv(bj&9#PJVW8@f$nA39b)dY%>4#; zpQG@+=gQ~~arYtB?|zfQw*uTaeA{)0nfo2)ewVx7qwsyty#?K2?jC#E@BVv{JhmoCEx|u9cAt>nEOla{))n{J$D-2 zQSM$o+~b?y-%$8%)Fs9kag`Cj