diff options
author | Benjamin Saller <bcsaller@objectrealms.net> | 2007-07-30 16:48:06 (GMT) |
---|---|---|
committer | Benjamin Saller <bcsaller@objectrealms.net> | 2007-07-30 16:48:06 (GMT) |
commit | b56dfd73ba5aeae93c9375cf97452e80fbe66e84 (patch) | |
tree | 960a4f64a8491e120bccf26e436ad64349e94f81 | |
parent | dfefc1f8623611f5ce2615a62679f349f7428f75 (diff) |
wip on versioning
-rw-r--r-- | src/olpc/datastore/datastore.py | 40 | ||||
-rw-r--r-- | src/olpc/datastore/deltastream.py | 45 | ||||
-rw-r--r-- | tests/runalltests.py | 2 | ||||
-rw-r--r-- | tests/simple_versions.txt | 107 | ||||
-rw-r--r-- | tests/testutils.py | 10 |
5 files changed, 195 insertions, 9 deletions
diff --git a/src/olpc/datastore/datastore.py b/src/olpc/datastore/datastore.py index 19342e2..9883da3 100644 --- a/src/olpc/datastore/datastore.py +++ b/src/olpc/datastore/datastore.py @@ -56,7 +56,6 @@ class DataStore(dbus.service.Object): self.backends.append(backendClass) ## MountPoint API - #@utils.sanitize_dbus @dbus.service.method(DS_DBUS_INTERFACE, in_signature="sa{sv}", out_signature='s') @@ -98,7 +97,6 @@ class DataStore(dbus.service.Object): """ return [mp.descriptor() for mp in self.mountpoints.itervalues()] - #@utils.sanitize_dbus @dbus.service.method(DS_DBUS_INTERFACE, in_signature="s", out_signature="") @@ -184,7 +182,38 @@ class DataStore(dbus.service.Object): return mp # PUBLIC API - #@utils.sanitize_dbus + @dbus.service.method(DS_DBUS_INTERFACE, + in_signature='a{sv}s', + out_signature='ss') + def checkin(self, props, filelike=None): + """Check in a new content object. When uid is included in the + properties this is considered an update to the content object + which automatically creates a new revision. + + This method returns the uid and version id tuple. + """ + mp = self._resolveMountpoint(props) + uid = props.get('uid') + if uid: + # this is an update operation + + else: + # this is a create operation + + return uid, vid + + + @dbus.service.method(DS_DBUS_INTERFACE, + in_signature='ss', + out_signature='a{sv}s') + def checkout(self, uid, vid=None): + """Check out a revision of a document. Returns the properties + of that version and a filename with the contents of that + version. + """ + pass + + @dbus.service.method(DS_DBUS_INTERFACE, in_signature='a{sv}s', out_signature='s') @@ -234,7 +263,6 @@ class DataStore(dbus.service.Object): return d, len(d) - #@utils.sanitize_dbus @dbus.service.method(DS_DBUS_INTERFACE, in_signature='a{sv}', out_signature='aa{sv}u') @@ -369,7 +397,6 @@ class DataStore(dbus.service.Object): continue return c - #@utils.sanitize_dbus @dbus.service.method(DS_DBUS_INTERFACE, in_signature='s', out_signature='s') @@ -380,7 +407,6 @@ class DataStore(dbus.service.Object): except AttributeError: pass return '' - #@utils.sanitize_dbus @dbus.service.method(DS_DBUS_INTERFACE, in_signature='s', out_signature='a{sv}') @@ -407,7 +433,6 @@ class DataStore(dbus.service.Object): return results - #@utils.sanitize_dbus @dbus.service.method(DS_DBUS_INTERFACE, in_signature='sa{sv}s', out_signature='') @@ -426,7 +451,6 @@ class DataStore(dbus.service.Object): @dbus.service.signal(DS_DBUS_INTERFACE, signature="s") def Updated(self, uid): pass - #@utils.sanitize_dbus @dbus.service.method(DS_DBUS_INTERFACE, in_signature='s', out_signature='') diff --git a/src/olpc/datastore/deltastream.py b/src/olpc/datastore/deltastream.py new file mode 100644 index 0000000..f880a07 --- /dev/null +++ b/src/olpc/datastore/deltastream.py @@ -0,0 +1,45 @@ +""" +deltastream +~~~~~~~~~~~~~~~~~~~~ +A forward or backward stream of delta information used to manage file +versions efficiently + +""" + +__author__ = 'Benjamin Saller <bcsaller@objectrealms.net>' +__docformat__ = 'restructuredtext' +__copyright__ = 'Copyright ObjectRealms, LLC, 2007' +__license__ = 'The GNU Public License V2+' + + +import bsdiff +FULL = 1 +PATCH = 2 + +class DeltaStream(object): + """Record and Reconstruct objects from a forward diff chain. When diff + size/distance from the original is over a threshold we record a + new version in its entirety + """ + + def _record(self, old_fn, new_fn): + od = open(old_fn, 'r').read() + nd = open(new_fn, 'r').read() + + #XXX: This needs to be done more memory efficiently + patch = bsdiff.Patch(od, nd) + # XXX: again, memory inefficient + if len(str(patch)) < (len(nd) / 2.0): + # The patch is larger than some threshold, we want to + # record a new full version rather than a patch + return FULL, nd + else: + return PATCH, patch + + def record(self, old_fn, new_fn): + mode, data = self._record(old_fn, new_fn) + if mode is FULL: + pass + elif mode is PATCH: + pass + diff --git a/tests/runalltests.py b/tests/runalltests.py index 8fee87e..a152156 100644 --- a/tests/runalltests.py +++ b/tests/runalltests.py @@ -27,7 +27,7 @@ doctests = [ resource_filename(__name__, "mountpoints.txt"), resource_filename(__name__, "properties.txt"), resource_filename(__name__, "dateranges.txt"), - + resource_filename(__name__, "simple_versions.txt"), ] doctest_options = doctest.ELLIPSIS diff --git a/tests/simple_versions.txt b/tests/simple_versions.txt new file mode 100644 index 0000000..9bf61d3 --- /dev/null +++ b/tests/simple_versions.txt @@ -0,0 +1,107 @@ +The Datastore supports versioning of content objects and +metadata. Inorder to support this we introduce new API calls and a +higher level set of semantic operations around versioning. + +Let's create a simple datastore and add some content. + +>>> import os +>>> assert os.system('rm -rf /tmp/store1/') == 0 + +>>> from olpc.datastore import DataStore +>>> from olpc.datastore import backingstore +>>> from testutils import * + +>>> ds = DataStore() +>>> ds.registerBackend(backingstore.FileBackingStore) +>>> mp1 = ds.mount("/tmp/store1", dict(title="Primary Storage")) + + +The checkin operation will create new content or update existing +content and increment the version all in a single operation. It takes +the arguments of a properties dictionary and an optional filename of +the content to store with this revision. + +>>> fn = tmpData("Part 1 -- it begins") +>>> uid, vid = ds.checkin({'title' : 'A day in the life'}, fn) + +This operation returns the uid of the object and its current version +id. + +From the datastore we can now verify that this object exists and is +indexed. To ensure this for testing we first allow the indexer to +complete all its pending operations + +>>> ds.complete_indexing() + +>>> results, count = ds.find("A day") +>>> assert count == 1 +>>> assert results[0]['uid'] == uid + +We can also search on its content directly. + +>>> results, count = ds.find("Part 1") +>>> assert count == 1 +>>> assert results[0]['uid'] == uid + + +To get a copy of this file out that we can manipulate we can use the +checkout command. By default checkout will check out the HEAD revsion +(the most recent) of a document. It returns the properties dictionary +and the filename of the checkout which is ours to manipulate. + +>>> props, fn = ds.checkout(uid) + +>>> assert props['title'] == "A day in the life" +>>> assert props['vid'] == vid + +>>> contents = open(fn, 'r').read() +>>> assert contents.startswith("Part 1") + +Lets make a revision to this content. + +>>> fn2 = tmpData("Part Two -- the second helping") + +We are going to check in the new file using the props dict of the last +call after making our modifications and supplying our new file. + +(note that we changed the case of 'life' here) + +>>> props['title'] = "A day in the Life" +>>> ds.checkin(props, fn2) +>>> ds.complete_indexing() + + +Verify that the contents of the old version isn't returned in the +search. By default old versions are not included. + +>>> r, c = ds.find("begins")) +>>> assert c == 0 + +Verify that the HEAD revision of the content is searchable by default. + +>>> r, c = ds.find("second") +>>> assert c == 1 +>>> assert r[0]['uid'] == uid + +Lets check out the head version of this document now. + +>>> props, rev2 = ds.checkout(uid) + +Check that the id is the same and the version id isn't. + +>>> assert props['id'] == uid +>>> assert props['vid'] != vid + +Verify the contents of the file is as expected. + +>>> contents = open(rev2, 'r').read() +>>> assert contents.startswith("Part Two") + + + +>>> ds.stop(); del ds + + + + + diff --git a/tests/testutils.py b/tests/testutils.py index fc667db..e81b22c 100644 --- a/tests/testutils.py +++ b/tests/testutils.py @@ -8,3 +8,13 @@ def tmpData(data): os.close(fd) return fn +# Search result set handlers +def expect(r, count=None): + if count: assert r[1] == count + return list(r[0]) +def expect_single(r): + assert r[1] == 1 + return r[0].next() +def expect_none(r): + assert r[1] == 0 + assert list(r[0]) == [] |