h5fs.py
author Alain Leufroy <alain.leufroy@logilab.fr>
Tue, 02 Apr 2013 12:21:13 +0200
changeset 14 f4791bdd245a
parent 0 03d0140deb6c
child 2 3164ba33cbc3
permissions -rw-r--r--
refactorization - separate fs specific code to hdf5 specific codes - user better names - use generators instead of list - move functions to class method as they should be overwriten for future implementations

#!/usr/bin/env python

# Copyright (C) 2012  Alain Leufroy <alain.leufroy@logilab.fr>
#
# This program can be distributed under the terms of the GNU LGPL.
# See the file COPYING.
#

'''
HDF5 filesystem implementation.
'''

import os
import os.path as osp
import stat
import errno
from itertools import imap, chain
from functools import partial

import h5py

import fuse
fuse.fuse_python_api = (0, 2)

__version__ = (0, 0, 1)


class H5IOError(IOError):
    """Raised when a bad hdf5 entry was given"""
    # OSError and IOError are converted to errno by fuse.ErrnoWrapper

class Stat(fuse.FuseStruct): # pylint: disable-msg=R0903
    "Fill up stat attributes with user acces"
    st_mode  = None
    st_ino   = 0
    st_dev   = 0
    st_nlink = None
    st_uid   = os.getuid()
    st_gid   = os.getgid()
    st_size  = 0
    st_atime = 0
    st_mtime = 0
    st_ctime = 0


class GroupStat(Stat): # pylint: disable-msg=R0903
    "Fill up stat attributes with user acces for Groups"
    st_mode = stat.S_IFDIR | stat.S_IREAD | stat.S_IEXEC
    st_nlink = 1


class DatasetStat(Stat): # pylint: disable-msg=R0903
    "Fill up stat attributes with user acces for Groups"
    st_mode = stat.S_IFREG | stat.S_IREAD
    st_nlink = 1


# TODO: h5file.id.get_comment() ...
class FsMixin(object):
    '''Implement the HDF5 filesytem interface'''

    h5file = None

    def get_entry(self, path):
        '''Return the entry recorded at ``path`` or raise H5IOError'''
        try:
            h5entry = self.h5file[path]
        except KeyError:
            raise H5IOError(errno.ENOENT, 'No such Dataset or Group', path)
        return h5entry

    @staticmethod
    def get_stat(h5entry):
        '''Return the entry stat information (an instance of Stat)'''
        if isinstance(h5entry, h5py.highlevel.Group):
            return GroupStat()
        if isinstance(h5entry, h5py.highlevel.Dataset):
            return DatasetStat()
        raise H5IOError(errno.ENOENT, 'Unknown entry type', h5entry.name)

    @staticmethod
    def get_name(h5entry):
        '''Return the entry file name as string'''
        # XXX unicode seems not to be allowed
        return str(osp.basename(h5entry.name))

    def list_group(self, group, specs=('.', '..')):
        '''A fonction generator that yields name of the entries in the group.
        The list is in arbritary order with specials entries ``specs``.'''
        return chain(iter(specs), imap(self.get_name, group.itervalues()))

    def readdir(self, path, offset):
        '''Return a generator that yields entries from the given Group path'''
        convert = partial(fuse.Direntry, offset=offset)
        return imap(convert, self.list_group(self.get_entry(path)))

    def getattr(self, path):
        '''Return the stat attributes of the given ``path``'''
        return self.get_stat(self.get_entry(path))


def __main__():
    '''Main entry point that start the HDF5 filesystem server'''
    usage = "\nUserspace HDF5 file explorer.\n\n%prog sourcefile mountpoint"
    class H5FS(FsMixin, fuse.Fuse):
        '''merge Fuse + HDF5 interface'''
        pass
    server = H5FS(version="%proog " + fuse.__version__,
                  usage=usage,
                  dash_s_do='setsingle',
                  standard_mods=True,
                  fetch_mp=True)
    server.parse(errex=1)
    _opts, ags = server.cmdline
    h5file = h5py.File(ags.pop(), 'r')
    try:
        server.h5file = h5file
        server.main()
    finally:
        h5file.close()

if __name__ == '__main__':
    __main__()