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 20:38:40 (GMT)
committer Sascha Silbe <sascha-pgp@silbe.org>2010-06-01 20:38:40 (GMT)
commit89fb3d7b4dbf8fea7b6f958cb0ff8a2d17f07a53 (patch)
tree09e0b9d277daeb113d95d152a072678e2bd87863
parente6ac5a4ccb8946f5438f35fdf68b8a7a32d759c7 (diff)
fix partially working version
-rwxr-xr-x[-rw-r--r--]datastore-fuse.py344
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__':