Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSascha Silbe <sascha-pgp@silbe.org>2012-01-05 22:15:57 (GMT)
committer Sascha Silbe <sascha-pgp@silbe.org>2012-01-05 22:15:57 (GMT)
commit4e66b46002673e648dce40ea294c1fe410d6ed94 (patch)
tree7de8153a59cbbd8328a630c75d7147ff65f2c839
parent3bfb2d72e10b6c5fd8bcabc951f409775c4353f9 (diff)
fsemulation: add support for by-tags
Add (resp. bring back, in the case of datastore-fuse) support for listing data store entries by tag (still just a single tag, not recursive).
-rw-r--r--fsemulation.py146
1 files changed, 138 insertions, 8 deletions
diff --git a/fsemulation.py b/fsemulation.py
index 2c38449..5f35bf7 100644
--- a/fsemulation.py
+++ b/fsemulation.py
@@ -34,7 +34,7 @@ DS_DBUS_PATH2 = '/org/laptop/sugar/DataStore2'
DBUS_TIMEOUT_MAX = 2 ** 31 / 1000
DBUS_PYTHON_VALUE_ERROR = 'org.freedesktop.DBus.Python.ValueError'
-_FILE_NAME_PROPS = ['mime_type', 'timestamp', 'title']
+_USEFUL_PROPS = ['mime_type', 'tags', 'timestamp', 'title']
"""Metadata properties used for determining the file name of an entry"""
@@ -149,7 +149,7 @@ class DataStore(object):
"""
query = query or {}
- properties = list(_FILE_NAME_PROPS)
+ properties = list(_USEFUL_PROPS)
if self._data_store.dbus_interface == DS_DBUS_INTERFACE2:
properties += ['parent_id', 'tree_id', 'version_id']
options = {'metadata': properties,
@@ -184,6 +184,26 @@ class DataStore(object):
"""Retrieve the tree_ids of all (matching) data store entries"""
return [unicode(entry[0]) for entry in self.list_object_ids(query)]
+ def list_property_values(self, name, query=None):
+ """Return all unique values of the given property"""
+ assert isinstance(name, unicode)
+
+ query = query or {}
+ if self._data_store.dbus_interface == DS_DBUS_INTERFACE2:
+ options = {'metadata': [name], 'all_versions': True}
+ entries = self._data_store.find(query, options,
+ timeout=DBUS_TIMEOUT_MAX,
+ byte_arrays=True)[0]
+ else:
+ # We can't use get_uniquevaluesfor() as sugar-datastore
+ # only supports it for activity_id, which is not what
+ # we need.
+ entries = self._data_store.find(query, [name],
+ timeout=DBUS_TIMEOUT_MAX,
+ byte_arrays=True)[0]
+
+ return dict.fromkeys([entry.get(name) for entry in entries]).keys()
+
def check_object_id(self, object_id):
"""Return True if the given object_id identifies a data store entry"""
try:
@@ -203,6 +223,27 @@ class DataStore(object):
byte_arrays=True)[0]
return bool(results)
+ def check_property_contains(self, name, word):
+ """Return True if there is at least one entry containing word in the
+ given property
+ """
+ assert isinstance(name, unicode)
+ assert isinstance(word, unicode)
+
+ query_string = u'%s:"%s"' % (name, word.replace(u'"', u''))
+ if self._data_store.dbus_interface == DS_DBUS_INTERFACE2:
+ options = {'limit': 1}
+ results = self._data_store.text_search({}, query_string, options,
+ timeout=DBUS_TIMEOUT_MAX,
+ byte_arrays=True)[0]
+ else:
+ query = {'query': query_string, 'limit': 1}
+ results = self._data_store.find(query, [name],
+ timeout=DBUS_TIMEOUT_MAX,
+ byte_arrays=True)[0]
+
+ return bool(results)
+
def get_properties(self, object_id, names=None):
"""Read given properties for data store entry identified by object_id
@@ -699,10 +740,99 @@ class ByTreeIdDirectory(Directory):
self, tree_id))
+class ByTagsSubDirectory(ByTitleDirectory):
+ def __init__(self, file_system, level, parent, tags):
+ self._tags = frozenset(tags)
+ ByTitleDirectory.__init__(self, file_system, level, parent)
+
+ def mknod(self, name):
+ if self._fs.try_resolve_title_name(name):
+ raise IOError(errno.EEXIST, os.strerror(errno.EEXIST))
+
+ props = {'title': name, 'tags': ' '.join(self._tags)}
+ object_id_ = self._ds.create_new(props)
+
+ def listdir(self):
+ for name in Directory.listdir(self):
+ yield name
+
+ for object_id, metadata in self._find_entries():
+ name = self._fs.lookup_title_name(object_id, metadata)
+ yield name
+
+ def readdir(self):
+ for name, entry in Directory.readdir(self):
+ yield name, entry
+
+ for object_id, metadata in self._find_entries():
+ name = self._fs.lookup_title_name(object_id, metadata)
+ yield (name, self._get_symlink(object_id))
+
+ def _find_entries(self):
+ query = {'query': ' '.join(self._tags)}
+ for object_id, props in self._ds.list_metadata(query):
+ entry_tags = frozenset(props.get('tags', '').split())
+ if self._tags - entry_tags:
+ continue
+
+ yield object_id, props
+
+
+class ByTagsDirectory(Directory):
+ def __init__(self, file_system, level, parent):
+ Directory.__init__(self, file_system, level, 0550, parent)
+ self._tag_dirs = {}
+
+ def listdir(self):
+ for name in Directory.listdir(self):
+ yield name
+
+ for tag in self._list_tags():
+ if u'/' in tag or tag.startswith(u'.'):
+ continue
+
+ yield tag
+
+ def readdir(self):
+ for name, entry in Directory.readdir(self):
+ yield name, entry
+
+ for tag in self._list_tags():
+ if u'/' in tag or tag.startswith(u'.'):
+ continue
+
+ if tag not in self._tag_dirs:
+ self._tag_dirs[tag] = ByTagsSubDirectory(self._fs,
+ self._level + 1,
+ self, [tag])
+ yield (tag, self._tag_dirs[tag])
+
+ def lookup(self, name):
+ if name not in self._tag_dirs:
+ if not self._check_tag(name):
+ raise IOError(errno.ENOENT, os.strerror(errno.ENOENT))
+
+ self._tag_dirs[name] = ByTagsSubDirectory(self._fs,
+ self._level + 1, self,
+ [name])
+ return self._tag_dirs[name]
+
+ def _check_tag(self, name):
+ return self._ds.check_property_contains(u'tags', name)
+
+ def _list_tags(self):
+ tags = set()
+ for value in self._ds.list_property_values(u'tags'):
+ tags.update((value or u'').split())
+
+ tags.discard(u'')
+ return tags
+
+
class RootDirectory(Directory):
def __init__(self, file_system, mode):
Directory.__init__(self, file_system, 0, mode, None)
- #self._by_tags_directory = ByTagsDirectory(u'/by-tags', u'/', filesystem)
+ self._by_tags_directory = ByTagsDirectory(file_system, 1, self)
self._by_title_directory = ByTitleDirectory(file_system, 1, self)
if self._ds.supports_versions:
self._by_id_directory = ByTreeIdDirectory(file_system, 1, self)
@@ -714,7 +844,7 @@ class RootDirectory(Directory):
yield name
yield u'by-id'
- #yield u'by-tags'
+ yield u'by-tags'
yield u'by-title'
def readdir(self):
@@ -722,14 +852,14 @@ class RootDirectory(Directory):
yield name, entry
yield (u'by-id', self._by_id_directory)
- #yield (u'by-tags', self._by_tags_directory)
+ yield (u'by-tags', self._by_tags_directory)
yield (u'by-title', self._by_title_directory)
def lookup(self, name):
if name == u'by-id':
return self._by_id_directory
-# elif name == u'by-tags':
-# return self._by_tags_directory
+ elif name == u'by-tags':
+ return self._by_tags_directory
elif name == u'by-title':
return self._by_title_directory
@@ -799,7 +929,7 @@ class FSEmulation(object):
if metadata is None:
metadata = self.data_store.get_properties(object_id,
- _FILE_NAME_PROPS)
+ _USEFUL_PROPS)
name = self._generate_title_name(metadata, object_id)
self._add_title_name(name, object_id)