diff options
author | Sascha Silbe <sascha-pgp@silbe.org> | 2010-06-01 13:29:35 (GMT) |
---|---|---|
committer | Sascha Silbe <sascha-pgp@silbe.org> | 2010-06-01 13:29:35 (GMT) |
commit | f3ee9f0aa7c84636c9dd9486fd51778ba02f3e5f (patch) | |
tree | 240f947f71e597be64be0b319efd02681023532c |
WIP
-rw-r--r-- | datastore-fuse.py | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/datastore-fuse.py b/datastore-fuse.py new file mode 100644 index 0000000..77b203d --- /dev/null +++ b/datastore-fuse.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python +"""datastore-fuse: access the Sugar data store using FUSE + +Mounting this "file system" allows "legacy" applications to access the Sugar +data store. +""" + +#import stat +import errno +import fuse +#from time import time +#from subprocess import * +import os +import dbus + + +fuse.fuse_python_api = (0, 2) + + +DS_DBUS_SERVICE = "org.laptop.sugar.DataStore" +DS_DBUS_INTERFACE = "org.laptop.sugar.DataStore" +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 + + def _parse_time(self, timestamp): + try: + return int(timestamp, 10) + except ValueError: + return 0 + + +def DirectoryStat(fuse.Stat): + 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 + + +class DataStoreFile(object): + + def __init__(self, path, flags, mode=0640): + self._path = path + # TODO: check flags + # TODO: open file + + def read(self, length, offset): + self._file.seek(offset) + return self._file.read(length) + + def write(self, buf, offset): + # TODO: check flags? + self._file.seek(offset) + self._file.write(buf) + return len(buf) + + def release(self, flags): + self._file.close() + # TODO: submit to data store / destroy checked out copy + + def fsync(self, isfsyncfile): + self._file.flush() + # TODO: submit to data store + + def flush(self): + return + + def fgetattr(self): + # return fuse.Stat + + def ftruncate(self, len): + self._file.truncate(len) + + def lock(self, cmd, owner, **kwargs): + raise IOError(errno.EACCES, os.strerror(errno.EACCES)) + + +class DataStoreFS(fuse.Fuse): + + file_class = DataStoreFile + + def __init__(self, *args, **kw): + fuse.Fuse.__init__(self, *args, **kw) + + bus = dbus.SessionBus() + self._data_store = dbus.Interface(_bus.get_object(DS_DBUS_SERVICE, + DS_DBUS_PATH), DS_DBUS_INTERFACE) + + def getattr(self, path): + return self._distribute(path, 'getattr') + + def _distribute(self, path, operation, args=None): + components = path.lstrip('/').split('/') + if not components[0]: + if operation == 'readdir': + path_name = 'by_title' + else: + path_name = 'root' + + parameters = [] + + 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: + self._throw_error(errno.ENOENT) + elif len(components) == 2: + 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 + + if args: + parameters = args + parameters + + return getattr(self, '_%s_%s' % (operation, path_name))(*parameters) + + def _getattr_root(self): + return DirectoryStat(0750) + + def _getattr_by_tags(self, tags): + if not tags: + return DirectoryStat(0550) + + # TODO + self._throw_error(errno.ENOENT) + + def _getattr_by_title(self, title): + # TODO +# entries = self._find({'title': title}, {'limit': 1, 'metadata': ['uid', 'title']}) + self._throw_error(errno.ENOENT) + + 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): + return self._distribute(path, 'readdir') + + def _readdir_by_title(self): + # special case: root directory contains by-* subdirectories and + # data store entries by title + for name in ['.', '..', 'by-id', 'by-tags']: + yield fuse.Direntry(name) + + for entry in self._find({}, {'metadata': ['title']}): + # FIXME: not unique + name = entry['title'] + yield fuse.Direntry(name) + + 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): + # TODO + return + + 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: + self._throw_error(errno.EEXIST) + + self._throw_error(errno.EACCES) + + def _mknod_by_tags(self, tags): + if not tags: + self._throw_error(errno.EEXIST) + + self._throw_error(errno.EACCES) + + 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) + + def unlink(self, path): + return self._distribute(path, 'unlink') + + def _unlink_by_tags(self, tags): + self._throw_error(errno.EACCES) + + def _unlink_by_id(self, object_id): + self._remove(uid) + + def _unlink_by_title(self, title): + # TODO + self._throw_error(errno.EACCES) + + def utime(self, path, times): + return + + 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): + self._throw_error(errno.EACCES) + + def _mkdir_by_tags(self, tags): + # TODO + self._throw_error(errno.EACCES) + + 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): + self._throw_error(errno.EACCES) + + def _rename_by_id(self, destination, object_id): + self._throw_error(errno.EACCES) + + def _rename_by_title(self, destination, title): + # TODO + self._throw_error(errno.EACCES) + + def _rename_by_tags(self, destination, tags): + # TODO + self._throw_error(errno.EACCES) + + def fsync(self, path, isfsyncfile): + return + + def symlink(self, destination, path): + # TODO for tags? + self._throw_error(errno.EACCES) + + def link(self, destination, path): + self._throw_error(errno.EPERM) + + def chmod(self, path, mode): + self._throw_error(errno.EACCES) + + def chown(self, path, user, group): + self._throw_error(errno.EACCES) + +# TODO +# def getxattr(self, path, name, size): +# val = name.swapcase() + '@' + path +# if size == 0: +# # We are asked for size of the value. +# return len(val) +# return val +# +# def listxattr(self, path, size): +# # We use the "user" namespace to please XFS utils +# aa = ["user." + a for a in ("foo", "bar")] +# if size == 0: +# # We are asked for size of the attr list, ie. joint size of attrs +# # plus null separators. +# return len("".join(aa)) + len(aa) +# return aa + + def _find(self, metadata, options): + mess = metadata.copy() + mess.update(options) + if 'metadata' in options: + properties = options['metadata'] + del mess['metadata'] + else: + properties = [] + + return self._data_store.find(mess, properties, timeout=-1, + byte_arrays=True)[1] + + def _get_metadata(self, object_id): + return self._data_store.get_properties(object_id, timeout=-1, + byte_arrays=True) + + def _create(self, metadata, path): + return self._data_store.create(metadata, path, False, timeout=-1, + byte_arrays=True) + + def _remove(self, object_id): + return self._data_store.delete(object_id) + + def _throw_error(self, nr): + raise IOError(nr, os.strerror(nr)) + + +def main(): + usage= __doc__ + fuse.Fuse.fusage + + server = DataStoreFS(version="%prog " + fuse.__version__, usage=usage, + dash_s_do='setsingle') + server.parse(errex=1) + server.main() + +if __name__ == '__main__': + main() |