Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/src/olpc/datastore/backingstore.py
diff options
context:
space:
mode:
authorBenjamin Saller <bcsaller@objectrealms.net>2007-06-21 21:21:12 (GMT)
committer Benjamin Saller <bcsaller@objectrealms.net>2007-06-21 21:21:12 (GMT)
commit746032d4a967aa3aa97d13f7f15efd8069f35f05 (patch)
tree3ce00178f149860b5ba7782984cfc5a28c9168f4 /src/olpc/datastore/backingstore.py
parenta6760376b32520ca694e3ee5ff1fe8ae6d0ebeaf (diff)
all tests pass on the multi-backend code
Diffstat (limited to 'src/olpc/datastore/backingstore.py')
-rw-r--r--src/olpc/datastore/backingstore.py261
1 files changed, 211 insertions, 50 deletions
diff --git a/src/olpc/datastore/backingstore.py b/src/olpc/datastore/backingstore.py
index 2485775..46bcb43 100644
--- a/src/olpc/datastore/backingstore.py
+++ b/src/olpc/datastore/backingstore.py
@@ -10,10 +10,15 @@ __docformat__ = 'restructuredtext'
__copyright__ = 'Copyright ObjectRealms, LLC, 2007'
__license__ = 'The GNU Public License V2+'
+import cPickle as pickle
import sha
import os
import subprocess
+from olpc.datastore import query
+from olpc.datastore import utils
+
+
class BackingStore(object):
"""Backing stores manage stable storage. We abstract out the
management of file/blob storage through this class, as well as the
@@ -32,17 +37,54 @@ class BackingStore(object):
"""
pass
- def connect(self):
- """connect to the metadata store"""
- self._connect()
-
-
- def prepare(self, datastore, querymanager, **kwargs):
- """Verify the backingstore is ready to begin its duties"""
+ # Init phases
+ @staticmethod
+ def parse(uri):
+ """parse the uri into an actionable mount-point.
+ Returns True or False indicating if this backend handles a
+ given uri.
+ """
return False
+ def initialize_and_load(self):
+ """phase to check the state of the located mount point, this
+ method returns True (mount point is valid) or False (invalid
+ or uninitialized mount point).
+ self.check() which must return a boolean should check if the
+ result of self.locate() is already a datastore and then
+ initialize/load it according to self.options.
+
+ When True self.load() is invoked.
+ When False self.create() followed by self.load() is invoked.
+ """
+ if self.check() is False:
+ self.initialize()
+ self.load()
+ def check(self):
+ return False
+
+ def load(self):
+ """load the index for a given mount-point, then initialize its
+ fulltext subsystem. This is the routine that will bootstrap
+ the querymanager (though create() may have just created it)
+ """
+ pass
+
+ def initialize(self):
+ """Initialize a new mount point"""
+ pass
+
+ # Informational
+ def descriptor(self):
+ """return a dict with atleast the following keys
+ 'id' -- the id used to refer explicitly to the mount point
+ 'title' -- Human readable identifier for the mountpoint
+ 'uri' -- The uri which triggered the mount
+ """
+ pass
+
class FileBackingStore(BackingStore):
""" A backing store that directs maps the storage of content
@@ -57,55 +99,123 @@ class FileBackingStore(BackingStore):
in place. Its the actions create/update that create new revisions
in the datastore and hence new versions.
"""
+ STORE_NAME = "store"
+ INDEX_NAME = "index"
+ DESCRIPTOR_NAME = "metainfo"
def __init__(self, uri, **kwargs):
""" FileSystemStore(path=<root of managed storage>)
"""
- self.base = os.path.join(uri, 'store')
- if not os.path.exists(self.base):
- os.makedirs(self.base)
-
- super(FileBackingStore, self).__init__(uri, **kwargs)
self.options = kwargs
+ self.local_querymanager = self.options.get('local_querymanager', True)
+
+ self.uri = uri
+ self.base = os.path.join(uri, self.STORE_NAME)
+ self.querymanager = None
- def prepare(self, datastore, querymanager, **kwargs):
+ # Informational
+ def descriptor(self):
+ """return a dict with atleast the following keys
+ 'id' -- the id used to refer explicitly to the mount point
+ 'title' -- Human readable identifier for the mountpoint
+ 'uri' -- The uri which triggered the mount
+ """
+ # a hidden file with a pickled dict will live in the base
+ # directory for each storage
+ fn = os.path.join(self.base, self.DESCRIPTOR_NAME)
+ if not os.path.exists(fn):
+ # the data isn't there, this could happen for a number of
+ # reasons (the store isn't writeable)
+ desc = {'id' : self.uri,
+ 'uri' : self.uri,
+ 'title' : self.uri
+ }
+ self.create_descriptor(**desc)
+ else:
+ fp = open(fn, 'r')
+ desc = pickle.load(fp)
+ fp.close()
+
+ return desc
+
+
+ def create_descriptor(self, **kwargs):
+ # create the information descriptor for this store
+ # defaults will be created if need be
+ # passing limited values will leave existing keys in place
+ fn = os.path.join(self.base, self.DESCRIPTOR_NAME)
+ desc = {}
+ if os.path.exists(fn):
+ fp = open(fn, 'r')
+ desc = pickle.load(fp)
+ fp.close()
+ if 'id' not in kwargs: desc['id'] = utils.create_uid()
+ if 'uri' not in kwargs: desc['uri'] = self.uri
+ if not kwargs.get('title', None): desc['title'] = self.uri
+
+ desc.update(kwargs)
+ fp = open(fn, 'w')
+ pickle.dump(desc, fp)
+ fp.close()
+
+ def get_id(self): return self.descriptor()['id']
+ id = property(get_id)
+
+ @staticmethod
+ def parse(uri):
+ return os.path.isabs(uri) or os.path.isdir(uri)
+
+ def check(self):
+ if not os.path.exists(self.uri): return False
+ if not os.path.exists(self.base): return False
+ return True
+
+ def initialize(self):
if not os.path.exists(self.base):
os.makedirs(self.base)
+
+ # examine options and see what the querymanager plan is
+ if self.local_querymanager:
+ # create a local storage using the querymanager
+ # otherwise we will connect the global manager
+ # in load
+ index_name = os.path.join(self.base, self.INDEX_NAME)
+ options = utils.options_for(self.options, 'querymanager_')
+ if 'fulltext_repo' not in options:
+ options['fulltext_repo'] = os.path.join(self.uri,
+ query.DefaultQueryManager.FULLTEXT_NAME)
+
+ qm = query.DefaultQueryManager(index_name, **options)
+ # This will ensure the fulltext and so on are all assigned
+ qm.prepare()
+ qm.bind_to(self)
+ self.create_descriptor(title=self.options.get('title', None))
+ self.querymanager = qm
+
+ def load(self):
+ if not self.querymanager and self.local_querymanager:
+ # create a local storage using the querymanager
+ # otherwise we will connect the global manager
+ # in load
+ index_name = os.path.join(self.base, self.INDEX_NAME)
+ qm = query.DefaultQueryManager(index_name,
+ **utils.options_for(self.options,
+ 'querymanager_'))
+ # This will ensure the fulltext and so on are all assigned
+ qm.prepare()
+ qm.bind_to(self)
+ self.querymanager = qm
+
+ def bind_to(self, datastore):
+ ## signal from datastore that we are being bound to it
self.datastore = datastore
- self.querymanager = querymanager
- return True
def _translatePath(self, uid):
"""translate a UID to a path name"""
return os.path.join(self.base, str(uid))
- def create(self, content, filelike):
- self._writeContent(content.id, filelike, replace=False)
-
-
- def get(self, uid, env=None, allowMissing=False):
- path = self._translatePath(uid)
- if not os.path.exists(path):
- raise KeyError("object for uid:%s missing" % uid)
- else:
- fp = open(path, 'r')
- # now return a Content object from the model associated with
- # this file object
- return self._mapContent(uid, fp, path, env)
-
- def set(self, uid, filelike):
- self._writeContent(uid, filelike)
-
- def delete(self, uid, allowMissing=True):
- path = self._translatePath(uid)
- if os.path.exists(path):
- os.unlink(path)
- else:
- if not allowMissing:
- raise KeyError("object for uid:%s missing" % uid)
-
def _targetFile(self, uid, fp, path, env):
- targetpath = os.path.join('/tmp/', path.replace('/', '_'))
+ targetpath = os.path.join('/tmp/', path.replace('/', '_').replace('.', '__'))
if subprocess.call(['cp', path, targetpath]):
raise OSError("unable to create working copy")
return open(targetpath, 'rw')
@@ -118,16 +228,17 @@ class FileBackingStore(BackingStore):
# we need to map a copy of the content from the backingstore into the
# activities addressable space.
# map this to a rw file
- targetfile = self._targetFile(uid, fp, path, env)
- content.file = targetfile
+ if fp:
+ targetfile = self._targetFile(uid, fp, path, env)
+ content.file = targetfile
- if self.options.get('verify', False):
- c = sha.sha()
- for line in targetfile:
- c.update(line)
- fp.seek(0)
- if c.hexdigest() != content.checksum:
- raise ValueError("Content for %s corrupt" % uid)
+ if self.options.get('verify', False):
+ c = sha.sha()
+ for line in targetfile:
+ c.update(line)
+ fp.seek(0)
+ if c.hexdigest() != content.checksum:
+ raise ValueError("Content for %s corrupt" % uid)
return content
def _writeContent(self, uid, filelike, replace=True):
@@ -148,5 +259,55 @@ class FileBackingStore(BackingStore):
content = self.querymanager.get(uid)
content.checksum = c.hexdigest()
+ # File Management API
+ def create(self, props, filelike):
+ content = self.querymanager.create(props, filelike)
+ filename = filelike
+ if filelike:
+ if isinstance(filelike, basestring):
+ # lets treat it as a filename
+ filelike = open(filelike, "r")
+ filelike.seek(0)
+ self._writeContent(content.id, filelike, replace=False)
+ return content
+ def get(self, uid, env=None, allowMissing=False):
+ content = self.querymanager.get(uid)
+ if not content: raise KeyError(uid)
+ path = self._translatePath(uid)
+ fp = None
+ if os.path.exists(path):
+ fp = open(path, 'r')
+ # now return a Content object from the model associated with
+ # this file object
+ return self._mapContent(uid, fp, path, env)
+
+ def update(self, uid, props, filelike=None):
+ self.querymanager.update(uid, props, filelike)
+ filename = filelike
+ if filelike:
+ if isinstance(filelike, basestring):
+ # lets treat it as a filename
+ filelike = open(filelike, "r")
+ filelike.seek(0)
+ self.set(uid, filelike)
+
+ def set(self, uid, filelike):
+ self._writeContent(uid, filelike)
+
+ def delete(self, uid, allowMissing=True):
+ self.querymanager.delete(uid)
+ path = self._translatePath(uid)
+ if os.path.exists(path):
+ os.unlink(path)
+ else:
+ if not allowMissing:
+ raise KeyError("object for uid:%s missing" % uid)
+
+
+ def find(self, query):
+ return self.querymanager.find(query)
+
+ def stop(self):
+ self.querymanager.stop()