# HG changeset patch # User Emmanuel Gil Peyrot # Date 1341400921 -7200 # Node ID 215d51f2a82fece9ae5c9cb08c0cd4d155d0070b Initial commit. diff --git a/danboorufs.py b/danboorufs.py new file mode 100755 --- /dev/null +++ b/danboorufs.py @@ -0,0 +1,142 @@ +#!/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 [...] ' % argv[0]) + exit(1) + + mountpoint = argv.pop() + + fuse = FUSE(Danbooru(argv[1:], os.path.dirname(mountpoint)), mountpoint, foreground=True)