From 48aad0795a4bf8110a7ac48707be3bdc7743597c Mon Sep 17 00:00:00 2001 From: Benjamin Saller Date: Fri, 31 Aug 2007 05:14:27 +0000 Subject: wip on tagging --- 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() -- cgit v0.9.1