#!/usr/bin/env python
#
# Copyright 2006 Nedko Arnaudov <nedko@arnaudov.name>
# You can use this software for any purpose you want
# Use it on your own risk
# You are allowed to modify the code
# You are disallowed to remove author credits from header
#
# FUSE filesystem for wiki
#
# Uses XML-RPC for accessing wiki
# Documentation here: http://www.jspwiki.org/Wiki.jsp?page=WikiRPCInterface2
#
# Requires:
#  FUSE python bindings, available from FUSE project http://fuse.sourceforge.net/
#  xmlrpclib, available since python 2.2
#
# Tested with:
#  Editors: xemacs, vi
#  FUSE lkm: one comming with 2.6.15
#  FUSE usermode: 2.5.2
#  Python: 2.4.2
#  Wiki engines: MoinMoinWiki 1.5.0
#
# Expected to work with:
#  Every editor (60%)
#  Python >= 2.2 (99%)
#  FUSE > 2.5.2 (70%)
#  MoinMoinWiki > 1.5.0 (90%)
#
# If you improve this code, send a modified version to author please
#
########################################################################################

wiki_xmlrpc_url = "http://wiki.atia.com/moin.cgi/?action=xmlrpc2"

# Authentication type
# Valid values are None, "moin_cookie"
#auth_type = None
auth_type = "moin_cookie"

# moin_cookie authentication (MoinMoinWiki)
#
# For this to work change server check for self.request.user.trusted to check for self.request.user.valid
# The check is made in wikirpc.py, near line 362
#
# auth_string is simply internal moin user id
# Look at your data/users directory in wiki instance to get exact value
auth_string = "1137148457.14.9776"

# Verbosity
#
# 0 - silent
# 1 - errors
# 2 - basic
# 3 - debug
verbosity = 1

from xmlrpclib import ServerProxy, Transport
from fuse import Fuse
import os, sys
import xmlrpclib
import types
from errno import *
#import thread

class WikiAuthTransport(Transport):
    def __init__(self, auth_type=None, auth_string=None):
        self.auth_type = auth_type
        self.auth_string = auth_string

    def get_host_info(self, host):
        host, extra_headers, x509 = Transport.get_host_info(self, host)

        if self.auth_type == "moin_cookie":
            if extra_headers == None:
                extra_headers = []
            extra_headers.append(("Cookie", "MOIN_ID="+self.auth_string))

        return host, extra_headers, x509

class WikiFS(Fuse):

    def __init__(self, url, auth_type, auth_string, verbosity, *args, **kw):
        Fuse.__init__(self, *args, **kw)

        self.verbosity = verbosity

        self.log_basic("mountpoint: %s" % repr(self.mountpoint))
        self.log_basic("unnamed mount options: %s" % self.optlist)
        self.log_basic("named mount options: %s" % self.optdict)
    
        # do stuff to set up your filesystem here, if you want
        #thread.start_new_thread(self.mythread, ())

        transport = WikiAuthTransport(auth_type, auth_string)
        if self.verbosity >= 3:
            xmlrpc_verbose = 1
        else:
            xmlrpc_verbose = 0
        self.wikiproxy = xmlrpclib.ServerProxy(url, transport=transport, verbose=xmlrpc_verbose)
        self.pages = {}

    def log(self, str):
        print str

    def log_error(self, str):
        if self.verbosity >= 1:
            self.log(str)

    def log_basic(self, str):
        if self.verbosity >= 2:
            self.log(str)

    def log_debug(self, str):
        if self.verbosity >= 3:
            self.log(str)

    def mythread(self):
    
        """
        The beauty of the FUSE python implementation is that with the python interp
        running in foreground, you can have threads
        """
        self.log_basic("mythread: started")
        #while 1:
        #    time.sleep(120)
        #    self.log_basic("mythread: ticking")
    
    flags = 1
    
    def getattr(self, path):
        self.log_basic("getattr \"%s\"" % path)

        ino = 0
        dev = 0
        nlink = 0
        uid = 0
        gid = 0
        atime = 0
        mtime = 0
        ctime = 0
        size = 0

        if (path == '/'):
            mode = 040777
        else:
            mode = 0100666

            info = self.wikiproxy.getPageInfo(path[1:])
            if info.has_key('faultCode'):
                self.log_error("getPageInfo(%s) failed" % path[1:] + repr(info))
                return -ENOENT

            if self.pages.has_key(path):
                size = self.pages[path]['size']
            else:
                page = self.wikiproxy.getPage(path[1:])
                size = len(page)

        return (mode,ino,dev,nlink,uid,gid,size,atime,mtime,ctime)

    def readlink(self, path):
        self.log_basic("readlink")
   	return -ENOSYS

    def listdir(self, path):
        all = self.wikiproxy.getAllPages()
        firstlevel = []
        for page in all:
            if page.find('/') == -1:
                firstlevel.append(page)
        return firstlevel

    def getdir(self, path):
        self.log_basic("getdir \"%s\"" % path)
    	return map(lambda x: (x,0), self.listdir(path))

    def unlink(self, path):
        self.log_basic("unlink")
   	return -ENOSYS
    def rmdir(self, path):
        self.log_basic("rmdir")
   	return -ENOSYS
    def symlink(self, path, path1):
        self.log_basic("symlink")
   	return -ENOSYS
    def rename(self, path, path1):
        self.log_basic("rename")
   	return -ENOSYS
    def link(self, path, path1):
        self.log_basic("link")
   	return -ENOSYS
    def chmod(self, path, mode):
        self.log_basic("chmod")
   	return -ENOSYS
    def chown(self, path, user, group):
        self.log_basic("chown")
   	return -ENOSYS
    def truncate(self, path, size):
        self.log_basic("truncate \"%s\" %u bytes" % (path, size))
        if not self.pages.has_key(path):
            self.pages[path] = {}
        else:
            if size > self.pages[path]['size']:
                return -EINVAL
        self.pages[path]['modified'] = True
        self.pages[path]['size'] = size
        if self.pages[path].has_key('data'):
            self.pages[path]['data'] = self.pages[path]['data'][size:]
   	return 0
    def mknod(self, path, mode, dev):
        self.log_basic("mknod")
   	return -ENOSYS
    def mkdir(self, path, mode):
        self.log_basic("mkdir")
   	return -ENOSYS
    def utime(self, path, times):
        self.log_basic("utime")
   	return -ENOSYS
    def open(self, path, flags):
        self.log_basic("open \"%s\" flags %u" % (path, flags))
        if not self.pages.has_key(path):
            self.pages[path] = {}
        data = self.wikiproxy.getPage(path[1:])
        if self.pages[path].has_key('size'):
            size = self.pages[path]['size']
            if size != len(data):
                self.log_basic("Truncating to %u bytes" % size)
                if size == 0:
                    data = ""
                else:
                    data = data[size:]
                self.pages[path]['modified'] = True
                self.pages[path]['data'] = data
                self.log_debug("\"%s\"" % data)
        else:
            size = len(data)
            self.pages[path]['modified'] = False
        self.pages[path]['data'] = data
        self.pages[path]['size'] = size
    	return 0
    
    def read(self, path, length, offset):
        self.log_basic("read \"%s\" %u bytes, from offset %u" % (path, length, offset))

        if offset + length > self.pages[path]['size']:
            length = self.pages[path]['size'] - offset

        if length == 0:
            data = ""
        else:
            data = self.pages[path]['data'][offset:offset+length]
        self.log_debug("\"%s\"" % data)
        self.log_debug(len(data))
   	return data
    
    def write(self, path, buf, offset):
        self.log_basic("write \"%s\" %u bytes, to offset %u" % (path, len(buf), offset))
        size = len(buf)
        pre = self.pages[path]['data'][:offset]
        post =  self.pages[path]['data'][offset+size:]
        data = pre + buf + post
        self.pages[path]['size'] = len(data)
        self.log_debug("\"%s\"" % data)
        self.pages[path]['data'] = data
        self.pages[path]['modified'] = True
   	return size
    
    def release(self, path, flags):
        self.log_basic("release \"%s\"" % path)
        ret = self.fsync_do(path)
        if ret == 0:
            del self.pages[path]['data']
   	return ret
    def statfs(self):
        """
        Should return a tuple with the following 6 elements:
            - blocksize - size of file blocks, in bytes
            - totalblocks - total number of blocks in the filesystem
            - freeblocks - number of free blocks
            - totalfiles - total number of file inodes
            - freefiles - nunber of free file inodes
    
        Feel free to set any of the above values to 0, which tells
        the kernel that the info is not available.
        """
        self.log_basic("statfs: returning fictitious values")
        blocks_size = 1024
        blocks = 100000
        blocks_free = 25000
        files = 100000
        files_free = 60000
        namelen = 80
        return (blocks_size, blocks, blocks_free, files, files_free, namelen)

    def fsync(self, path, isfsyncfile):
        self.log_basic("fsync: path=%s, isfsyncfile=%s" % (path, isfsyncfile))
        return self.fsync_do(path)

    def fsync_do(self, path):
        if self.pages.has_key(path):
            if self.pages[path]['modified']:
                self.log_basic("PUT PAGE")
                self.log_debug("\"%s\"" % self.pages[path]['data'])
                ret = self.wikiproxy.putPage(path[1:], self.pages[path]['data'])
                if type(ret) == types.BooleanType and ret == True:
                    self.pages[path]['modified'] = False
                    return 0
                else:
                    self.log_error("putPage(%s) failed" % path[1:] + repr(ret))
                    return -EIO

if __name__ == '__main__':
    server = WikiFS(wiki_xmlrpc_url, auth_type, auth_string, verbosity)
    server.multithreaded = 1
    server.main()
