diff options
author | Giuseppe D'Angelo <giuseppe.dangelo@kdab.com> | 2018-05-06 20:30:33 +0200 |
---|---|---|
committer | Tero Kojo <tero.kojo@qt.io> | 2018-05-07 07:40:52 +0000 |
commit | 58f1f8a6411926488342d2f9872811520d8eb580 (patch) | |
tree | b8b3d93568551ddc4f489fa891e0be1c195533c9 /rawdoglib/persister.py | |
parent | c7161128b2c74c92a631a4caea518bb41759f516 (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.py | 221 |
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 |