| # Copyright 2009 Google Inc. Released under the GPL v2 |
| |
| # This file contains the classes used for the known kernel versions persistent |
| # storage |
| |
| import cPickle, fcntl, os, tempfile |
| |
| |
| class item(object): |
| """Wrap a file item stored in a database.""" |
| def __init__(self, name, size, timestamp): |
| assert type(size) == int |
| assert type(timestamp) == int |
| |
| self.name = name |
| self.size = size |
| self.timestamp = timestamp |
| |
| |
| def __repr__(self): |
| return ("database.item('%s', %d, %d)" % |
| (self.name, self.size, self.timestamp)) |
| |
| |
| def __eq__(self, other): |
| if not isinstance(other, item): |
| return NotImplementedError |
| |
| return (self.name == other.name and self.size == other.size and |
| self.timestamp == other.timestamp) |
| |
| |
| def __ne__(self, other): |
| return not self.__eq__(other) |
| |
| |
| class database(object): |
| """ |
| This is an Abstract Base Class for the file items database, not strictly |
| needed in Python because of the dynamic nature of the language but useful |
| to document the expected common API of the implementations. |
| """ |
| |
| def get_dictionary(self): |
| """ |
| Should be implemented to open and read the persistent contents of |
| the database and return it as a key->value dictionary. |
| """ |
| raise NotImplementedError('get_dictionary not implemented') |
| |
| |
| def merge_dictionary(self, values): |
| """ |
| Should be implemented to merge the "values" dictionary into the |
| database persistent contents (ie to update existent entries and to add |
| those that do not exist). |
| """ |
| raise NotImplementedError('merge_dictionary not implemented') |
| |
| |
| class dict_database(database): |
| """ |
| A simple key->value database that uses standard python pickle dump of |
| a dictionary object for persistent storage. |
| """ |
| def __init__(self, path): |
| self.path = path |
| |
| |
| def get_dictionary(self, _open_func=open): |
| """ |
| Return the key/value pairs as a standard dictionary. |
| """ |
| try: |
| fd = _open_func(self.path, 'rb') |
| except IOError: |
| # no db file, considering as if empty dictionary |
| res = {} |
| else: |
| try: |
| res = cPickle.load(fd) |
| finally: |
| fd.close() |
| |
| return res |
| |
| |
| def _aquire_lock(self): |
| fd = os.open(self.path + '.lock', os.O_RDONLY | os.O_CREAT) |
| try: |
| # this may block |
| fcntl.flock(fd, fcntl.LOCK_EX) |
| except Exception, err: |
| os.close(fd) |
| raise err |
| |
| return fd |
| |
| |
| def merge_dictionary(self, values): |
| """ |
| Merge the contents of "values" with the current contents of the |
| database. |
| """ |
| if not values: |
| return |
| |
| # use file locking to make the read/write of the file atomic |
| lock_fd = self._aquire_lock() |
| |
| # make sure we release locks in case of exceptions (in case the |
| # process dies the OS will release them for us) |
| try: |
| contents = self.get_dictionary() |
| contents.update(values) |
| |
| # use a tempfile/atomic-rename technique to not require |
| # synchronization for get_dictionary() calls and also protect |
| # against full disk file corruption situations |
| fd, fname = tempfile.mkstemp(prefix=os.path.basename(self.path), |
| dir=os.path.dirname(self.path)) |
| write_file = os.fdopen(fd, 'wb') |
| try: |
| try: |
| cPickle.dump(contents, write_file, |
| protocol=cPickle.HIGHEST_PROTOCOL) |
| finally: |
| write_file.close() |
| |
| # this is supposed to be atomic on POSIX systems |
| os.rename(fname, self.path) |
| except Exception: |
| os.unlink(fname) |
| raise |
| finally: |
| # close() releases any locks on that fd |
| os.close(lock_fd) |