Attachment 'wikifuse.py'

Download

   1 #!/usr/bin/env python
   2 #
   3 # Copyright 2006 Nedko Arnaudov <nedko@arnaudov.name>
   4 # You can use this software for any purpose you want
   5 # Use it on your own risk
   6 # You are allowed to modify the code
   7 # You are disallowed to remove author credits from header
   8 #
   9 # FUSE filesystem for wiki
  10 #
  11 # Uses XML-RPC for accessing wiki
  12 # Documentation here: http://www.jspwiki.org/Wiki.jsp?page=WikiRPCInterface2
  13 #
  14 # Requires:
  15 #  FUSE python bindings, available from FUSE project http://fuse.sourceforge.net/
  16 #  xmlrpclib, available since python 2.2
  17 #
  18 # Tested with:
  19 #  Editors: xemacs, vi
  20 #  FUSE lkm: one comming with 2.6.15
  21 #  FUSE usermode: 2.5.2
  22 #  Python: 2.4.2
  23 #  Wiki engines: MoinMoinWiki 1.5.0
  24 #
  25 # Expected to work with:
  26 #  Every editor (60%)
  27 #  Python >= 2.2 (99%)
  28 #  FUSE > 2.5.2 (70%)
  29 #  MoinMoinWiki > 1.5.0 (90%)
  30 #
  31 # If you improve this code, send a modified version to author please
  32 #
  33 ########################################################################################
  34 
  35 wiki_xmlrpc_url = "http://wiki.atia.com/moin.cgi/?action=xmlrpc2"
  36 
  37 # Authentication type
  38 # Valid values are None, "moin_cookie"
  39 #auth_type = None
  40 auth_type = "moin_cookie"
  41 
  42 # moin_cookie authentication (MoinMoinWiki)
  43 #
  44 # For this to work change server check for self.request.user.trusted to check for self.request.user.valid
  45 # The check is made in wikirpc.py, near line 362
  46 #
  47 # auth_string is simply internal moin user id
  48 # Look at your data/users directory in wiki instance to get exact value
  49 auth_string = "1137148457.14.9776"
  50 
  51 # Verbosity
  52 #
  53 # 0 - silent
  54 # 1 - errors
  55 # 2 - basic
  56 # 3 - debug
  57 verbosity = 1
  58 
  59 from xmlrpclib import ServerProxy, Transport
  60 from fuse import Fuse
  61 import os, sys
  62 import xmlrpclib
  63 import types
  64 from errno import *
  65 #import thread
  66 
  67 class WikiAuthTransport(Transport):
  68     def __init__(self, auth_type=None, auth_string=None):
  69         self.auth_type = auth_type
  70         self.auth_string = auth_string
  71 
  72     def get_host_info(self, host):
  73         host, extra_headers, x509 = Transport.get_host_info(self, host)
  74 
  75         if self.auth_type == "moin_cookie":
  76             if extra_headers == None:
  77                 extra_headers = []
  78             extra_headers.append(("Cookie", "MOIN_ID="+self.auth_string))
  79 
  80         return host, extra_headers, x509
  81 
  82 class WikiFS(Fuse):
  83 
  84     def __init__(self, url, auth_type, auth_string, verbosity, *args, **kw):
  85         Fuse.__init__(self, *args, **kw)
  86 
  87         self.verbosity = verbosity
  88 
  89         self.log_basic("mountpoint: %s" % repr(self.mountpoint))
  90         self.log_basic("unnamed mount options: %s" % self.optlist)
  91         self.log_basic("named mount options: %s" % self.optdict)
  92     
  93         # do stuff to set up your filesystem here, if you want
  94         #thread.start_new_thread(self.mythread, ())
  95 
  96         transport = WikiAuthTransport(auth_type, auth_string)
  97         if self.verbosity >= 3:
  98             xmlrpc_verbose = 1
  99         else:
 100             xmlrpc_verbose = 0
 101         self.wikiproxy = xmlrpclib.ServerProxy(url, transport=transport, verbose=xmlrpc_verbose)
 102         self.pages = {}
 103 
 104     def log(self, str):
 105         print str
 106 
 107     def log_error(self, str):
 108         if self.verbosity >= 1:
 109             self.log(str)
 110 
 111     def log_basic(self, str):
 112         if self.verbosity >= 2:
 113             self.log(str)
 114 
 115     def log_debug(self, str):
 116         if self.verbosity >= 3:
 117             self.log(str)
 118 
 119     def mythread(self):
 120     
 121         """
 122         The beauty of the FUSE python implementation is that with the python interp
 123         running in foreground, you can have threads
 124         """
 125         self.log_basic("mythread: started")
 126         #while 1:
 127         #    time.sleep(120)
 128         #    self.log_basic("mythread: ticking")
 129     
 130     flags = 1
 131     
 132     def getattr(self, path):
 133         self.log_basic("getattr \"%s\"" % path)
 134 
 135         ino = 0
 136         dev = 0
 137         nlink = 0
 138         uid = 0
 139         gid = 0
 140         atime = 0
 141         mtime = 0
 142         ctime = 0
 143         size = 0
 144 
 145         if (path == '/'):
 146             mode = 040777
 147         else:
 148             mode = 0100666
 149 
 150             info = self.wikiproxy.getPageInfo(path[1:])
 151             if info.has_key('faultCode'):
 152                 self.log_error("getPageInfo(%s) failed" % path[1:] + repr(info))
 153                 return -ENOENT
 154 
 155             if self.pages.has_key(path):
 156                 size = self.pages[path]['size']
 157             else:
 158                 page = self.wikiproxy.getPage(path[1:])
 159                 size = len(page)
 160 
 161         return (mode,ino,dev,nlink,uid,gid,size,atime,mtime,ctime)
 162 
 163     def readlink(self, path):
 164         self.log_basic("readlink")
 165    	return -ENOSYS
 166 
 167     def listdir(self, path):
 168         all = self.wikiproxy.getAllPages()
 169         firstlevel = []
 170         for page in all:
 171             if page.find('/') == -1:
 172                 firstlevel.append(page)
 173         return firstlevel
 174 
 175     def getdir(self, path):
 176         self.log_basic("getdir \"%s\"" % path)
 177     	return map(lambda x: (x,0), self.listdir(path))
 178 
 179     def unlink(self, path):
 180         self.log_basic("unlink")
 181    	return -ENOSYS
 182     def rmdir(self, path):
 183         self.log_basic("rmdir")
 184    	return -ENOSYS
 185     def symlink(self, path, path1):
 186         self.log_basic("symlink")
 187    	return -ENOSYS
 188     def rename(self, path, path1):
 189         self.log_basic("rename")
 190    	return -ENOSYS
 191     def link(self, path, path1):
 192         self.log_basic("link")
 193    	return -ENOSYS
 194     def chmod(self, path, mode):
 195         self.log_basic("chmod")
 196    	return -ENOSYS
 197     def chown(self, path, user, group):
 198         self.log_basic("chown")
 199    	return -ENOSYS
 200     def truncate(self, path, size):
 201         self.log_basic("truncate \"%s\" %u bytes" % (path, size))
 202         if not self.pages.has_key(path):
 203             self.pages[path] = {}
 204         else:
 205             if size > self.pages[path]['size']:
 206                 return -EINVAL
 207         self.pages[path]['modified'] = True
 208         self.pages[path]['size'] = size
 209         if self.pages[path].has_key('data'):
 210             self.pages[path]['data'] = self.pages[path]['data'][size:]
 211    	return 0
 212     def mknod(self, path, mode, dev):
 213         self.log_basic("mknod")
 214    	return -ENOSYS
 215     def mkdir(self, path, mode):
 216         self.log_basic("mkdir")
 217    	return -ENOSYS
 218     def utime(self, path, times):
 219         self.log_basic("utime")
 220    	return -ENOSYS
 221     def open(self, path, flags):
 222         self.log_basic("open \"%s\" flags %u" % (path, flags))
 223         if not self.pages.has_key(path):
 224             self.pages[path] = {}
 225         data = self.wikiproxy.getPage(path[1:])
 226         if self.pages[path].has_key('size'):
 227             size = self.pages[path]['size']
 228             if size != len(data):
 229                 self.log_basic("Truncating to %u bytes" % size)
 230                 if size == 0:
 231                     data = ""
 232                 else:
 233                     data = data[size:]
 234                 self.pages[path]['modified'] = True
 235                 self.pages[path]['data'] = data
 236                 self.log_debug("\"%s\"" % data)
 237         else:
 238             size = len(data)
 239             self.pages[path]['modified'] = False
 240         self.pages[path]['data'] = data
 241         self.pages[path]['size'] = size
 242     	return 0
 243     
 244     def read(self, path, length, offset):
 245         self.log_basic("read \"%s\" %u bytes, from offset %u" % (path, length, offset))
 246 
 247         if offset + length > self.pages[path]['size']:
 248             length = self.pages[path]['size'] - offset
 249 
 250         if length == 0:
 251             data = ""
 252         else:
 253             data = self.pages[path]['data'][offset:offset+length]
 254         self.log_debug("\"%s\"" % data)
 255         self.log_debug(len(data))
 256    	return data
 257     
 258     def write(self, path, buf, offset):
 259         self.log_basic("write \"%s\" %u bytes, to offset %u" % (path, len(buf), offset))
 260         size = len(buf)
 261         pre = self.pages[path]['data'][:offset]
 262         post =  self.pages[path]['data'][offset+size:]
 263         data = pre + buf + post
 264         self.pages[path]['size'] = len(data)
 265         self.log_debug("\"%s\"" % data)
 266         self.pages[path]['data'] = data
 267         self.pages[path]['modified'] = True
 268    	return size
 269     
 270     def release(self, path, flags):
 271         self.log_basic("release \"%s\"" % path)
 272         ret = self.fsync_do(path)
 273         if ret == 0:
 274             del self.pages[path]['data']
 275    	return ret
 276     def statfs(self):
 277         """
 278         Should return a tuple with the following 6 elements:
 279             - blocksize - size of file blocks, in bytes
 280             - totalblocks - total number of blocks in the filesystem
 281             - freeblocks - number of free blocks
 282             - totalfiles - total number of file inodes
 283             - freefiles - nunber of free file inodes
 284     
 285         Feel free to set any of the above values to 0, which tells
 286         the kernel that the info is not available.
 287         """
 288         self.log_basic("statfs: returning fictitious values")
 289         blocks_size = 1024
 290         blocks = 100000
 291         blocks_free = 25000
 292         files = 100000
 293         files_free = 60000
 294         namelen = 80
 295         return (blocks_size, blocks, blocks_free, files, files_free, namelen)
 296 
 297     def fsync(self, path, isfsyncfile):
 298         self.log_basic("fsync: path=%s, isfsyncfile=%s" % (path, isfsyncfile))
 299         return self.fsync_do(path)
 300 
 301     def fsync_do(self, path):
 302         if self.pages.has_key(path):
 303             if self.pages[path]['modified']:
 304                 self.log_basic("PUT PAGE")
 305                 self.log_debug("\"%s\"" % self.pages[path]['data'])
 306                 ret = self.wikiproxy.putPage(path[1:], self.pages[path]['data'])
 307                 if type(ret) == types.BooleanType and ret == True:
 308                     self.pages[path]['modified'] = False
 309                     return 0
 310                 else:
 311                     self.log_error("putPage(%s) failed" % path[1:] + repr(ret))
 312                     return -EIO
 313 
 314 if __name__ == '__main__':
 315     server = WikiFS(wiki_xmlrpc_url, auth_type, auth_string, verbosity)
 316     server.multithreaded = 1
 317     server.main()

Attached Files

To refer to attachments on a page, use attachment:filename, as shown below in the list of files. Do NOT use the URL of the [get] link, since this is subject to change and can break easily.
  • [get | view] (2006-02-24 02:41:33, 9.7 KB) [[attachment:wikifuse.py]]
 All files | Selected Files: delete move to page copy to page

You are not allowed to attach a file to this page.