#!/bin/env python
import sys, os, os.path, stat, errno, locale

import fuse
fuse.fuse_python_api = (0, 2)

import sqlalchemy as sa
from tagit.db import open_db, with_session, Session, File, Tag, Attr

def split(path):
    return [a for a in path.split(os.path.sep) if a]


# Filesystem
class Tagfs(fuse.Fuse):
    def __init__(self):
        super(Tagfs, self).__init__()
        self.file_class = TagfsFile
        
        # I'm not sure _why_ this is necessary, but without it the mount breaks
        # completely.
        self.multithreaded = False
        
        # Initialize database
        open_db('fs.sqlite')
    
    @with_session
    def getattr(session, self, path):
        print "*** getattr", path
        parts = split(path)
        
        st = fuse.Stat()
        st.st_uid = os.getuid()
        st.st_gid = os.getgid()
        
        if parts and parts[-1] == 'files':
            parts.pop()
            st.st_mode = (stat.S_IFREG | stat.S_IRUSR)
            st.st_size = len(TagfsFile(path, 0).contents) + 1
            st.st_size = 0
            st.st_nlink = 1
        else:
            st.st_mode = (stat.S_IFDIR | stat.S_IRUSR | stat.S_IXUSR)
            # As far as I can tell, st_nlink for directories is a mere
            # optimization, and setting it to 1 is always safe.
            st.st_nlink = 1
        
        # Check that given tags exist
        tags = [tag.name for tag in 
                session.query(Tag).filter(
                Tag.name.in_(parts)).order_by(Tag.name)]
        
        if tags != sorted(parts): 
            return -errno.ENOENT
        
        return st
    
    @with_session
    def readdir(session, self, path, offset):
        print "*** getdir", path
        tags = split(path)
        
        # Find tags other than the existing ones
        query = session.query(Tag).filter(sa.not_(Tag.name.in_(tags)))
        
        # Find only tags which share files with all present tags
        for tag in tags:
            query = query.filter(Tag.files.any(File.tags.any(name = tag)))
        
        # TODO: Find only tags which have a file in common with this tag set
        return ([fuse.Direntry("files", type=stat.S_IFREG)] +
                [fuse.Direntry(str(tag.name), type=stat.S_IFDIR)
                 for tag in query])
    
    @with_session
    def mkdir(session, self, path, mode):
        # Adds a tag to the database
        name = split(path)[-1]
        
        if session.query(Tag).get(name): return -errno.EEXIST
        
        session.add(Tag(name))
        session.commit()
    
    @with_session
    def rmdir(session, self, path):
        print "rmdir", path
        
        # Removes a tag
        name = split(path)[-1]
        
        tag = session.query(Tag).get(name)
        if not tag: return -errno.ENOENT
        
        session.delete(tag)
        session.commit()
    


# Files
class TagfsFile(object):
    
    # Note: For some reason, fuse apparently occasionally tries to access a
    # ".direct_io" property. Possibly relatedly, cat will occasionally fail with
    # "Invalid argument".
    
    @with_session
    def __init__(session, self, path, mode):
        print "*** TagfsFile(%s, %s)" % (path, mode)
        self.path = path
        self.mode = mode
        self.tags = path.split(os.path.sep)[1:-1]
        
        q = session.query(File)
        for tag in self.tags:
            q = q.filter(File.tags.any(name=tag))
        
        self.contents = ''.join(f.path+'\n' for f in q).encode(
            locale.getpreferredencoding())
    
    def read(self, size, offset):
        print "*** read %s, %s, %s" % (self.path, size, offset)
        return self.contents[offset:offset+size]


# Program runner itself
if __name__ == "__main__":
    fs = Tagfs()
    fs.main(sys.argv)
