Mercurial > danboorufs
view danboorufs.py @ 1:63ccd8b0d615 draft
Add tag exclusion support.
author | Emmanuel Gil Peyrot <linkmauve@linkmauve.fr> |
---|---|
date | Thu, 02 Aug 2012 12:19:31 +0200 |
parents | 215d51f2a82f |
children | 85cbd44f98b1 |
line wrap: on
line source
#!/usr/bin/env python2 # -*- encoding: utf-8 -*- from __future__ import with_statement from errno import ENOENT, ENOTDIR 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') 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) real_path = path[1:].split('/') # Remove the leading - of tag exclusion. path = [tag[1:] if tag[0] == '-' else tag for tag in real_path] if filter(lambda tag: tag not in self.tags, path[:-1]): raise FuseOSError(ENOENT) if path[-1] in self.tags: return (real_path, None) if path[-1] not in self.paths: raise FuseOSError(ENOENT) return (real_path[:-1], self.paths[real_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] inclusion_tags = set(tag for tag in tags if tag[0] != '-') exclusion_tags = set(tag[1:] for tag in tags if tag[0] == '-') # Get the list of the files corresponding to those tags. files = reduce((lambda s, t: s.intersection(self.tags[t])), inclusion_tags, set(self.files)) files -= set([f for f in files if exclusion_tags.intersection(self.files[f])]) # Those next two steps are for useless tags removal. # 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)