rfusefs 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gemtest +0 -0
- data/History.txt +9 -0
- data/README.rdoc +108 -0
- data/Rakefile +25 -0
- data/TODO.txt +7 -0
- data/lib/fuse/rfusefs-fuse.rb +497 -0
- data/lib/fusefs/dirlink.rb +46 -0
- data/lib/fusefs/metadir.rb +244 -0
- data/lib/fusefs/pathmapper.rb +193 -0
- data/lib/fusefs.rb +8 -0
- data/lib/rfusefs.rb +318 -0
- data/samples/demo.rb +64 -0
- data/samples/dictfs.rb +84 -0
- data/samples/hello.rb +24 -0
- data/samples/openurifs.rb +53 -0
- data/samples/railsfs.rb +77 -0
- data/samples/sqlfs.rb +134 -0
- data/samples/yamlfs.rb +168 -0
- data/spec/rfusefs_spec.rb +400 -0
- data/spec/sample_spec.rb +29 -0
- data/spec/spec_helper.rb +41 -0
- data/spec-fusefs/fusefs_spec.rb +12 -0
- data.tar.gz.sig +0 -0
- metadata +166 -0
- metadata.gz.sig +1 -0
data/lib/rfusefs.rb
ADDED
@@ -0,0 +1,318 @@
|
|
1
|
+
# RFuseFS.rb
|
2
|
+
#gem 'rfuse-ng' , "=0.4.0"
|
3
|
+
require 'fuse/rfusefs-fuse'
|
4
|
+
|
5
|
+
# This is FuseFS compatible module built over RFuse-NG
|
6
|
+
|
7
|
+
module FuseFS
|
8
|
+
VERSION = "0.8.0"
|
9
|
+
@mounts = { }
|
10
|
+
|
11
|
+
# Start the FuseFS root at mountpoint with opts. *RFuseFS* extension
|
12
|
+
# @param mountpoint [String] {mount_under}
|
13
|
+
# @param root [Object] see {set_root}
|
14
|
+
# @param opts [Array<String>] FUSE options see {mount_under}
|
15
|
+
def FuseFS.start(mountpoint,root,*opts)
|
16
|
+
print "Starting FuseFS #{root} at #{mountpoint} with #{opts}\n"
|
17
|
+
Signal.trap("TERM") { FuseFS.exit() }
|
18
|
+
Signal.trap("INT") { FuseFS.exit() }
|
19
|
+
FuseFS.set_root(root)
|
20
|
+
FuseFS.mount_under(mountpoint,*opts)
|
21
|
+
FuseFS.run()
|
22
|
+
FuseFS.unmount()
|
23
|
+
end
|
24
|
+
|
25
|
+
# Forks {start} so you can access your filesystem with ruby File
|
26
|
+
# operations (eg for testing). *RFuseFS* extension
|
27
|
+
def FuseFS.mount(mountpoint,root = nil,*opts)
|
28
|
+
|
29
|
+
pid = Kernel.fork do
|
30
|
+
FuseFS.start(mountpoint, root,*opts)
|
31
|
+
end
|
32
|
+
@mounts[mountpoint] = pid
|
33
|
+
pid
|
34
|
+
end
|
35
|
+
|
36
|
+
# Unmount a filesystem
|
37
|
+
# @param mountpoint [String] If nil?, unmounts the filesystem started with {start}
|
38
|
+
# otherwise signals the forked process started with {mount}
|
39
|
+
# to exit and unmount. *RFuseFS* extension
|
40
|
+
def FuseFS.unmount(mountpoint=nil)
|
41
|
+
|
42
|
+
if (mountpoint)
|
43
|
+
if @mounts.has_key?(mountpoint)
|
44
|
+
pid = @mounts[mountpoint]
|
45
|
+
print "Sending TERM to forked FuseFS (#{pid})\n"
|
46
|
+
Process.kill("TERM",pid)
|
47
|
+
Process.waitpid(pid)
|
48
|
+
else
|
49
|
+
raise "Unknown mountpoint #{mountpoint}"
|
50
|
+
end
|
51
|
+
else
|
52
|
+
#Local unmount, make sure we only try to unmount once
|
53
|
+
if @fuse
|
54
|
+
print "Unmounting #{@fuse.mountname}\n"
|
55
|
+
@fuse.unmount()
|
56
|
+
end
|
57
|
+
@fuse = nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Set the root virtual directory
|
62
|
+
# @param root [Object] an object implementing a subset of {FuseFS::API}
|
63
|
+
def FuseFS.set_root(root)
|
64
|
+
@root=root
|
65
|
+
end
|
66
|
+
|
67
|
+
# This will cause FuseFS to virtually mount itself under the given path. {set_root} must have
|
68
|
+
# been called previously.
|
69
|
+
# @param path [String] an existing directory where the filesystem will be virtually mounted
|
70
|
+
# @param opts [Array<String>] are FUSE options. Most likely, you will only want 'allow_other'
|
71
|
+
# or 'allow_root'. The two are mutually exclusive in FUSE, but allow_other
|
72
|
+
# will let other users, including root, access your filesystem. allow_root
|
73
|
+
# will only allow root to access it.
|
74
|
+
#
|
75
|
+
# Also available for FuseFS users are:
|
76
|
+
# default_permissions, max_read=N, fsname=NAME.
|
77
|
+
#
|
78
|
+
# For more information, see http://fuse.sourceforge.net
|
79
|
+
def FuseFS.mount_under(path,*opts)
|
80
|
+
@fuse = RFuseFS.new(path,opts,[],@root)
|
81
|
+
end
|
82
|
+
|
83
|
+
# This is the main loop waiting on then executing filesystem operations from the
|
84
|
+
# kernel.
|
85
|
+
#
|
86
|
+
# @note Running in a separate thread is generally not useful. In particular
|
87
|
+
# you cannot access your filesystem using ruby File operations.
|
88
|
+
def FuseFS.run
|
89
|
+
unless @fuse
|
90
|
+
raise "fuse is not mounted"
|
91
|
+
end
|
92
|
+
|
93
|
+
begin
|
94
|
+
io = IO.for_fd(@fuse.fd)
|
95
|
+
rescue Errno::EBADF
|
96
|
+
raise "fuse not mounted"
|
97
|
+
end
|
98
|
+
|
99
|
+
@running = true
|
100
|
+
while @running
|
101
|
+
begin
|
102
|
+
#We wake up every 2 seconds to check we are still running.
|
103
|
+
IO.select([io],[],[],2)
|
104
|
+
if @fuse.process() < 0
|
105
|
+
@running = false
|
106
|
+
end
|
107
|
+
rescue Errno::EBADF
|
108
|
+
@running = false
|
109
|
+
rescue Interrupt
|
110
|
+
#do nothing
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Exit the run loop and teardown FUSE
|
116
|
+
# Most useful from Signal.trap() or Kernel.at_exit()
|
117
|
+
def FuseFS.exit
|
118
|
+
@running = false
|
119
|
+
|
120
|
+
if @fuse
|
121
|
+
print "Exitting FUSE #{@fuse.mountname}\n"
|
122
|
+
@fuse.exit
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# When the filesystem is accessed, the accessor's uid is returned
|
127
|
+
# You can use this in determining your permissions, or even provide different files
|
128
|
+
# for different users.
|
129
|
+
def self.reader_uid
|
130
|
+
Thread.current[:fusefs_reader_uid]
|
131
|
+
end
|
132
|
+
|
133
|
+
# When the filesystem is accessed, the accessor's gid is returned
|
134
|
+
def self.reader_gid
|
135
|
+
Thread.current[:fusefs_reader_gid]
|
136
|
+
end
|
137
|
+
|
138
|
+
# Not supported in RFuseFS (yet). The original FuseFS had special handling for editor
|
139
|
+
# swap/backup but this does not seem to be required, eg for the demo filesystems.
|
140
|
+
# If it is required it can be implemented in a filesystem
|
141
|
+
def self.handle_editor(bool)
|
142
|
+
#do nothing
|
143
|
+
end
|
144
|
+
|
145
|
+
# Defines convenience methods for path manipulation. You should typically inherit
|
146
|
+
# from here in your own directory projects
|
147
|
+
class FuseDir
|
148
|
+
|
149
|
+
# base,rest = split_path(path)
|
150
|
+
# @return [Array<String,String>] base,rest. base is the first directory in
|
151
|
+
# path, and rest is nil> or the remaining path.
|
152
|
+
# Typically if rest is not nil? you should
|
153
|
+
# recurse the paths
|
154
|
+
def split_path(path)
|
155
|
+
cur, *rest = path.scan(/[^\/]+/)
|
156
|
+
if rest.empty?
|
157
|
+
[ cur, nil ]
|
158
|
+
else
|
159
|
+
[ cur, File::SEPARATOR + File.join(rest) ]
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# base,*rest = scan_path(path)
|
164
|
+
# @return [Array<String>] all directory and file elements in path. Useful
|
165
|
+
# when encapsulating an entire fs into one object
|
166
|
+
def scan_path(path)
|
167
|
+
path.scan(/[^\/]+/)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
# This class is equivalent to using Object.new() as the virtual directory
|
173
|
+
# for target for FuseFS.start(). It exists only to document the API
|
174
|
+
#
|
175
|
+
# == Method call sequences
|
176
|
+
#
|
177
|
+
# === Stat (getattr)
|
178
|
+
#
|
179
|
+
# FUSE itself will generally stat referenced files and validate the results
|
180
|
+
# before performing any file/directory operations so this sequence is called
|
181
|
+
# very often
|
182
|
+
#
|
183
|
+
# 1. {#directory?} is checked first
|
184
|
+
# * {#can_write?} OR {#can_mkdir?} with .\_rfusefs_check\_ to determine write permissions
|
185
|
+
# * {#times} is called to determine atime,mtime,ctime info for the directory
|
186
|
+
#
|
187
|
+
# 2. {#file?} is checked next
|
188
|
+
# * {#can_write?}, {#executable?}, {#size}, {#times} are called to fill out the details
|
189
|
+
#
|
190
|
+
# 3. otherwise we tell FUSE that the path does not exist
|
191
|
+
#
|
192
|
+
# === List directory
|
193
|
+
#
|
194
|
+
# FUSE confirms the path is a directory (via stat above) before we call {#contents}
|
195
|
+
#
|
196
|
+
# FUSE will generally go on to stat each directory entry in the results
|
197
|
+
#
|
198
|
+
# === Reading files
|
199
|
+
#
|
200
|
+
# FUSE confirms path is a file before we call {#read_file}
|
201
|
+
#
|
202
|
+
# For fine control of file access see {#raw_open}, {#raw_read}, {#raw_close}
|
203
|
+
#
|
204
|
+
# === Writing files
|
205
|
+
#
|
206
|
+
# FUSE confirms path for the new file is a directory
|
207
|
+
#
|
208
|
+
# * {#can_write?} is checked at file open
|
209
|
+
# * {#write_to} is called when the file is flushed or closed
|
210
|
+
#
|
211
|
+
# See also {#raw_open}, {#raw_truncate}, {#raw_write}, {#raw_close}
|
212
|
+
#
|
213
|
+
# === Deleting files
|
214
|
+
#
|
215
|
+
# FUSE confirms path is a file before we call {#can_delete?} then {#delete}
|
216
|
+
#
|
217
|
+
# === Creating directories
|
218
|
+
#
|
219
|
+
# FUSE confirms parent is a directory before we call {#can_mkdir?} then {#mkdir}
|
220
|
+
#
|
221
|
+
# === Deleting directories
|
222
|
+
#
|
223
|
+
# FUSE confirms path is a directory before we call {#can_rmdir?} then {#rmdir}
|
224
|
+
#
|
225
|
+
# === Renaming files and directories
|
226
|
+
#
|
227
|
+
# FUSE confirms the rename is valid (eg. not renaming a directory to a file)
|
228
|
+
#
|
229
|
+
# * Try {#rename} to see if the virtual directory wants to handle this itself
|
230
|
+
# * If rename returns false/nil then we try to copy/delete (files only) ie.
|
231
|
+
# * {#file?}(from), {#can_write?}(to), {#can_delete?}(from) and if all true
|
232
|
+
# * {#read_file}(from), {#write_to}(to), {#delete}(from)
|
233
|
+
# * otherwise reject the rename
|
234
|
+
class API < FuseDir
|
235
|
+
|
236
|
+
# @return [Boolean] true if path is a directory
|
237
|
+
def directory?(path);end
|
238
|
+
|
239
|
+
# @return [Boolean] true if path is a file
|
240
|
+
def file?(path);end
|
241
|
+
|
242
|
+
# @return [Array<String>] array of file and directory names within path
|
243
|
+
def contents(path);return [];end
|
244
|
+
|
245
|
+
# @return [Boolean] true if path is an executable file
|
246
|
+
def executable?(path);end
|
247
|
+
|
248
|
+
# File size
|
249
|
+
# @return [Fixnum] the size in byte of a file (lots of applications rely on this being accurate )
|
250
|
+
def size(path);return 0;end
|
251
|
+
|
252
|
+
# File time information. RFuseFS extension.
|
253
|
+
# @return [Array<Fixnum>] a 3 element array [ atime, mtime. ctime ] (good for rsync etc)
|
254
|
+
def times(path);return INIT_TIMES;end
|
255
|
+
|
256
|
+
# @return [String] the contents of the file at path
|
257
|
+
def read_file(path);end
|
258
|
+
|
259
|
+
# @return [Boolean] true if the user can write to file at path
|
260
|
+
def can_write?(path);end
|
261
|
+
|
262
|
+
# Write the contents of str to file at path
|
263
|
+
def write_to(path,str);end
|
264
|
+
|
265
|
+
# @return [Boolean] true if the user can delete the file at path
|
266
|
+
def can_delete?(path);end
|
267
|
+
|
268
|
+
# Delete the file at path
|
269
|
+
def delete(path);end
|
270
|
+
|
271
|
+
# @return [Boolean] true if user can make a directory at path
|
272
|
+
def can_mkdir?(path);end
|
273
|
+
|
274
|
+
# Make a directory at path
|
275
|
+
def mkdir(path);end
|
276
|
+
|
277
|
+
# @return [Boolean] true if user can remove a directory at path
|
278
|
+
def can_rmdir?(path);end
|
279
|
+
|
280
|
+
# Remove the directory at path
|
281
|
+
def rmdir(path);end
|
282
|
+
|
283
|
+
# Neat toy. Called when a file is touched or has its timestamp explicitly modified
|
284
|
+
def touch(path,modtime);end
|
285
|
+
|
286
|
+
# Move a file or directory.
|
287
|
+
# @return [Object] non nil/false to indicate the rename has been handled,
|
288
|
+
# otherwise will fallback to copy/delete
|
289
|
+
def rename(from_path,to_path);end
|
290
|
+
|
291
|
+
# Raw file access
|
292
|
+
# @param mode [String] "r","w" or "rw", with "a" if file is opened for append
|
293
|
+
# @return [Object] a non nil object if you want lower level control of file operations
|
294
|
+
# Under RFuseFS this object will be passed back in to the other raw
|
295
|
+
# methods as the optional parameter _raw_
|
296
|
+
#
|
297
|
+
def raw_open(path,mode);end
|
298
|
+
|
299
|
+
# RFuseFS extension.
|
300
|
+
#
|
301
|
+
# Truncate file at path (or filehandle raw) to offset bytes. Called immediately after a file is opened
|
302
|
+
# for write without append.
|
303
|
+
#
|
304
|
+
# This method can also be invoked (without raw) outside of an open file context. See
|
305
|
+
# FUSE documentation on truncate() vs ftruncate()
|
306
|
+
def raw_truncate(path,off,raw=nil);end
|
307
|
+
|
308
|
+
# Read _sz_ bytes from file at path (or filehandle raw) starting at offset off
|
309
|
+
def raw_read(path,off,sz,raw=nil);end
|
310
|
+
|
311
|
+
# Write _sz_ bytes from file at path (or filehandle raw) starting at offset off
|
312
|
+
def raw_write(path,off,sz,buf,raw=nil);end
|
313
|
+
|
314
|
+
# Close the file previously opened at path (or filehandle raw)
|
315
|
+
def raw_close(path,raw=nil);end
|
316
|
+
|
317
|
+
end
|
318
|
+
end
|
data/samples/demo.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require 'rfusefs'
|
3
|
+
|
4
|
+
include FuseFS
|
5
|
+
|
6
|
+
root = MetaDir.new
|
7
|
+
|
8
|
+
# if (ARGV.size != 1)
|
9
|
+
# puts "Usage: #{$0} <directory>"
|
10
|
+
# exit
|
11
|
+
# end
|
12
|
+
|
13
|
+
dirname = ARGV.shift
|
14
|
+
|
15
|
+
unless File.directory?(dirname)
|
16
|
+
puts "Usage: #{$0} <directory>"
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
|
20
|
+
class Counter
|
21
|
+
def initialize
|
22
|
+
@counter = 0
|
23
|
+
end
|
24
|
+
def to_s
|
25
|
+
@counter += 1
|
26
|
+
@counter.to_s + "\n"
|
27
|
+
end
|
28
|
+
def size
|
29
|
+
@counter.to_s.size
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Randwords
|
34
|
+
def initialize(*ary)
|
35
|
+
@ary = ary.flatten
|
36
|
+
end
|
37
|
+
def to_s
|
38
|
+
@ary[rand(@ary.size)].to_s + "\n"
|
39
|
+
end
|
40
|
+
def size
|
41
|
+
@size ||= @ary.map{|v| v.size}.max
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
root.write_to('/hello',"Hello, World!\n")
|
46
|
+
|
47
|
+
progress = '.'
|
48
|
+
|
49
|
+
root.write_to('/progress',progress)
|
50
|
+
|
51
|
+
Thread.new do
|
52
|
+
20.times do
|
53
|
+
sleep 5
|
54
|
+
progress << '.'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
root.write_to('/counter',Counter.new)
|
59
|
+
root.write_to('/color',Randwords.new('red','blue','green','purple','yellow','bistre','burnt sienna','jade'))
|
60
|
+
root.write_to('/animal',Randwords.new('duck','dog','cat','duck billed platypus','silly fella'))
|
61
|
+
|
62
|
+
root.mkdir("/#{ENV['USER']}",DirLink.new(ENV['HOME']))
|
63
|
+
|
64
|
+
FuseFS.start(dirname,root,'nolocalcaches', *ARGV)
|
data/samples/dictfs.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# dictfs.rb
|
2
|
+
#
|
3
|
+
|
4
|
+
require "rubygems"
|
5
|
+
require 'fusefs'
|
6
|
+
include FuseFS
|
7
|
+
|
8
|
+
require 'dict'
|
9
|
+
|
10
|
+
class DictFS < FuseFS::FuseDir
|
11
|
+
def initialize
|
12
|
+
@servers = ['dict.org','alt0.dict.org']
|
13
|
+
@database = DICT::ALL_DATABASES
|
14
|
+
@strategy = 'exact'
|
15
|
+
@match_strategy = DICT::DEFAULT_MATCH_STRATEGY
|
16
|
+
@port = DICT::DEFAULT_PORT
|
17
|
+
|
18
|
+
@dict = DICT.new(@servers, @port, false, false)
|
19
|
+
@dict.client("%s v%s" % ["Dictionary","1.0"])
|
20
|
+
end
|
21
|
+
def contents(path)
|
22
|
+
# The 'readme' file
|
23
|
+
['readme']
|
24
|
+
end
|
25
|
+
def file?(path)
|
26
|
+
base, rest = split_path(path)
|
27
|
+
rest.nil? # DictFS doesn't have subdirs.
|
28
|
+
end
|
29
|
+
def read_file(path)
|
30
|
+
word, rest = split_path(path)
|
31
|
+
word.downcase!
|
32
|
+
if word == "readme"
|
33
|
+
return %Q[
|
34
|
+
DictFS: You may not see the files, but if you cat any file here, it will look
|
35
|
+
that file up on dict.org!
|
36
|
+
].lstrip
|
37
|
+
end
|
38
|
+
puts "Looking up #{word}"
|
39
|
+
m = @dict.match(@database, @strategy, word)
|
40
|
+
if m
|
41
|
+
contents = []
|
42
|
+
m.each do |db,words|
|
43
|
+
words.each do |w|
|
44
|
+
defs = @dict.define(db,w)
|
45
|
+
str = []
|
46
|
+
defs.each do |d|
|
47
|
+
str << "Definition of '#{w}' (by #{d.description})"
|
48
|
+
d.definition.each do |line|
|
49
|
+
str << " #{line.strip}"
|
50
|
+
end
|
51
|
+
contents << str.join("\n")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
contents << ''
|
56
|
+
contents.join("\n")
|
57
|
+
else
|
58
|
+
"No dictionary definitions found\n"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
if (File.basename($0) == File.basename(__FILE__))
|
64
|
+
if (ARGV.size != 1)
|
65
|
+
puts "Usage: #{$0} <directory>"
|
66
|
+
exit
|
67
|
+
end
|
68
|
+
|
69
|
+
dirname = ARGV.shift
|
70
|
+
|
71
|
+
unless File.directory?(dirname)
|
72
|
+
puts "Usage: #{dirname} is not a directory."
|
73
|
+
exit
|
74
|
+
end
|
75
|
+
|
76
|
+
root = DictFS.new
|
77
|
+
|
78
|
+
# Set the root FuseFS
|
79
|
+
FuseFS.set_root(root)
|
80
|
+
|
81
|
+
FuseFS.mount_under(dirname)
|
82
|
+
|
83
|
+
FuseFS.run # This doesn't return until we're unmounted.
|
84
|
+
end
|
data/samples/hello.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
class HelloDir
|
2
|
+
def contents(path)
|
3
|
+
['hello.txt']
|
4
|
+
end
|
5
|
+
|
6
|
+
def file?(path)
|
7
|
+
path == '/hello.txt'
|
8
|
+
end
|
9
|
+
|
10
|
+
def read_file(path)
|
11
|
+
"Hello, World!\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
def size(path)
|
15
|
+
read_file(path).size
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
if __FILE__ == $0
|
20
|
+
require 'rfusefs'
|
21
|
+
hellodir = HelloDir.new
|
22
|
+
mountpoint = ARGV.shift
|
23
|
+
FuseFS.start(mountpoint,hellodir)
|
24
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# openurifs.rb
|
2
|
+
#
|
3
|
+
|
4
|
+
require "rubygems"
|
5
|
+
require 'fusefs'
|
6
|
+
include FuseFS
|
7
|
+
|
8
|
+
require 'open-uri'
|
9
|
+
|
10
|
+
class OpenUriFS < FuseFS::FuseDir
|
11
|
+
def contents(path)
|
12
|
+
# The 'readme' file
|
13
|
+
[]
|
14
|
+
end
|
15
|
+
def directory?(path)
|
16
|
+
uri = scan_path(path)
|
17
|
+
fn = uri.pop
|
18
|
+
return true if fn =~ /\.(com|org|net|us|de|jp|ru|uk|biz|info)$/
|
19
|
+
return true if fn =~ /^\d+\.\d+\.\d+\.\d+$/
|
20
|
+
! (fn =~ /\./) # Does the last item doesn't contain a '.' ?
|
21
|
+
end
|
22
|
+
def file?(path)
|
23
|
+
!directory?(path)
|
24
|
+
end
|
25
|
+
def read_file(path)
|
26
|
+
proto, rest = split_path(path)
|
27
|
+
uri = "#{proto}://#{rest}"
|
28
|
+
open(uri).read
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
if (File.basename($0) == File.basename(__FILE__))
|
33
|
+
if (ARGV.size != 1)
|
34
|
+
puts "Usage: #{$0} <directory>"
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
dirname = ARGV.shift
|
39
|
+
|
40
|
+
unless File.directory?(dirname)
|
41
|
+
puts "Usage: #{dirname} is not a directory."
|
42
|
+
exit
|
43
|
+
end
|
44
|
+
|
45
|
+
root = OpenUriFS.new
|
46
|
+
|
47
|
+
# Set the root FuseFS
|
48
|
+
FuseFS.set_root(root)
|
49
|
+
|
50
|
+
FuseFS.mount_under(dirname)
|
51
|
+
|
52
|
+
FuseFS.run # This doesn't return until we're unmounted.
|
53
|
+
end
|
data/samples/railsfs.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# RailsFS, as written by _why_the_lucky_stiff
|
4
|
+
#
|
5
|
+
# Full instructions:
|
6
|
+
# http://redhanded.hobix.com/inspect/railsfsAfterACoupleMinutesOfToolingWithFuseWhoa.html
|
7
|
+
#
|
8
|
+
=begin
|
9
|
+
Instructions cut and paste from _why's blog:
|
10
|
+
|
11
|
+
Save the railfs.rb script as script/filesys in your Rails app. (If you'd
|
12
|
+
rather not cut-and-paste the above, it's here.)
|
13
|
+
|
14
|
+
Now, run mkdir ~/railsmnt. Then, script/filesys ~/railsmnt. The rules are as
|
15
|
+
follows:
|
16
|
+
|
17
|
+
* ls ~/railsmnt will give you a list of tables.
|
18
|
+
* ls ~/railsmnt/table will list IDs from the table.
|
19
|
+
* cat ~/railsmnt/table/id will display a record in YAML.
|
20
|
+
* vim ~/railsmnt/table/id to edit the record in YAML!
|
21
|
+
|
22
|
+
=end
|
23
|
+
|
24
|
+
require "rubygems"
|
25
|
+
require 'fusefs'
|
26
|
+
require File.dirname(__FILE__) + '/../config/environment'
|
27
|
+
|
28
|
+
class RailsFS < FuseFS::FuseDir
|
29
|
+
def initialize
|
30
|
+
@classes = {}
|
31
|
+
require 'find'
|
32
|
+
Find.find( File.join(RAILS_ROOT, 'app/models') ) do |model|
|
33
|
+
if /(\w+)\.rb$/ =~ model
|
34
|
+
model = $1
|
35
|
+
( @classes[model] = Kernel::const_get( Inflector.classify( model ) ) ).
|
36
|
+
find :first rescue @classes.delete( model )
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
def directory? path
|
41
|
+
tname, key = scan_path path
|
42
|
+
table = @classes[tname]
|
43
|
+
if table.nil?; false # /table
|
44
|
+
elsif key; false # /table/id
|
45
|
+
else; true end
|
46
|
+
end
|
47
|
+
def file? path
|
48
|
+
tname, key = scan_path path
|
49
|
+
table = @classes[tname]
|
50
|
+
key and table and table.find( key )
|
51
|
+
end
|
52
|
+
def can_delete?; true end
|
53
|
+
def can_write? path; file? path end
|
54
|
+
def contents path
|
55
|
+
tname, key = scan_path path
|
56
|
+
table = @classes[tname]
|
57
|
+
if tname.nil?; @classes.keys.sort # /
|
58
|
+
else; table.find( :all ).map { |o| o.id.to_s } end # /table
|
59
|
+
end
|
60
|
+
def write_to path, body
|
61
|
+
obj = YAML::load( body )
|
62
|
+
obj.save
|
63
|
+
end
|
64
|
+
def read_file path
|
65
|
+
tname, key = scan_path path
|
66
|
+
table = @classes[tname]
|
67
|
+
YAML::dump( table.find( key ) )
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
if (File.basename($0) == File.basename(__FILE__))
|
72
|
+
root = RailsFS.new
|
73
|
+
FuseFS.set_root(root)
|
74
|
+
FuseFS.mount_under(ARGV[0])
|
75
|
+
FuseFS.run # This doesn't return until we're unmounted.
|
76
|
+
end
|
77
|
+
|