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-08-31 05:14:27 (GMT)
committer Benjamin Saller <bcsaller@objectrealms.net>2007-08-31 05:14:27 (GMT)
commit48aad0795a4bf8110a7ac48707be3bdc7743597c (patch)
tree2e1466adca17d38c8b6a042743d84d9fe8c79fca
parent57e90f572b41d40f9601c50e2d7e63bc47a6279a (diff)
wip on tagging
-rw-r--r--src/olpc/datastore/backingstore.py2
-rw-r--r--src/olpc/datastore/datastore.py33
-rw-r--r--src/olpc/datastore/hg_backingstore.py26
-rw-r--r--src/olpc/datastore/model.py11
-rw-r--r--src/olpc/datastore/xapianindex.py74
-rw-r--r--tests/xapianindex.txt5
6 files changed, 146 insertions, 5 deletions
diff --git a/src/olpc/datastore/backingstore.py b/src/olpc/datastore/backingstore.py
index 0d04b4a..dcb989e 100644
--- a/src/olpc/datastore/backingstore.py
+++ b/src/olpc/datastore/backingstore.py
@@ -52,7 +52,7 @@ class BackingStore(object):
capabilities = ()
def __init__(self, uri, **kwargs):
- """The kwargs are used to configure the backend so it can
+ """The kwargs are used to configure the backend so it can
provide its interface. See specific backends for details
"""
pass
diff --git a/src/olpc/datastore/datastore.py b/src/olpc/datastore/datastore.py
index 0a37f49..d197eda 100644
--- a/src/olpc/datastore/datastore.py
+++ b/src/olpc/datastore/datastore.py
@@ -317,6 +317,39 @@ class DataStore(dbus.service.Object):
return mp.checkout(uid, vid, target=target, dir=dir)
@dbus.service.method(DS_DBUS_INTERFACE,
+ in_signature="sss",
+ out_signature=""
+ )
+ def tags(self, uid, tags, rev=None):
+ """ apply tags to a version of a document, there are cases
+ where some tags apply to all versions of a document and others
+ where they apply to a specific revision.
+
+ By default tags apply to all revisions but if they end with :0
+ then they are demarked as being version specific
+
+ for example
+
+ >>> ds.tags(uid, "foo bar")
+
+ would mark all instances of uid with the tags foo and bar
+
+ while
+
+ >>> ds.tags(uid, "foo bar:0")
+
+ would mark all instances of this uid with "foo" and this
+ specific instance with "bar". No version changes would be
+ created to any versions using this call.
+
+ In its current implementation even tags that apply to all
+ revisions only apply to revision on a particular store.
+ """
+ c = self.get(uid)
+ c.backingstore.tags(uid, tags)
+
+
+ @dbus.service.method(DS_DBUS_INTERFACE,
in_signature='ssss',
out_signature='a{sv}s')
def copy(self, uid, vid=None, mountpoint=None, target_mountpoint=None):
diff --git a/src/olpc/datastore/hg_backingstore.py b/src/olpc/datastore/hg_backingstore.py
index 7a4d1ba..dad8987 100644
--- a/src/olpc/datastore/hg_backingstore.py
+++ b/src/olpc/datastore/hg_backingstore.py
@@ -315,6 +315,32 @@ class HgBackingStore(FileBackingStore):
return open(target, 'rw')
+ def tags(self, uid, tags):
+ """ apply tags to a version of a document, there are cases
+ where some tags apply to all versions of a document and others
+ where they apply to a specific revision.
+
+ By default tags apply to all revisions but if they end with :0
+ then they are demarked as being version specific
+
+ for example
+
+ >>> ds.tags(uid, "foo bar")
+
+ would mark all instances of uid with the tags foo and bar
+
+ while
+
+ >>> ds.tags(uid, "foo bar:0")
+
+ would mark all instances of this uid with "foo" and this
+ specific instance with "bar". No version changes would be
+ created to any versions using this call.
+ """
+ pass
+
+
+
def checkin(self, props, filelike):
"""create or update the content object, creating a new
version"""
diff --git a/src/olpc/datastore/model.py b/src/olpc/datastore/model.py
index 1e13a13..f17e842 100644
--- a/src/olpc/datastore/model.py
+++ b/src/olpc/datastore/model.py
@@ -349,6 +349,10 @@ registerPropertyType('text', noop, noop, 'string', {'store' : True,
'collapse' : True,
})
+registerPropertyType('tag', noop, str.lower, 'string', {'store' : True,
+ 'exact' : True,
+ })
+
# Now the convention is to directly use DBus.ByteArray
registerPropertyType('binary', str, dbus.ByteArray, None, {'store' : True,
'exact' : False,
@@ -397,9 +401,10 @@ defaultModel = Model().addFields(
('ctime', 'date'),
('mtime', 'date'),
# this will just be a space delimited list of tags
- # indexed with the content
- # I give them high weight as they have user given semantic value.
- ('tags', 'text', {'weight' :3 } ),
+ # tags are case-insensitive and managed via the tags() API call
+ # while they can be set as normal properties with checkin that
+ # will create new versions and the tags() call will not.
+ ('tags', 'tag'),
# olpc specific
('activity', 'string'),
diff --git a/src/olpc/datastore/xapianindex.py b/src/olpc/datastore/xapianindex.py
index ceb7d56..d4128d4 100644
--- a/src/olpc/datastore/xapianindex.py
+++ b/src/olpc/datastore/xapianindex.py
@@ -331,8 +331,80 @@ class IndexManager(object):
if self.backingstore:
return "inplace" in self.backingstore.capabilities
return False
+
+ def _parse_tags(self, tags):
+ # convert tags into (TAG, rev) pairs indicating if this tag
+ # applies to this rev or all revs
+ # all revs is ('tag', False)
+ # the specific rev (unknown in this function is ('tag', True)
+ t = tags.lower().split()
+ r = []
+ for tag in t:
+ all = True
+ mode = ADD
+ if tag.startswith("-"):
+ tag = tag[1:]
+ mode = REMOVE
+
+ if tag[-2:] == ":0":
+ tag = tag[:-2]
+
+ r.append((tag, all, mode))
+
+ return r
-
+ def tag(self, uid, tags, rev=None):
+ # this can't create items so we either resolve the uid (which
+ # should be a given since we got to this layer) or fail
+ results, count = self.get_by_uid_prop(uid, rev)
+ if count == 0:
+ raise KeyError('unable to apply tags to uid %s' % uid)
+
+ # pull the whole version chain
+ results = list(results)
+
+ tags = self._parse_tags(tags)
+
+ for tag, all, mode in tags:
+ if all:
+ used = results
+ else:
+ # select the revision indicated by rev
+ # when None is provided this will be the tip
+ pass
+
+ if not tag and mode is REMOVE:
+ # special case the '-' which removes all tags
+ for c in used:
+ if 'tags' in c._doc.data:
+ del c._doc.data['tags']
+ else:
+ # not a global remove so we need to look at each
+ # document, each tag and handle them case by case
+ for c in used:
+ # we need to manipulate the field list of the existing
+ # docs and then replace them in the database
+ # to avoid adding new versions
+
+ # XXX: this should really be model driven and support
+ # any field that is of the tags type...
+ existing = set(c.get_property('tags', []))
+ if tag in existing and mode is REMOVE:
+ existing.remove(tag)
+ else:
+ existing.add(tag)
+
+ # XXX: low level interface busting
+ # replace the current tags with the updated set
+ c._doc.data['tags'] = list(existing)
+
+
+ # Sync version, (enque with update for async)
+ with self._write_lock:
+ for c in results:
+ self.write_index.replace_document(c)
+
+
def index(self, props, filename=None):
"""Index the content of an object.
Props must contain the following:
diff --git a/tests/xapianindex.txt b/tests/xapianindex.txt
index b28ac90..583f589 100644
--- a/tests/xapianindex.txt
+++ b/tests/xapianindex.txt
@@ -86,6 +86,11 @@ Partial search...
+We also support tagging of documents.
+
+>>> im.tag(uid, "foo bar")
+>>> assert expect_single(im.search('tags:foo')).id == uid
+
Cleanly shut down.
>>> im.stop()