[dokuwiki] Re: DokuFS issue
- From: Michael Hamann <michael@xxxxxxxxxxxxxxxx>
- To: dokuwiki@xxxxxxxxxxxxx
- Date: Wed, 10 Sep 2008 21:54:22 +0200
Hi,
Stefan Waidele schrieb:
> I installed the developer-version (Sept 10th, 2008, 20:00) using the
> instructions from http://www.dokuwiki.org/devel:darcs
>
> "darcs get --partial http://dev.splitbrain.org/darcs/dokuwiki"
>
> I enabled XMLRPC in the configuration.
Okay, and that's part of your problem. During DokuWiki Hackdays some
functions of the XMLRPC-interface were changed and DokuFS is not yet
compatible with these changes. Nevertheless, you don't have to downgrade
(it should work with the latest stable release of DokuWiki) but you can
just upgrade DokuFS and enjoy yet more features and hopefully bugfixes.
E.g. media up- and downloads are possible now thanks to a new option
-media that switches DokuFS into "media-mode".
> On my desktop, I am using Ubuntu 7.10 - I had to install some
> Ruby-packet "sudo apt-get install libfusefs-ruby" and the
> dokufs.rb-skript worked.
>
> Here's my experience:
>
> * I was able to create a page on my brand-new dev-wiki "start.dw" and
> it showed both in DokuFS and in the browser.
>
> * I edited the page in the browser, but the change did not show in the
> DokuFS.
This is because I do cache in all pages in DokuFS. Every five minutes I
pull the recent changes from DokuWiki and update all files that changed.
That means after a maximum of 5 minutes you should see the changes in
the DokuFS.
I might add another option for specifying this update interval or also a
cache timeout, that means that the page is deleted from the cache when
it's older than e.g. 2 minutes, but I don't know if that's a good
option. If you have any ideas for this, please let me know.
> * After some reading and writing attempts to start.dw in DokuFS,
> dokufs.rb terminated with the following message:
>
> ----8<--------------------------------------------------------------
> stw@fourgb:~$ ./dokufs.rb -user xxx -password yyy -no-ssl -server
> stefan.waidele.info -path /wiki/lib/exe/xmlrpc.php dw
> /usr/lib/ruby/1.8/xmlrpc/client.rb:414:in `call': There are no changes
> in the specified timeframe (XMLRPC::FaultException)
> from ./dokufs.rb:300:in `update'
> from ./dokufs.rb:340
> from ./dokufs.rb:338:in `initialize'
> from ./dokufs.rb:338:in `new'
> from ./dokufs.rb:338
> ----8<--------------------------------------------------------------
Interesting, yes, that's correct that you see this error because I don't
catch it. Didn't know the server sends such an error message but now I
see it and it's clear. DokuFS is still very bad in error checking. I've
added a check that should prevent this error.
> * Any further attempt to mount the wiki failed like this:
>
> ----8<--------------------------------------------------------------
> stw@fourgb:/SRV/home/stw$ ./dokufs.rb -user xxx -password yyy -no-ssl
> -server stefan.waidele.info -path /wiki/lib/exe/xmlrpc.php dw2
> ./dokufs.rb:109:in `initialize': private method `gsub' called for
> #<Hash:0xb7288874> (NoMethodError)
> from ./dokufs.rb:108:in `each'
> from ./dokufs.rb:108:in `initialize'
> from ./dokufs.rb:335:in `new'
> from ./dokufs.rb:335
> ----8<--------------------------------------------------------------
>
> As you can see, I even created a new mountpoint.
And now that's the incompatibility I talked about. The data the server
sends has changed. Don't know why you haven't got it in the first place,
perhaps because your wiki was empty.
> I will reboot my workstation after sending this message to see if I can
> mount the wiki again after reboot and if I can reproduce the error.
You don't have to, as I said it's a compatibility problem.
> Let me know if I can supply any more information which could be usefull
> to improve this great project.
Thanks you!
I've attached a snapshot of my local development version. I guess it
still has some bugs (I still haven't found the time to write some more
and especially more sophisticated tests) but it works with the latest
version of DokuWiki, has the media-feature I already talked about
(although I am currently not using a cache for media-files which makes
the media-feature a bit slow when you use graphical programs that read
the files very often) and it should be able to handle acls much better
(that means e.g. you can't open pages that are read-only for writing, in
the previous version that was possible).
So the bugs you found shouldn't happen again in this version (hopefully ;)).
In the case you should detect any bugs please tell me (or write to the
list, but I doubt anybody but me will be helping and I don't know if
everybody is interested in these bugs as most of them only concern
DokuFS ;) ).
Greetings
Michael Hamann
#!/usr/bin/ruby
# DokuFS
# A Filesystem for accessing DokuWiki (version 2008-05-05 or above)
# on your local filesystem. More information can be found on
# http://www.content-space.de/go/dokufs
#
# Copyright (C) 2008 Michael Hamann michael <at> content-space.de
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# The idea of this program and some of the recursive functions here
# are adapted versions of MetaFS which was written by Greg Millam and
# distributed with FuseFS. Besides Ruby this FuseFS which can be
# obtained on http://rubyforge.org/projects/fusefs/ is the only
# dependency of DokuFS.
# TODO: recognize when save failed
require "cgi"
require "fusefs"
require "xmlrpc/client"
class StringCache < Hash
def initialize (maxsize)
super()
@maxsize = maxsize
@lru_keys = []
end
def clear
super
@lru_keys.clear
end
def []= (key, value)
raise ArgumentError, "Value must be kind of String" unless
value.kind_of?(String)
remove_lru
super
touch key
end
def merge! (hash)
hash.each { |k,v| self[k] = v }
end
def delete (key)
value = super
@lru_keys.delete key
value
end
protected
def touch (key)
@lru_keys.delete key
@lru_keys << key
end
def mem_size
result = 0
each_value do |v|
result += v.size
end
return result
end
def remove_lru
while mem_size >= @maxsize
key = @lru_keys.delete_at 0
delete key
end
end
end
class DokuFS < FuseFS::FuseDir
AUTH_NONE = 0
AUTH_READ = 1
AUTH_EDIT = 2
AUTH_CREATE = 4
AUTH_UPLOAD = 8
AUTH_DELETE = 16
AUTH_ADMIN = 255
DEFAULT_OPTS = {
:use_ssl => true,
:path => "/lib/exe/xmlrpc.php",
:host => "localhost"
}
def root?
@is_root
end
def media?
@is_media
end
def initialize(user_opts = nil)
@pages = {}
@subdirs = {}
if ! user_opts.nil?
opts = DEFAULT_OPTS
opts.merge!(user_opts)
opts[:path] +=
"?u=#{CGI.escape(opts[:user])}&p=#{CGI.escape(opts[:password])}" if opts[:user]
&& opts[:password]
@server = XMLRPC::Client.new3(opts)
@is_root = true
@is_media = opts[:media]
@last_update = Time.now.utc.to_i
unless self.media?
@cache = StringCache.new(1024*1024*5)
@server.call("wiki.getAllPages").each do |page|
self.add(pagename_to_path(page['id']),
page)
end
else
@server.call("wiki.getAttachments", "",
{:recursive => true}).each do |media|
self.add(pagename_to_path(media['id']),
media)
end
end
end
end
def add(path, data)
base, rest = split_path(path)
case
when base.nil?
return false
when rest.nil?
@pages[base] = data
when @subdirs.has_key?(base)
@subdirs[base].add(rest, data)
else
(@subdirs[base] = self.class.new).add(rest, data)
end
end
def getdata(path)
base, rest = split_path(path)
case
when base.nil?
false
when rest.nil?
if @pages.has_key?(base)
return @pages[base]
else
return false
end
when ! @subdirs.has_key?(base)
false
else
@subdirs[base].getdata(rest)
end
end
def contents(path)
base, rest = split_path(path)
case
when base.nil?
(@pages.keys + @subdirs.keys).sort.uniq
when ! @subdirs.has_key?(base)
nil
when rest.nil?
@subdirs[base].contents('/')
else
@subdirs[base].contents(rest)
end
end
def size(path)
if directory?(path)
return 4000
else
if file?(path)
return getdata(path)['size']
end
end
end
def directory?(path)
base, rest = split_path(path)
case
when base.nil?
true
when ! @subdirs.has_key?(base)
false
when rest.nil?
true
else
@subdirs[base].directory?(rest)
end
end
def file?(path)
if (self.root? && ! self.media?)
return true if @cache.has_key?(path_to_pagename(path))
end
base, rest = split_path(path)
case
when base.nil?
false
when rest.nil?
@pages.has_key?(base)
when ! @subdirs.has_key?(base)
false
else
@subdirs[base].file?(rest)
end
end
def can_write? path
if file?(path)
perms = getdata(path)['perms']
if media?
return perms >= AUTH_DELETE
else
return perms >= AUTH_EDIT
end
else
if media?
perms = @server.call('wiki.aclCheck',
path_to_pagename(path))
return perms >= AUTH_UPLOAD
else
return false unless path =~ /\.dw\Z/
perms = @server.call('wiki.aclCheck',
path_to_pagename(path))
return perms >= AUTH_CREATE
end
end
end
# mkdir
def can_mkdir? path
return true
end
def mkdir(path)
base, rest = split_path(path)
case
when base.nil?
false
when rest.nil?
@subdirs[base] = self.class.new
true
when ! @subdirs.has_key?(base)
false
else
@subdirs[base].mkdir(rest)
end
end
# Delete a file
def can_delete?(path)
#return false unless Process.uid == FuseFS.reader_uid
if file?(path)
perms = getdata(path)['perms']
if media?
return perms >= AUTH_DELETE
else
return perms >= AUTH_EDIT
end
else
return false
end
end
def remove_from_tree(path)
base, rest = split_path(path)
case
when base.nil?
nil
when rest.nil?
# Delete it.
@pages.delete(base)
when ! @subdirs.has_key?(base)
nil
else
@subdirs[base].remove_from_tree(rest)
end
end
def delete(path)
if media?
@server.call("wiki.deleteAttachment",
path_to_pagename(path))
else
@server.call("wiki.putPage", path_to_pagename(path),
"", { "sum" => "deleted by DokuFS", "minor" => false })
@cache.delete(path_to_pagename(path))
end
self.remove_from_tree(path)
end
# Delete an existing directory.
def can_rmdir?(path)
#return false unless Process.uid == FuseFS.reader_uid
base, rest = split_path(path)
if base.nil?
@pages.empty?
else
if @subdirs.has_key?(base)
if rest.nil?
@subdirs[base].can_rmdir?("/")
else
@subdirs[base].can_rmdir?(rest)
end
else
false
end
end
end
def rmdir(path)
base, rest = split_path(path)
case
when base.nil?
false
when rest.nil?
@subdirs.delete(base)
true
when ! @subdirs.has_key?(base)
false
else
@subdirs[base].rmdir(rest)
end
end
def read_file path
pagename = path_to_pagename(path)
if media?
XMLRPC::Base64.decode(@server.call("wiki.getAttachment", pagename))
else
@cache[pagename] ||= @server.call("wiki.getPage",
pagename)
end
end
def write_to (path, content)
pagename = path_to_pagename(path)
if media?
begin
encoded_content = XMLRPC::Base64.encode(content)
@server.call("wiki.putAttachment", pagename,
encoded_content, {:overwrite => self.file?(path)})
data = {
'id' => path_to_pagename(path),
'size' => encoded_content.size,
'perms' =>
@server.call('wiki.aclCheck', path_to_pagename(path)),
}
self.add(path, data)
rescue Exception => e
puts e.message
end
else
message = { "sum" => "", "minor" => true }
plain_content = content
if content[0] == "%"[0]
content.sub!(/\A%\s?([^\n]+)\n?/m) do
message["sum"] = $1
""
end
message["minor"] = false
end
if content =~ /\A\s*\Z/m # when the page is empty, it
is deleted
if message["sum"].empty?
return false
end
self.remove_from_tree(path)
else
data = {
'id' => path_to_pagename(path),
'size' => plain_content.size,
'perms' =>
@server.call('wiki.aclCheck', path_to_pagename(path)),
}
self.add(path, data)
end
@cache[pagename] = plain_content
@server.call("wiki.putPage", path_to_pagename(path),
content, message)
end
end
def update
ltime = @last_update
@last_update = Time.now.utc.to_i
begin
@server.call("wiki.getRecentChanges", ltime).each do |page|
path = pagename_to_path(page["name"])
if self.file?(path)
@cache.delete(page["name"])
self.remove_from_tree(path) if self.read_file(path).empty?
else
self.add(path, page)
end
end
rescue XMLRPC::FaultException => e
end
return true
end
def path_to_pagename(path)
path.sub(/\.dw\Z/, "").gsub("/", ":").reverse.chop.reverse
end
def pagename_to_path(id)
if self.media?
return '/'+id.gsub(":", "/")
else
return '/'+id.gsub(":", "/")+'.dw'
end
end
end
if (File.basename($0) == File.basename(__FILE__))
Thread.abort_on_exception = true # for debugging...
opts = {}
begin
arg = ARGV.shift
case arg
when "-user"
opts[:user] = ARGV.shift
when "-password"
opts[:password] = ARGV.shift
when "-server"
opts[:host] = ARGV.shift
when "-path"
opts[:path] = ARGV.shift
when "-no-ssl"
opts[:use_ssl] = false
when "-media"
opts[:media] = true
else
if ARGV.empty? && !arg.nil? && File.directory?(arg)
root = DokuFS.new(opts)
FuseFS.set_root(root)
FuseFS.mount_under(arg)
unless opts[:media]
updater = Thread.new do
sleep 5*60
root.update
end
end
FuseFS.run # This doesn't return until we're
unmounted.
Thread.exit(updater) unless opts[:media]
else
puts <<-EOF
With DokuFS you can mount a DokuWiki under a path in your filesystem
All arguments except the path where to mount are optional, defaults are ssl,
localhost as server and /lib/exe/xmlrpc.php as path. No authentication is
default.
The -media-flag indicates if DokuFS should work in media-mode, that means it
will display all media files instead of wiki-pages. Upload of media-files is
possible.
Usage: dokufs.rb [-media] [-user your_username -password your_password]
[-server your_server.com] [-path your/path/to/lib/exe/xmlrpc.php] [-no-ssl]
path/where/to/mount/
EOF
exit;
end
end
end while arg != nil
end
- Follow-Ups:
- [dokuwiki] Re: DokuFS issue
- From: Stefan Waidele
- References:
- [dokuwiki] DokuFS issue
- From: Stefan Waidele
Other related posts:
- » [dokuwiki] DokuFS issue
- » [dokuwiki] Re: DokuFS issue
- » [dokuwiki] Re: DokuFS issue
- [dokuwiki] Re: DokuFS issue
- From: Stefan Waidele
- [dokuwiki] DokuFS issue
- From: Stefan Waidele