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-01 13:29:35 (GMT)
committer Sascha Silbe <sascha-pgp@silbe.org>2010-06-01 13:29:35 (GMT)
commitf3ee9f0aa7c84636c9dd9486fd51778ba02f3e5f (patch)
tree240f947f71e597be64be0b319efd02681023532c
WIP
-rw-r--r--datastore-fuse.py348
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()