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.You are not allowed to attach a file to this page.