Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBenjamin Saller <bcsaller@objectrealms.net>2007-07-30 16:48:06 (GMT)
committer Benjamin Saller <bcsaller@objectrealms.net>2007-07-30 16:48:06 (GMT)
commitb56dfd73ba5aeae93c9375cf97452e80fbe66e84 (patch)
tree960a4f64a8491e120bccf26e436ad64349e94f81
parentdfefc1f8623611f5ce2615a62679f349f7428f75 (diff)
wip on versioning
-rw-r--r--src/olpc/datastore/datastore.py40
-rw-r--r--src/olpc/datastore/deltastream.py45
-rw-r--r--tests/runalltests.py2
-rw-r--r--tests/simple_versions.txt107
-rw-r--r--tests/testutils.py10
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]) == []