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>2010-06-03 21:30:29 (GMT)
committer Sascha Silbe <sascha-pgp@silbe.org>2010-06-03 21:30:29 (GMT)
commit4c8c00702c4c52dd8a5161a60c226c92da004ab3 (patch)
tree723c4040a597e43f427d10093766b23c80b73ec0
parentb23d2cddda0e1a80d94983ee36dbcba4034d8cf3 (diff)
implement ByTagsDirectory, including setting hard link count and inode number
-rwxr-xr-xdatastore-fuse.py179
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)