diff options
author | Sascha Silbe <sascha-pgp@silbe.org> | 2010-06-01 20:38:40 (GMT) |
---|---|---|
committer | Sascha Silbe <sascha-pgp@silbe.org> | 2010-06-01 20:38:40 (GMT) |
commit | 89fb3d7b4dbf8fea7b6f958cb0ff8a2d17f07a53 (patch) | |
tree | 09e0b9d277daeb113d95d152a072678e2bd87863 | |
parent | e6ac5a4ccb8946f5438f35fdf68b8a7a32d759c7 (diff) |
fix partially working version
-rwxr-xr-x[-rw-r--r--] | datastore-fuse.py | 344 |
1 files changed, 221 insertions, 123 deletions
diff --git a/datastore-fuse.py b/datastore-fuse.py index 88c1a1d..19eed32 100644..100755 --- a/datastore-fuse.py +++ b/datastore-fuse.py @@ -5,15 +5,20 @@ Mounting this "file system" allows "legacy" applications to access the Sugar data store. """ -#import stat import errno import fuse +import logging import os +import shutil +import stat import tempfile import time import dbus +from sugar.logger import trace +import sugar.logger + fuse.fuse_python_api = (0, 2) @@ -24,18 +29,14 @@ DS_DBUS_PATH = "/org/laptop/sugar/DataStore" class DataStoreObjectStat(fuse.Stat): - def __init__(self, metadata, size): - self.st_mode = stat.S_IFREG || 0755 - self.st_ino = 0 - self.st_dev = 0 - self.st_nlink = 1 - # FIXME: use FUSE context - self.st_uid = os.getuid() - self.st_gid = os.getgid() - self.st_size = size - self.st_mtime = self._parse_time(metadata.get('timestamp', '')) - self.st_ctime = self.st_mtime - self.st_atime = self.st_mtime + + # pylint: disable-msg=R0902,R0903 + def __init__(self, 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 def _parse_time(self, timestamp): try: @@ -44,101 +45,128 @@ class DataStoreObjectStat(fuse.Stat): return 0 -def DirectoryStat(fuse.Stat): +class DirectoryStat(fuse.Stat): + + # pylint: disable-msg=R0902,R0903 def __init__(self, mode): - self.st_mode = stat.S_IFDIR || mode - self.st_ino = 0 - self.st_dev = 0 - self.st_nlink = 2 - # FIXME: use FUSE context - self.st_uid = os.getuid() - self.st_gid = os.getgid() - self.st_size = 4096 - self.st_mtime = time.time() - self.st_ctime = self.st_mtime - self.st_atime = self.st_mtime + fuse.Stat.__init__(self, st_mode = stat.S_IFDIR | mode, st_nlink = 2, + st_uid = os.getuid(), st_gid = os.getgid(), st_size = 4096, + st_mtime = time.time()) + self.st_ctime = self.st_mtime + self.st_atime = self.st_mtime class DataStoreFile(object): - + _MODE_MASK = os.O_RDONLY | os.O_RDWR | os.O_WRONLY - def __init__(self, fs, path, flags, mode=None): - self._filesystem = fs + def __init__(self, filesystem, path, flags, mode_=None): + self._filesystem = filesystem self._path = path - if flags & self._MODE_MASK == os.O_WRONLY: - self._read_only = False + self._flags = flags + self._read_only = False + self._is_temporary = False + if flags & self._MODE_MASK == os.O_RDONLY: + self._read_only = True + self._file = self._checkout(path, False) + + elif flags & os.O_EXCL: + self._filesystem.create_new(path) self._file = self._create() - elif flags & self._MODE_MASK == os.O_RDWR: - self._read_only = False - self._file = self._checkout(path) + else: - self._read_only = True - self._file = self._checkout(path) + self._file = self._checkout(path, flags & os.O_CREAT) + + if flags & os.O_TRUNC: + self.ftruncate(0) def _create(self): - return tempfile.NamedTemporaryFile() # TODO dir/prefix + self._is_temporary = True + return tempfile.NamedTemporaryFile() # TODO dir/prefix - def _checkout(self, path): - return self._fs._checkout(path, not self._read_only) + def _checkout(self, path, allow_create): + name = self._filesystem.checkout(path, allow_create) + if not name: + # existing, but empty entry + return self._create() + + if self._read_only: + return file(name) + + try: + copy = self._create() + shutil.copyfileobj(file(name), copy) + copy.seek(0) + return copy + + finally: + os.remove(name) def read(self, length, offset): self._file.seek(offset) return self._file.read(length) - + def write(self, buf, offset): - self._file.seek(offset) + if self._flags & os.O_APPEND: + self._file.seek(0, os.SEEK_END) + else: + self._file.seek(offset) + self._file.write(buf) return len(buf) - - def release(self, flags): + + def release(self, flags_): self.fsync(False) self._file.close() - if not isinstance(self._file, NamedTemporaryFile): + if not self._is_temporary: os.remove(self._file.name) - - def fsync(self, isfsyncfile): + + def fsync(self, isfsyncfile_): self._file.flush() if not self._read_only: - self._fs._save(self._path, self._file.name) - + self._filesystem.save(self._path, self._file.name) + def flush(self): self._file.flush() - + def fgetattr(self): - return os.fstat(self._file) - - def ftruncate(self, len): - self._file.truncate(len) + return os.fstat(self._file.fileno()) - def lock(self, cmd, owner, **kwargs): + def ftruncate(self, length): + self._file.truncate(length) + + def lock(self, cmd_, owner_, **kwargs_): raise IOError(errno.EACCES, os.strerror(errno.EACCES)) - + class DataStoreFS(fuse.Fuse): - + def __init__(self_fs, *args, **kw): + # pylint: disable-msg=E0213 class WrappedDataStoreFile(DataStoreFile): def __init__(self_file, *args, **kwargs): - DataStoreFile(self_file, self_fs, *args, **kwargs) + # pylint: disable-msg=E0213 + DataStoreFile.__init__(self_file, self_fs, *args, **kwargs) self_fs.file_class = WrappedDataStoreFile fuse.Fuse.__init__(self_fs, *args, **kw) bus = dbus.SessionBus() - self_fs._data_store = dbus.Interface(_bus.get_object(DS_DBUS_SERVICE, + self_fs._data_store = dbus.Interface(bus.get_object(DS_DBUS_SERVICE, DS_DBUS_PATH), DS_DBUS_INTERFACE) - self._uid_to_title_name = {} - self._title_name_to_uid = {} + self_fs._uid_to_title_name = {} + self_fs._title_name_to_uid = {} # TODO: listen to DS signals to update name mapping # TODO: factor out name mapping code into separate class def getattr(self, path): return self._distribute(path, 'getattr') - def _distribute(self, path, operation, args=None): + @trace() + def _distribute(self, path, operation, *args): components = path.lstrip('/').split('/') + logging.debug('components=%r', components) if not components[0]: path_name = 'root' parameters = [] @@ -146,7 +174,7 @@ class DataStoreFS(fuse.Fuse): elif components[0] == 'by-tags': path_name = 'by_tags' parameters = [components[1:]] - + elif components[0] == 'by-id': path_name = 'by_id' if len(components) > 2: @@ -155,20 +183,22 @@ class DataStoreFS(fuse.Fuse): parameters = [components[1]] else: parameters = [None] - + elif operation == 'readdir': self._throw_error(errno.ENOTDIR) - + elif len(components) > 1: self._throw_error(errno.ENOTDIR) - + else: path_name = 'by_title' - parameters = path[0] or None - + parameters = [components[0] or None] + if args: - parameters = args + parameters + parameters += list(args) + logging.debug('parameters=%r', parameters) + # pylint: disable-msg=W0142 return getattr(self, '_%s_%s' % (operation, path_name))(*parameters) def _getattr_root(self): @@ -182,16 +212,17 @@ class DataStoreFS(fuse.Fuse): self._throw_error(errno.ENOENT) def _getattr_by_title(self, title): - self._getattr_by_id(self._resolve_title_name(title)) + return self._getattr_by_id(self._resolve_title_name(title)) def _getattr_by_id(self, object_id): if not object_id: return DirectoryStat(0550) entry = self._get_metadata(object_id) - return DataStoreObjectStat(entry) - - def readdir(self, path, offset): + size = self._get_size(object_id) + return DataStoreObjectStat(entry, size) + + def readdir(self, path, offset_): return self._distribute(path, 'readdir') def _readdir_root(self): @@ -199,11 +230,11 @@ class DataStoreFS(fuse.Fuse): # data store entries by title for name in ['.', '..', 'by-id', 'by-tags']: yield fuse.Direntry(name) - - for entry in self._find({}, {'metadata': ['title']}): + + for entry in self._find({}, {'metadata': ['title', 'uid']}): name = self._uid_to_title_name.get(entry['uid']) if not name: - name = self._generate_title_name(metadata) + name = self._generate_title_name(entry) self._add_title_name(name, entry) yield fuse.Direntry(name) @@ -211,24 +242,24 @@ class DataStoreFS(fuse.Fuse): def _readdir_by_id(self, object_id): if object_id: self._throw_error(errno.ENOTDIR) - + for entry in self._find({}, {'metadata': ['uid']}): yield fuse.Direntry(entry['uid']) - - def _readdir_by_tags(self, tags): + + def _readdir_by_tags(self, tags_): # TODO return - def mknod(self, path, mode, dev): - return self._distribute(path, 'mknod') + def mknod(self, path, mode_, dev_): + return self._distribute(path, 'mknod') def _mknod_root(self): self._throw_error(errno.EEXIST) - + def _mknod_by_id(self, object_id): - if not uid: + if not object_id: self._throw_error(errno.EEXIST) - + self._throw_error(errno.EACCES) def _mknod_by_tags(self, tags): @@ -237,18 +268,20 @@ class DataStoreFS(fuse.Fuse): self._throw_error(errno.EACCES) - def _mknod_by_title(self, title): + def _mknod_by_title(self, title_): #object_id_ = self._create({'title': title}, '') # let's try out whether we really need mknod() support... self._throw_error(errno.EACCES) - def truncate(self, path, mode, dev): - self._throw_error(errno.EACCES) + @trace() + def truncate(self, path_, mode_=None, dev_=None): + # FIXME: apparently needed for all text editors :-/ + self._throw_error(errno.EACCES) def unlink(self, path): return self._distribute(path, 'unlink') - def _unlink_by_tags(self, tags): + def _unlink_by_tags(self, tags_): self._throw_error(errno.EACCES) def _unlink_by_id(self, object_id): @@ -258,62 +291,70 @@ class DataStoreFS(fuse.Fuse): def _unlink_by_title(self, title): return self._unlink_by_id(self._resolve_title_name(title)) - def utime(self, path, times): + @trace() + def utime(self, path_, times_): + # TODO: update timestamp property return - def mkdir(self, path, mode): + def mkdir(self, path, mode_): self._distribute(path, 'mkdir') - + def _mkdir_root(self): self._throw_error(errno.EEXIST) - + def _mkdir_by_id(self, object_id): if object_id: self._throw_error(errno.EEXIST) - + self._throw_error(errno.EACCES) - - def _mkdir_by_title(self, title): + + def _mkdir_by_title(self, title_): self._throw_error(errno.EACCES) - - def _mkdir_by_tags(self, tags): + + def _mkdir_by_tags(self, tags_): # TODO self._throw_error(errno.EACCES) - def rmdir(self, path): + @trace() + def rmdir(self, path_): self._throw_error(errno.EACCES) def rename(self, pathfrom, pathto): return self._distribute(pathfrom, 'rename', pathto) - def _rename_root(self, destination): + def _rename_root(self, destination_): self._throw_error(errno.EACCES) - - def _rename_by_id(self, destination, object_id): + + def _rename_by_id(self, destination_, object_id_): self._throw_error(errno.EACCES) - - def _rename_by_title(self, destination, title): + + def _rename_by_title(self, destination_, title_): # TODO self._throw_error(errno.EACCES) - - def _rename_by_tags(self, destination, tags): + + def _rename_by_tags(self, destination_, tags_): # TODO self._throw_error(errno.EACCES) - - def fsync(self, path, isfsyncfile): + + @trace() + def fsync(self, path_, isfsyncfile_): return - def symlink(self, destination, path): + @trace() + def symlink(self, destination_, path_): # TODO for tags? self._throw_error(errno.EACCES) - def link(self, destination, path): + @trace() + def link(self, destination_, path_): self._throw_error(errno.EPERM) - def chmod(self, path, mode): + @trace() + def chmod(self, path_, mode_): self._throw_error(errno.EACCES) - def chown(self, path, user, group): + @trace() + def chown(self, path_, user_, group_): self._throw_error(errno.EACCES) # TODO @@ -333,7 +374,7 @@ class DataStoreFS(fuse.Fuse): # return len("".join(aa)) + len(aa) # return aa - def _checkout(self, path, allow_create): + def checkout(self, path, allow_create): return self._distribute(path, 'checkout', allow_create) def _checkout_root(self, allow_create): @@ -342,19 +383,59 @@ class DataStoreFS(fuse.Fuse): def _checkout_by_id(self, object_id, allow_create): return self._get_data(object_id) - def _checkout_by_tags(self, tags, allow_create): + def _checkout_by_tags(self, tags_, allow_create): self._throw_error(errno.ENOENT) def _checkout_by_title(self, title, allow_create): return self._get_data(self._resolve_title_name(title, allow_create)) + def create_new(self, path): + return self._distribute(path, 'create_new') + + def _create_new_root(self): + self._throw_error(errno.EISDIR) + + def _create_new_by_id(self, object_id): + self._throw_error(errno.EACCES) + + def _create_new_by_tags(self, tags_): + # TODO? + self._throw_error(errno.EACCES) + + def _create_new_by_title(self, title): + object_id = self._title_name_to_uid.get(title) + if object_id: + self._throw_error(errno.EEXIST) + + metadata = {'title': title} + object_id = self._create(metadata, '') + metadata['uid'] = object_id + return self._add_title_name(title, metadata) + + def save(self, path, file_name): + return self._distribute(path, 'save', file_name) + + def _save_root(self, path_, file_name_): + self._throw_error(errno.EISDIR) + + def _save_by_id(self, object_id, file_name): + self._write_data(object_id, file_name) + + def _save_by_tags(self, tags_, file_name_): + self._throw_error(errno.EACCES) + + def _save_by_title(self, title, file_name): + self._write_data(self._resolve_title_name(title), file_name) + + @trace() def _find(self, metadata, options): mess = metadata.copy() mess.update(options) properties = mess.pop('metadata', []) - - return self._data_store.find(mess, properties, timeout=-1, - byte_arrays=True)[1] + logging.debug('mess=%r, properties=%r', mess, properties) + + return self._data_store.find(mess, properties, timeout=-1, + byte_arrays=True)[0] def _get_metadata(self, object_id): return self._data_store.get_properties(object_id, timeout=-1, @@ -371,22 +452,38 @@ class DataStoreFS(fuse.Fuse): return self._data_store.get_filename(object_id, timeout=-1, byte_arrays=True) + 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 _get_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 _resolve_title_name(self, title, allow_create=False): object_id = self._title_name_to_uid.get(title) if object_id: return object_id - + if not allow_create: self._throw_error(errno.ENOENT) - + metadata = {'title': title} object_id = self._create(metadata, '') metadata['uid'] = object_id return self._add_title_name(title, metadata) def _add_title_name(self, name, metadata): - self._uid_to_title_name[entry['uid']] = name - self._title_name_to_uid[name] = entry['uid'] + self._uid_to_title_name[metadata['uid']] = name + self._title_name_to_uid[name] = metadata['uid'] return name def _generate_title_name(self, metadata): @@ -400,7 +497,7 @@ class DataStoreFS(fuse.Fuse): while current_name in self._title_name_to_uid: counter += 1 current_name = '%s %d' % (name, counter) - + return current_name def _remove_title_name_by_uid(self, uid): @@ -416,16 +513,17 @@ class DataStoreFS(fuse.Fuse): def _safe_name(self, name): return name.replace('/', '_') - def _throw_error(self, nr): - raise IOError(nr, os.strerror(nr)) + def _throw_error(self, number): + raise IOError(number, os.strerror(number)) def main(): - usage= __doc__ + fuse.Fuse.fusage + usage = __doc__ + fuse.Fuse.fusage - server = DataStoreFS(version="%prog " + fuse.__version__, usage=usage, + server = DataStoreFS(version="%prog " + fuse.__version__, usage=usage, dash_s_do='setsingle') server.parse(errex=1) + sugar.logger.start() server.main() if __name__ == '__main__': |