#!/usr/bin/python

# Copyright (c) 2008-2009 Pavol Rusnak <prusnak@suse.cz>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:

# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.

import sys
import os
try:
    import osc
    import osc.conf
    import osc.core
except:
    # allow loading module from working copy if osc is not installed
    sys.path.append(os.path.abspath(os.path.dirname(sys.argv[0]) + '/../osc'))
    import osc
    import osc.conf
    import osc.core
import fuse
import stat
import errno
import tempfile

fuse.fuse_python_api = (0, 2)

projects = []
cache = {}

class EmptyStat(fuse.Stat):
    def __init__(self):
        self.st_mode = 0
        self.st_ino = 0
        self.st_dev = 0
        self.st_nlink = 0
        self.st_uid = 0
        self.st_gid = 0
        self.st_size = 0
        self.st_atime = 0
        self.st_mtime = 0
        self.st_ctime = 0

class CacheEntry(object):
    def __init__(self):
        self.stat = None
        self.handle = None
        self.tmpname = None

class oscFS(fuse.Fuse):

    def __init__(self, *args, **kw):
        fuse.Fuse.__init__(self, *args, **kw)
        print 'OK'

    def getattr(self, path):
        st = EmptyStat()
        # path is project
        if path == '/' or path in projects or len(filter(lambda x: x.startswith(path), projects)) > 0:
            st.st_mode = stat.S_IFDIR | 0555
            st.st_nlink = 2
            return st
        # path is package
        if os.path.dirname(path) in projects:
            st.st_mode = stat.S_IFDIR | 0555
            st.st_nlink = 2
            return st
        # path is file
        if cache.has_key(path):
            return cache[path].stat
        else:
            return -errno.ENOENT

    def readdir(self, path, offset):
        yield fuse.Direntry('.')
        yield fuse.Direntry('..')

        if os.path.dirname(path) in projects: # path is package
            prj = os.path.dirname(path).replace('/','')
            pkg = os.path.basename(path)
            for f in osc.core.meta_get_filelist(osc.conf.config['apiurl'], prj, pkg, verbose=True):
                st = EmptyStat()
                st.st_mode = stat.S_IFREG | 0444
                st.st_size = f.size
                st.st_atime = f.mtime
                st.st_ctime = f.mtime
                st.st_mtime = f.mtime
                cache[path + '/' + f.name] = CacheEntry()
                cache[path + '/' + f.name].stat = st
                yield fuse.Direntry(f.name)
            return

        if path in projects: # path is project
            prj = path.replace('/','')
            for p in osc.core.meta_get_packagelist(osc.conf.config['apiurl'], prj):
                yield fuse.Direntry(p)

        else: # path is project structure
            if (path != '/'):
                path += '/'
            l = len(path)
            for d in set( map(lambda x: x[l:].split('/')[0], filter(lambda x: x.startswith(path), projects) ) ) :
                yield fuse.Direntry(d)

    def mythread ( self ):
        print '*** mythread'
        return -errno.ENOSYS

    def chmod ( self, path, mode ):
        print '*** chmod', path, oct(mode)
        return -errno.ENOSYS

    def chown ( self, path, uid, gid ):
        print '*** chown', path, uid, gid
        return -errno.ENOSYS

    def fsync ( self, path, isFsyncFile ):
        print '*** fsync', path, isFsyncFile
        return -errno.ENOSYS

    def link ( self, targetPath, linkPath ):
        print '*** link', targetPath, linkPath
        return -errno.ENOSYS

    def mkdir ( self, path, mode ):
        print '*** mkdir', path, oct(mode)
        return -errno.ENOSYS

    def mknod ( self, path, mode, dev ):
        print '*** mknod', path, oct(mode), dev
        return -errno.ENOSYS

    def open ( self, path, flags ):
        file = os.path.basename(path)
        d = os.path.dirname(path)
        pkg = os.path.basename(d)
        prj = os.path.dirname(d).replace('/','')
        if not cache.has_key(path):
            return -errno.ENOENT
        if cache[path].stat == None:
            return -errno.ENOENT
        tmp = tempfile.mktemp(prefix = 'oscfs_')
        osc.core.get_source_file(osc.conf.config['apiurl'], prj, pkg, file, tmp)
        cache[path].handle = open(tmp, 'r')
        cache[path].tmpname = tmp

    def read ( self, path, length, offset ):
        if not cache.has_key(path):
            return -errno.EACCES
        f = cache[path].handle
        f.seek(offset)
        return f.read(length)

    def readlink ( self, path ):
        print '*** readlink', path
        return -errno.ENOSYS

    def release ( self, path, flags ):
        if cache.has_key(path):
            cache[path].handle.close()
            cache[path].handle = None
            os.unlink(f.cache[path].tmpname)
            cache[path].tmpname = None

    def rename ( self, oldPath, newPath ):
        print '*** rename', oldPath, newPath
        return -errno.ENOSYS

    def rmdir ( self, path ):
        print '*** rmdir', path
        return -errno.ENOSYS

    def statfs ( self ):
        print '*** statfs'
        return -errno.ENOSYS

    def symlink ( self, targetPath, linkPath ):
        print '*** symlink', targetPath, linkPath
        return -errno.ENOSYS

    def truncate ( self, path, size ):
        print '*** truncate', path, size
        return -errno.ENOSYS

    def unlink ( self, path ):
        print '*** unlink', path
        return -errno.ENOSYS

    def utime ( self, path, times ):
        print '*** utime', path, times
        return -errno.ENOSYS

    def write ( self, path, buf, offset ):
        print '*** write', path, buf, offset
        return -errno.ENOSYS

def fill_projects():
    try:
        for prj in osc.core.meta_get_project_list(osc.conf.config['apiurl']):
            projects.append( '/' + prj.replace(':', ':/') )
    except:
        print 'failed'
        sys.exit(1)

if __name__ == '__main__':
    print 'Loading config ...',
    osc.conf.get_config()
    print 'OK'
    print 'Getting projects list ...',
    fill_projects()
    print 'OK'
    print 'Starting FUSE ...',
    oscfs = oscFS( version = '%prog ' + fuse.__version__, usage = '', dash_s_do = 'setsingle')
    oscfs.flags = 0
    oscfs.multithreaded = 0
    oscfs.parse(values = oscfs, errex = 1)
    oscfs.main()
