view danboorufs.py @ 0:215d51f2a82f draft

Initial commit.
author Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
date Wed, 04 Jul 2012 13:22:01 +0200
parents
children 63ccd8b0d615
line wrap: on
line source

#!/usr/bin/env python2
# -*- encoding: utf-8 -*-

from __future__ import with_statement

from errno import ENOENT, ENOTDIR
from os.path import realpath
from sys import argv, exit
from threading import Lock
from time import time

import os

from fuse import FUSE, FuseOSError, Operations, LoggingMixIn


class Danbooru(LoggingMixIn, Operations):
    '''
    Represent a list of images as a filesystem tree, with nice tag filtering.
    '''

    def __init__(self, tagfiles, root):
        '''
        Takes a list of files containing the tags. They have to be named as the
        image, with ".tags" at the end.
        '''
        self.paths = {}
        self.files = {}
        self.tags = {}
        self.cache = {}

        start = time()

        for name in tagfiles:
            filename = name.replace('.tags', '')
            basename = os.path.basename(filename)
            self.paths[basename] = filename
            tags = []
            self.files[basename] = tags
            with open(name, 'r') as file:
                for line in file:
                    for tag in line.split():
                        tag = tag.decode('UTF-8')
                        if '/' in tag:
                            tag = tag.replace('/', u'�') #XXX
                        tags.append(tag)
                        self.tags.setdefault(tag, []).append(basename)

        print('[%d] Index done.' % (time() - start))

        self.root = root
        self.rwlock = Lock()

    def _split_path(self, path):
        if path == '/':
            return (None, None)

        path = path[1:].split('/')
        if filter(lambda tag: tag not in self.tags, path[:-1]):
            raise FuseOSError(ENOENT)

        if path[-1] in self.tags:
            return (path, None)

        if path[-1] not in self.paths:
            raise FuseOSError(ENOENT)

        return (path[:-1], self.paths[path[-1]])

    def access(self, path, mode):
        self._split_path(path)

    def getattr(self, path, fh=None):
        tags, file = self._split_path(path)
        path = file if file else self.root
        st = os.lstat(path)
        return dict((key, getattr(st, key)) for key in ('st_atime', 'st_ctime',
            'st_gid', 'st_mode', 'st_mtime', 'st_nlink', 'st_size', 'st_uid'))

    getxattr = None
    listxattr = None
    def open(self, path, flags):
        tags, file = self._split_path(path)
        return os.open(file, flags)

    def read(self, path, size, offset, fh):
        with self.rwlock:
            os.lseek(fh, offset, 0)
            return os.read(fh, size)

    def readdir(self, path, fh):
        if path == '/':
            return ['.', '..'] + self.tags.keys() + self.files.keys()

        tags, file = self._split_path(path)
        if file:
            return FuseOSError(ENOTDIR)

        tags = set(tags)

        l = ' '.join(tags)
        if l in self.cache:
            return ['.', '..'] + self.cache[l]

        # Get the list of the files corresponding to those tags.
        files = reduce((lambda s, t: s.intersection(self.tags[t])), tags, set(self.files))

        # Get the tags of those files.
        taglist = reduce((lambda s, f: s.union(self.files[f])), files, set())
        taglist -= tags

        # Remove the tags that can’t precise the file list anymore.
        rm = reduce((lambda s, f: s.intersection(self.files[f])), files, taglist)
        taglist -= rm

        self.cache[l] = list(taglist) + list(files)
        return ['.', '..'] + self.cache[l]

    readlink = os.readlink

    def release(self, path, fh):
        return os.close(fh)

    def statfs(self, path):
        tags, file = self._split_path(path)
        path = file if file else self.root
        stv = os.statvfs(path)
        return dict((key, getattr(stv, key)) for key in ('f_bavail', 'f_bfree',
            'f_blocks', 'f_bsize', 'f_favail', 'f_ffree', 'f_files', 'f_flag',
            'f_frsize', 'f_namemax'))

    utimens = os.utime


if __name__ == '__main__':
    if len(argv) < 3:
        print('usage: %s <tag file> [<tag file>...] <mountpoint>' % argv[0])
        exit(1)

    mountpoint = argv.pop()

    fuse = FUSE(Danbooru(argv[1:], os.path.dirname(mountpoint)), mountpoint, foreground=True)