summaryrefslogtreecommitdiffstats
path: root/rawdoglib/persister.py
diff options
context:
space:
mode:
authorGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2018-05-06 20:30:33 +0200
committerTero Kojo <tero.kojo@qt.io>2018-05-07 07:40:52 +0000
commit58f1f8a6411926488342d2f9872811520d8eb580 (patch)
treeb8b3d93568551ddc4f489fa891e0be1c195533c9 /rawdoglib/persister.py
parentc7161128b2c74c92a631a4caea518bb41759f516 (diff)
Upgrade to rawdog 2.22
Change-Id: Iccd32db1068ca65ee1e6de88ad137fa3950efde5 Reviewed-by: Tero Kojo <tero.kojo@qt.io>
Diffstat (limited to 'rawdoglib/persister.py')
-rw-r--r--rawdoglib/persister.py221
1 files changed, 163 insertions, 58 deletions
diff --git a/rawdoglib/persister.py b/rawdoglib/persister.py
index 6c06e2c..40169da 100644
--- a/rawdoglib/persister.py
+++ b/rawdoglib/persister.py
@@ -1,87 +1,192 @@
-# persister: safe class persistance wrapper
-# Copyright 2003, 2004, 2005 Adam Sampson <ats@offog.org>
+# persister: persist Python objects safely to pickle files
+# Copyright 2003, 2004, 2005, 2013, 2014 Adam Sampson <ats@offog.org>
#
-# persister is free software; you can redistribute it and/or modify it
-# under the terms of the GNU Lesser General Public License as
-# published by the Free Software Foundation; either version 2.1 of the
-# License, or (at your option) any later version.
+# rawdog is free software; you can redistribute and/or modify it
+# under the terms of that license as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option)
+# any later version.
#
-# persister is distributed in the hope that it will be useful, but
+# rawdog 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
-# Lesser General Public License for more details.
+# 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 Lesser General Public
-# License along with persister; see the file COPYING.LGPL. If not,
-# write to the Free Software Foundation, Inc., 51 Franklin Street,
-# Fifth Floor, Boston, MA 02110-1301, USA, or see http://www.gnu.org/.
+# You should have received a copy of the GNU General Public License
+# along with rawdog; see the file COPYING. If not, write to the Free
+# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA, or see http://www.gnu.org/.
-import fcntl, os, errno
import cPickle as pickle
+import errno
+import fcntl
+import os
+import sys
class Persistable:
- """Something which can be persisted. When a subclass of this wants to
- indicate that it has been modified, it should call
- self.modified()."""
- def __init__(self): self._modified = False
- def modified(self, state = True): self._modified = state
- def is_modified(self): return self._modified
+ """An object which can be persisted."""
-class Persister:
- """Persist another class to a file, safely. The class being persisted
- must derive from Persistable (although this isn't enforced)."""
+ def __init__(self):
+ self._modified = False
- def __init__(self, filename, klass, use_locking = True):
- self.filename = filename
+ def modified(self, state=True):
+ """Mark the object as having been modified (or not)."""
+ self._modified = state
+
+ def is_modified(self):
+ return self._modified
+
+class Persisted:
+ """Context manager for a persistent object. The object being persisted
+ must implement the Persistable interface."""
+
+ def __init__(self, klass, filename, persister):
self.klass = klass
- self.use_locking = use_locking
- self.file = None
+ self.filename = filename
+ self.persister = persister
+ self.lock_file = None
self.object = None
+ self.refcount = 0
+
+ def rename(self, new_filename):
+ """Rename the persisted file. This works whether the file is
+ currently open or not."""
+
+ self.persister._rename(self.filename, new_filename)
+ for ext in ("", ".lock"):
+ try:
+ os.rename(self.filename + ext,
+ new_filename + ext)
+ except OSError, e:
+ # If the file doesn't exist (yet),
+ # that's OK.
+ if e.errno != errno.ENOENT:
+ raise e
+ self.filename = new_filename
+
+ def __enter__(self):
+ """As open()."""
+ return self.open()
- def load(self, no_block = True):
- """Load the persisted object from the file, or create a new one
- if this isn't possible. Returns the loaded object."""
+ def __exit__(self, type, value, tb):
+ """As close(), unless an exception occurred in which case do
+ nothing."""
+ if tb is None:
+ self.close()
- def get_lock():
- if not self.use_locking:
- return True
+ def open(self, no_block=True):
+ """Return the persistent object, loading it from its file if it
+ isn't already open. You must call close() once you're finished
+ with the object.
+
+ If no_block is True, then this will return None if loading the
+ object would otherwise block (i.e. if it's locked by another
+ process)."""
+
+ if self.refcount > 0:
+ # Already loaded.
+ self.refcount += 1
+ return self.object
+
+ try:
+ self._open(no_block)
+ except KeyboardInterrupt:
+ sys.exit(1)
+ except:
+ print "An error occurred while reading state from " + os.path.abspath(self.filename) + "."
+ print "This usually means the file is corrupt, and removing it will fix the problem."
+ sys.exit(1)
+
+ self.refcount = 1
+ return self.object
+
+ def _get_lock(self, no_block):
+ if not self.persister.use_locking:
+ return True
+
+ self.lock_file = open(self.filename + ".lock", "w+")
+ try:
mode = fcntl.LOCK_EX
if no_block:
mode |= fcntl.LOCK_NB
- try:
- fcntl.lockf(self.file.fileno(), mode)
- except IOError, e:
- if no_block and e.errno in (errno.EACCES, errno.EAGAIN):
- return False
- raise e
- return True
+ fcntl.lockf(self.lock_file.fileno(), mode)
+ except IOError, e:
+ if no_block and e.errno in (errno.EACCES, errno.EAGAIN):
+ return False
+ raise e
+ return True
+
+ def _open(self, no_block):
+ self.persister.log("Loading state file: ", self.filename)
+
+ if not self._get_lock(no_block):
+ return None
try:
- self.file = open(self.filename, "r+")
- if not get_lock():
- return None
- self.object = pickle.load(self.file)
- self.object.modified(False)
+ f = open(self.filename, "rb")
except IOError:
- self.file = open(self.filename, "w+")
- if not get_lock():
- return None
+ # File can't be opened.
+ # Create a new object.
self.object = self.klass()
self.object.modified()
- return self.object
+ return
+
+ self.object = pickle.load(f)
+ self.object.modified(False)
+ f.close()
+
+ def close(self):
+ """Reduce the reference count of the persisted object, saving
+ it back to its file if necessary."""
+
+ self.refcount -= 1
+ if self.refcount > 0:
+ # Still in use.
+ return
- def save(self):
- """Save the persisted object back to the file if necessary."""
if self.object.is_modified():
+ self.persister.log("Saving state file: ", self.filename)
newname = "%s.new-%d" % (self.filename, os.getpid())
newfile = open(newname, "w")
- try:
- pickle.dump(self.object, newfile, pickle.HIGHEST_PROTOCOL)
- except AttributeError:
- # Python 2.2 doesn't have the protocol
- # argument.
- pickle.dump(self.object, newfile, True)
+ pickle.dump(self.object, newfile, pickle.HIGHEST_PROTOCOL)
newfile.close()
os.rename(newname, self.filename)
- self.file.close()
+ if self.lock_file is not None:
+ self.lock_file.close()
+ self.persister._remove(self.filename)
+
+class Persister:
+ """Manage the collection of persisted files."""
+
+ def __init__(self, config):
+ self.files = {}
+ self.log = config.log
+ self.use_locking = config.locking
+
+ def get(self, klass, filename):
+ """Get a context manager for a persisted file.
+ If the file is already open, this will return
+ the existing context manager."""
+
+ if filename in self.files:
+ return self.files[filename]
+
+ p = Persisted(klass, filename, self)
+ self.files[filename] = p
+ return p
+
+ def _rename(self, old_filename, new_filename):
+ self.files[new_filename] = self.files[old_filename]
+ del self.files[old_filename]
+
+ def _remove(self, filename):
+ del self.files[filename]
+
+ def delete(self, filename):
+ """Delete a persisted file, along with its lock file,
+ if they exist."""
+ for ext in ("", ".lock"):
+ try:
+ os.unlink(filename + ext)
+ except OSError:
+ pass