From ada4ccb107ca6cee061402624afb5fb9fc15e803 Mon Sep 17 00:00:00 2001 From: Sascha Silbe Date: Thu, 03 Jun 2010 13:40:04 +0000 Subject: Put files in / and symlinks in /by-id, rather than the other way round. FUSE doesn't like files getting replaced by symlinks upon creation, so we need to put the "files" in the place where the user can create new entries. --- diff --git a/datastore-fuse.py b/datastore-fuse.py index 4638f5f..1b45664 100755 --- a/datastore-fuse.py +++ b/datastore-fuse.py @@ -32,14 +32,14 @@ DS_DBUS_PATH = "/org/laptop/sugar/DataStore" class DataStoreObjectStat(fuse.Stat): # pylint: disable-msg=R0902,R0903 - def __init__(self, parent, metadata, size): + def __init__(self, filesystem, metadata, size): fuse.Stat.__init__(self, st_mode=stat.S_IFREG | 0750, st_nlink=1, 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 self.metadata = metadata - self.parent = parent + self._filesystem = filesystem self.object_id = metadata['uid'] def _parse_time(self, timestamp): @@ -49,10 +49,10 @@ class DataStoreObjectStat(fuse.Stat): return 0 def should_truncate(self): - return self.parent.should_truncate(self.object_id) + return self._filesystem.should_truncate(self.object_id) def reset_truncate(self): - return self.parent.reset_truncate(self.object_id) + return self._filesystem.reset_truncate(self.object_id) class Symlink(fuse.Stat): @@ -105,16 +105,11 @@ class Directory(fuse.Stat): def remove(self, name_): raise IOError(errno.EACCES, os.strerror(errno.EACCES)) - def truncate(self, name_): - raise IOError(errno.EACCES, os.strerror(errno.EACCES)) - class ByTitleDirectory(Directory): def __init__(self, filesystem, root): self.root = root Directory.__init__(self, filesystem, 0750) - self._object_id_to_title_name = {} - self._title_name_to_object_id = {} def readdir(self, offset): Directory.readdir(self, offset) @@ -122,83 +117,42 @@ class ByTitleDirectory(Directory): for entry in self._filesystem.find({}, {'metadata': ['title', 'uid', 'timestamp']}): - name = self._object_id_to_title_name.get(entry['uid']) - if not name: - name = self._generate_title_name(entry) - self._add_title_name(name, entry) - + name = self._filesystem.lookup_title_name(entry['uid']) yield fuse.Direntry(name) + def getxattr(self, name, attribute): + object_id = self._filesystem.resolve_title_name(name) + metadata = self._filesystem.get_metadata(object_id) + if attribute in metadata: + return metadata[attribute] + + Directory.getxattr(self, object_id, attribute) + + def listxattr(self, name): + object_id = self._filesystem.resolve_title_name(name) + metadata = self._filesystem.get_metadata(object_id) + return [str(name) for name in metadata.keys()] + def lookup(self, name): - object_id = self._resolve_title_name(name) - return Symlink(self._filesystem, 'by-id/' + str(object_id)) + 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) def mknod(self, name): - if name in self._title_name_to_object_id: + if self._filesystem.try_resolve_title_name(name): raise IOError(errno.EEXIST, os.strerror(errno.EEXIST)) - metadata = {'title': name} - object_id = self._filesystem.create_new(metadata, '') - metadata['uid'] = object_id - self._add_title_name(name, metadata) + object_id = self._filesystem.create_new(name, '') def remove(self, name): - if name in ['.', '..']: - raise IOError(errno.EACCES, os.strerror(errno.EACCES)) - - object_id = self._resolve_title_name(name) - self.root.by_id_directory.remove(object_id) - self._remove_title_name_by_object_id(object_id) - - def _resolve_title_name(self, name): - try: - return self._title_name_to_object_id[name] - - except KeyError: - raise IOError(errno.ENOENT, os.strerror(errno.ENOENT)) - - def _add_title_name(self, name, metadata): - self._object_id_to_title_name[metadata['uid']] = name - self._title_name_to_object_id[name] = metadata['uid'] - return name - - @trace() - def _generate_title_name(self, metadata): - title = metadata.get('title') - try: - mtime = float(metadata['timestamp']) - except (KeyError, ValueError): - mtime = time.time() - - time_human = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(mtime)) - name = '%s - %s' % (title, time_human) - name = self._safe_name(name) - current_name = name - counter = 1 - while current_name in self._title_name_to_object_id: - counter += 1 - current_name = '%s %d' % (name, counter) - - return current_name - - def _remove_title_name_by_object_id(self, object_id): - name = self._object_id_to_title_name.pop(object_id, None) - if name: - del self._title_name_to_object_id[name] - - def _remove_title_name_by_name(self, name): - object_id = self._title_name_to_object_id.pop(name, None) - if object_id: - del self._object_id_to_title_name[object_id] - - def _safe_name(self, name): - return name.replace('/', '_') + object_id = self._filesystem.resolve_title_name(name) + self._filesystem.remove_entry(object_id) class ByIdDirectory(Directory): def __init__(self, filesystem): Directory.__init__(self, filesystem, 0550) - self._truncate_object_ids = set() def getxattr(self, object_id, attribute): metadata = self._filesystem.get_metadata(object_id) @@ -212,9 +166,8 @@ class ByIdDirectory(Directory): return [str(name) for name in metadata.keys()] def lookup(self, object_id): - metadata = self._filesystem.get_metadata(object_id) - size = self._get_size(object_id) - return DataStoreObjectStat(self, metadata, size) + name = self._filesystem.lookup_title_name(object_id) + return Symlink(self._filesystem, '../' + name) def readdir(self, offset): Directory.readdir(self, offset) @@ -225,25 +178,6 @@ class ByIdDirectory(Directory): def remove(self, object_id): self._filesystem.remove_entry(object_id) - def truncate(self, object_id): - self._truncate_object_ids.add(object_id) - - def should_truncate(self, object_id): - return object_id in self._truncate_object_ids - - def reset_truncate(self, object_id): - self._truncate_object_ids.discard(object_id) - - def _get_size(self, object_id): - file_name = self._filesystem.get_data(object_id) - if not file_name: - return 0 - - try: - return os.stat(file_name).st_size - finally: - os.remove(file_name) - # TODO class ByTagsDirectory(Directory): @@ -390,8 +324,8 @@ class DataStoreFile(object): @trace() def fgetattr(self): -# return os.fstat(self._file.fileno()) - return self._filesystem.getattr(self._path) + return os.fstat(self._file.fileno()) +# return self._filesystem.getattr(self._path) @trace() def ftruncate(self, length): @@ -411,6 +345,9 @@ class DataStoreFS(fuse.Fuse): DataStoreFile.__init__(self_file, self_fs, *args, **kwargs) self_fs.file_class = WrappedDataStoreFile + self_fs._truncate_object_ids = set() + self_fs._object_id_to_title_name = {} + self_fs._title_name_to_object_id = {} fuse.Fuse.__init__(self_fs, *args, **kw) @@ -435,20 +372,24 @@ class DataStoreFS(fuse.Fuse): directory = self.getattr(directory_name) return getattr(directory, action)(file_name, *args) - def readdir(self, path, offset): + def readdir(self, path, offset=None): return self.getattr(path).readdir(offset) def readlink(self, path): return self._delegate(path, 'readlink') - def mknod(self, path, mode_, dev_): + def mknod(self, path, mode_=None, dev_=None): # called by FUSE for open(O_CREAT) before instantiating the file return self._delegate(path, 'mknod') def truncate(self, path, mode_=None, dev_=None): # Documented to be called by FUSE when opening files with O_TRUNC, # unless -o o_trunc_atomic is passed as a CLI option - self._delegate(path, 'truncate') + entry = self.getattr(path) + if isinstance(entry, Directory): + raise IOError(errno.EISDIR, os.strerror(errno.EISDIR)) + + self._truncate_object_ids.add(entry.object_id) def unlink(self, path): self._delegate(path, 'remove') @@ -507,6 +448,12 @@ class DataStoreFS(fuse.Fuse): return attribute_names + def should_truncate(self, object_id): + return object_id in self._truncate_object_ids + + def reset_truncate(self, object_id): + self._truncate_object_ids.discard(object_id) + def find(self, metadata, options): mess = metadata.copy() mess.update(options) @@ -520,23 +467,99 @@ class DataStoreFS(fuse.Fuse): return self._data_store.get_properties(object_id, timeout=-1, byte_arrays=True) - def create_new(self, metadata, path): - return self._data_store.create(metadata, path, False, timeout=-1, + def create_new(self, name, path): + metadata = {'title': name} + object_id = self._data_store.create(metadata, path, False, timeout=-1, byte_arrays=True) + self._add_title_name(name, object_id) - def remove_data(self, object_id): - return self._data_store.delete(object_id) + def remove_entry(self, object_id): + self._data_store.delete(object_id) + self._remove_title_name_by_object_id(object_id) + self._truncate_object_ids.discard(object_id) def get_data(self, object_id): return self._data_store.get_filename(object_id, timeout=-1, byte_arrays=True) + def get_data_size(self, object_id): + file_name = self.get_data(object_id) + if not file_name: + return 0 + + try: + return os.stat(file_name).st_size + finally: + os.remove(file_name) + @trace() def write_data(self, object_id, file_name): metadata = self.get_metadata(object_id) return self._data_store.update(object_id, metadata, file_name, False, timeout=-1, byte_arrays=True) + def resolve_title_name(self, name): + if name not in self._title_name_to_object_id: + # FIXME: Hack to fill self._title_name_to_object_id. To be + # replaced by parsing the name and doing a specific search. + list(self.readdir('/')) + + try: + return self._title_name_to_object_id[name] + + except KeyError: + raise IOError(errno.ENOENT, os.strerror(errno.ENOENT)) + + def try_resolve_title_name(self, name): + return self._title_name_to_object_id.get(name) + + def lookup_title_name(self, object_id): + name = self._object_id_to_title_name.get(object_id) + if name: + return name + + metadata = self.get_metadata(object_id) + name = self._generate_title_name(metadata) + self._add_title_name(name, object_id) + return name + + def _add_title_name(self, name, object_id): + self._object_id_to_title_name[object_id] = name + self._title_name_to_object_id[name] = object_id + return name + + @trace() + def _generate_title_name(self, metadata): + title = metadata.get('title') + try: + mtime = float(metadata['timestamp']) + except (KeyError, ValueError): + mtime = time.time() + + time_human = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(mtime)) + name = '%s - %s' % (title, time_human) + name = self._safe_name(name) + current_name = name + counter = 1 + while current_name in self._title_name_to_object_id: + counter += 1 + current_name = '%s %d' % (name, counter) + + return current_name + + def _remove_title_name_by_object_id(self, object_id): + name = self._object_id_to_title_name.pop(object_id, None) + if name: + del self._title_name_to_object_id[name] + + def _remove_title_name_by_name(self, name): + object_id = self._title_name_to_object_id.pop(name, None) + if object_id: + del self._object_id_to_title_name[object_id] + + def _safe_name(self, name): + return name.replace('/', '_') + def main(): usage = __doc__ + fuse.Fuse.fusage -- cgit v0.9.1