diff options
author | Sascha Silbe <sascha-pgp@silbe.org> | 2010-06-03 21:30:29 (GMT) |
---|---|---|
committer | Sascha Silbe <sascha-pgp@silbe.org> | 2010-06-03 21:30:29 (GMT) |
commit | 4c8c00702c4c52dd8a5161a60c226c92da004ab3 (patch) | |
tree | 723c4040a597e43f427d10093766b23c80b73ec0 | |
parent | b23d2cddda0e1a80d94983ee36dbcba4034d8cf3 (diff) |
implement ByTagsDirectory, including setting hard link count and inode number
-rwxr-xr-x | datastore-fuse.py | 179 |
1 files changed, 147 insertions, 32 deletions
diff --git a/datastore-fuse.py b/datastore-fuse.py index de0f215..f1086d7 100755 --- a/datastore-fuse.py +++ b/datastore-fuse.py @@ -26,6 +26,7 @@ import os import os.path import shutil import stat +import sys import tempfile import time @@ -50,12 +51,15 @@ XATTR_REPLACE = 2 class DataStoreObjectStat(fuse.Stat): # pylint: disable-msg=R0902,R0903 - def __init__(self, filesystem, metadata, size): - fuse.Stat.__init__(self, st_mode=stat.S_IFREG | 0750, st_nlink=1, + def __init__(self, filesystem, metadata, size, inode): + fuse.Stat.__init__(self, st_mode=stat.S_IFREG | 0750, st_ino=inode, st_uid=os.getuid(), st_gid=os.getgid(), st_size=size, st_mtime=self._parse_time(metadata.get('timestamp', ''))) self.st_ctime = self.st_mtime self.st_atime = self.st_mtime + tags = [tag for tag in metadata.get('tags', '').split() + if tag and '/' not in tag] + self.st_nlink = len(tags) + 1 self.metadata = metadata self._filesystem = filesystem self.object_id = metadata['uid'] @@ -74,24 +78,26 @@ class DataStoreObjectStat(fuse.Stat): class Symlink(fuse.Stat): - def __init__(self, filesystem, target): + def __init__(self, filesystem, target, inode_nr): self._filesystem = filesystem self.target = target fuse.Stat.__init__(self, st_mode=stat.S_IFLNK | 0777, st_nlink=1, - st_uid=os.getuid(), st_gid=os.getgid(), + st_uid=os.getuid(), st_gid=os.getgid(), st_ino=inode_nr, st_mtime=time.time()) self.st_ctime = self.st_mtime self.st_atime = self.st_mtime class Directory(fuse.Stat): - def __init__(self, filesystem, mode): + def __init__(self, path, parent_path, filesystem, mode): + self._path = path self._filesystem = filesystem fuse.Stat.__init__(self, st_mode=stat.S_IFDIR | mode, st_nlink=2, st_uid=os.getuid(), st_gid=os.getgid(), st_mtime=time.time()) self.st_ctime = self.st_mtime self.st_atime = self.st_mtime + self.st_ino = filesystem.get_inode_number(path) def getxattr(self, name_, attribute_): # on Linux ENOATTR=ENODATA (Python errno doesn't contain ENOATTR) @@ -110,8 +116,10 @@ class Directory(fuse.Stat): raise IOError(errno.EACCES, os.strerror(errno.EACCES)) def readdir(self, offset_): - for name in ['.', '..']: - yield fuse.Direntry(name) + yield fuse.Direntry('.', + self._filesystem.get_inode_number(self._path)) + yield fuse.Direntry('.', + self._filesystem.get_inode_number(self._parent_path)) def readlink(self, name): entry = self.lookup(name) @@ -124,22 +132,30 @@ class Directory(fuse.Stat): raise IOError(errno.EACCES, os.strerror(errno.EACCES)) def setxattr(self, name_, attribute_, value_, flags_): - raise IOError(errno.ENOTSUP, os.strerror(errno.ENOTSUP)) + # On Linux ENOTSUP = EOPNOTSUPP + raise IOError(errno.EOPNOTSUPP, os.strerror(errno.EOPNOTSUPP)) class ByTitleDirectory(Directory): - def __init__(self, filesystem, root): - self.root = root - Directory.__init__(self, filesystem, 0750) + def __init__(self, path, parent_path, filesystem): + Directory.__init__(self, path, parent_path, filesystem, 0750) def readdir(self, offset): Directory.readdir(self, offset) - for entry in self._filesystem.find({}, - {'metadata': ['title', 'uid', 'timestamp']}): + for entry in self._find_entries(): + if 'uid' not in entry: + # corrupted entry + continue name = self._filesystem.lookup_title_name(entry['uid']) - yield fuse.Direntry(name) + yield fuse.Direntry(name, + ino=self._filesystem.get_inode_number(entry['uid'])) + + @trace() + def _find_entries(self): + return self._filesystem.find({}, + {'metadata': ['title', 'uid', 'timestamp']}) def getxattr(self, name, attribute): object_id = self._filesystem.resolve_title_name(name) @@ -158,7 +174,8 @@ class ByTitleDirectory(Directory): object_id = self._filesystem.resolve_title_name(name) metadata = self._filesystem.get_metadata(object_id) size = self._filesystem.get_data_size(object_id) - return DataStoreObjectStat(self._filesystem, metadata, size) + return DataStoreObjectStat(self._filesystem, metadata, size, + self._filesystem.get_inode_number(object_id)) def mknod(self, name): if self._filesystem.try_resolve_title_name(name): @@ -185,8 +202,8 @@ class ByTitleDirectory(Directory): class ByIdDirectory(Directory): - def __init__(self, filesystem): - Directory.__init__(self, filesystem, 0550) + def __init__(self, path, parent_path, filesystem): + Directory.__init__(self, path, parent_path, filesystem, 0550) def getxattr(self, object_id, attribute): metadata = self._filesystem.get_metadata(object_id) @@ -201,37 +218,117 @@ class ByIdDirectory(Directory): def lookup(self, object_id): name = self._filesystem.lookup_title_name(object_id) - return Symlink(self._filesystem, '../' + name) + path = '%s/%s' % (self._path, object_id) + return Symlink(self._filesystem, '../' + name, + self._filesystem.get_inode_number(path)) def readdir(self, offset): Directory.readdir(self, offset) for entry in self._filesystem.find({}, {'metadata': ['uid']}): - yield fuse.Direntry(entry['uid']) + if 'uid' not in entry: + # corrupted entry + continue + + yield fuse.Direntry(entry['uid'], + ino=self._filesystem.get_inode_number(entry['uid'])) def remove(self, object_id): self._filesystem.remove_entry(object_id) -# TODO +class ByTagsSubDirectory(ByTitleDirectory): + def __init__(self, path, parent_path, filesystem, tags): + self._tags = frozenset(tags) + ByTitleDirectory.__init__(self, path, parent_path, filesystem) + + def mknod(self, name): + if self._filesystem.try_resolve_title_name(name): + raise IOError(errno.EEXIST, os.strerror(errno.EEXIST)) + + object_id = self._filesystem.create_new(name, '', self._tags) + + @trace() + def _find_entries(self): + # The current data store doesn't support searching within the tags + # property, so we need to do an unspecific full text search and + # filter out any extra matches. + query = {'query': ' '.join(self._tags)} + for entry in self._filesystem.find(query, + {'metadata': ['uid', 'tags']}): + + entry_tags = frozenset(entry.get('tags', '').split()) + if self._tags - entry_tags: + continue + + yield entry + + class ByTagsDirectory(Directory): - def __init__(self, filesystem): - Directory.__init__(self, filesystem, 0550) + def __init__(self, path, parent_path, filesystem): + Directory.__init__(self, path, parent_path, filesystem, 0550) + self._tag_dirs = {} + + def readdir(self, offset): + Directory.readdir(self, offset) + + for tag in self._get_tags(): + if '/' in tag: + continue + + path = '%s/%s' % (self._path, tag) + yield fuse.Direntry(tag, + ino=self._filesystem.get_inode_number(path)) + + def lookup(self, tag): + if tag not in self._tag_dirs: + if not self._check_tag(tag): + raise IOError(errno.ENOENT, os.strerror(errno.ENOENT)) + + path = '%s/%s' % (self._path, tag) + self._tag_dirs[tag] = ByTagsSubDirectory(path, self._path, + self._filesystem, [tag]) + + return self._tag_dirs[tag] + + @trace() + def _check_tag(self, tag): + # The current data store doesn't support searching within the tags + # property, so we need to do an unspecific full text search and + # filter out any extra matches. + query = {'query': tag} + for entry in self._filesystem.find(query, {'metadata': ['tags']}): + if tag in entry.get('tags', '').split(): + return True + + return False + + @trace() + def _get_tags(self): + tags = set() + # The current data store doesn't support get_uniquevaluesfor('tags'). + for entry in self._filesystem.find({}, {'metadata': ['tags']}): + logging.debug('entry=%r', entry) + tags.update(entry.get('tags', '').split()) + + tags.discard('') + return tags class RootDirectory(ByTitleDirectory): def __init__(self, filesystem): - ByTitleDirectory.__init__(self, filesystem, self) - self.by_id_directory = ByIdDirectory(filesystem) - self.by_tags_directory = ByTagsDirectory(filesystem) + ByTitleDirectory.__init__(self, '/', '/', filesystem) + self.by_id_directory = ByIdDirectory('/by-id', '/', filesystem) + self.by_tags_directory = ByTagsDirectory('/by-tags', '/', filesystem) self.by_title_directory = self def readdir(self, offset_): for name in ['by-id', 'by-tags']: - yield fuse.Direntry(name) + yield fuse.Direntry(name, + ino=self._filesystem.get_inode_number('/' + name)) - for name in ByTitleDirectory.readdir(self, offset_): - yield name + for entry in ByTitleDirectory.readdir(self, offset_): + yield entry def lookup(self, name): if name == 'by-id': @@ -379,6 +476,8 @@ class DataStoreFS(fuse.Fuse): self_fs._truncate_object_ids = set() self_fs._object_id_to_title_name = {} self_fs._title_name_to_object_id = {} + self_fs._max_inode_number = 1 + self_fs._object_id_to_inode_number = {} fuse.Fuse.__init__(self_fs, *args, **kw) @@ -492,10 +591,12 @@ class DataStoreFS(fuse.Fuse): def reset_truncate(self, object_id): self._truncate_object_ids.discard(object_id) + @trace() def find(self, metadata, options): mess = metadata.copy() mess.update(options) properties = mess.pop('metadata', []) + logging.debug('mess=%r, properties=%r', mess, properties) return self._data_store.find(mess, properties, timeout=-1, byte_arrays=True)[0] @@ -506,13 +607,16 @@ class DataStoreFS(fuse.Fuse): except Exception, exception: raise IOError(errno.ENOENT, str(exception)) - def create_new(self, name, path): + def create_new(self, name, path, tags=None): base_name = os.path.splitext(name)[0] metadata = {'title': base_name} mime_type = sugar.mime.get_from_file_name(name) if mime_type: metadata['mime_type'] = mime_type + if tags: + metadata['tags'] = ' '.join(tags) + object_id = self._data_store.create(metadata, path, False, timeout=-1, byte_arrays=True) self._add_title_name(name, object_id) @@ -599,7 +703,7 @@ class DataStoreFS(fuse.Fuse): time_human = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(mtime)) name = '%s - %s' % (title, time_human) - name = self._safe_name(name) + name = safe_name(name) extension = self._guess_extension(metadata.get('mime_type'), object_id) if extension: current_name = '%s.%s' % (name, extension) @@ -625,8 +729,13 @@ class DataStoreFS(fuse.Fuse): if object_id: del self._object_id_to_title_name[object_id] - def _safe_name(self, name): - return name.replace('/', '_') + def get_inode_number(self, key): + if key not in self._object_id_to_inode_number: + inode_number = self._max_inode_number + self._max_inode_number += 1 + self._object_id_to_inode_number[key] = inode_number + + return self._object_id_to_inode_number[key] def _guess_extension(self, mime_type, object_id): extension = None @@ -645,9 +754,15 @@ class DataStoreFS(fuse.Fuse): return extension +def safe_name(name): + return name.replace('/', '_') + + def main(): usage = __doc__ + fuse.Fuse.fusage + # FIXME: figure out how to force options to on, properly. + sys.argv += ['-o', 'use_ino'] server = DataStoreFS(version="%prog " + fuse.__version__, usage=usage, dash_s_do='setsingle') server.parse(errex=1) |