rfusefs 1.0.0 → 1.0.1.RC0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -1
- data/.travis.yml +8 -0
- data/README.rdoc +13 -21
- data/Rakefile +1 -0
- data/lib/fuse/fusedir.rb +15 -0
- data/lib/fuse/rfusefs-fuse.rb +26 -8
- data/lib/fusefs/metadir.rb +2 -1
- data/lib/fusefs/pathmapper.rb +185 -56
- data/lib/fusefs/sqlitemapper.rb +125 -0
- data/lib/rfusefs/version.rb +1 -1
- data/lib/rfusefs.rb +55 -1
- data/rfusefs.gemspec +30 -0
- data/spec/metadir_spec.rb +24 -24
- data/spec/mount_unmount_spec.rb +21 -0
- data/spec/pathmapper_spec.rb +143 -53
- data/spec/rfusefs_spec.rb +23 -1
- data/spec/sample_spec.rb +9 -9
- data/spec/spec_helper.rb +0 -5
- data/spec/sqlitemapper_spec.rb +135 -0
- metadata +49 -9
data/.gitignore
CHANGED
data/.travis.yml
ADDED
data/README.rdoc
CHANGED
@@ -16,22 +16,7 @@ RFuseFS is api compatible with FuseFS (0.7.0)
|
|
16
16
|
FuseFS provides a layer of abstraction to a programmer who wants to create a
|
17
17
|
virtual filesystem via FUSE.
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
1. FuseFS, which is defined in 'rfusefs.rb'
|
22
|
-
2. An object that defines a virtual directory by subclassing {FuseFS::FuseDir}
|
23
|
-
|
24
|
-
To write a FuseFS program, you must:
|
25
|
-
|
26
|
-
* Define and create a Directory object that responds to the methods required
|
27
|
-
by FuseFS for its desired use.
|
28
|
-
|
29
|
-
* Call
|
30
|
-
|
31
|
-
FuseFS.start <virtualdir> <mountpoint> [mount_options]
|
32
|
-
|
33
|
-
where <virtualdir> with an object defining your virtual directory.
|
34
|
-
and <mountpoint> is a real directory on your filesystem
|
19
|
+
First define a virtual directory by subclassing {FuseFS::FuseDir}
|
35
20
|
|
36
21
|
See samples under /samples and also the following starter classes
|
37
22
|
|
@@ -39,13 +24,19 @@ See samples under /samples and also the following starter classes
|
|
39
24
|
* {FuseFS::MetaDir}
|
40
25
|
* {FuseFS::DirLink}
|
41
26
|
* {FuseFS::PathMapperFS}
|
27
|
+
* {FuseFS::SqliteMapperFS}
|
42
28
|
|
43
|
-
|
29
|
+
Then start your filesystem with
|
30
|
+
|
31
|
+
* {FuseFS.start}
|
32
|
+
* {FuseFS.main}
|
33
|
+
|
34
|
+
Finally to use the filesystem open up your favourite file browser/terminal and
|
44
35
|
explore the contents under <mountpoint>
|
45
36
|
|
46
37
|
Happy Filesystem Hacking!
|
47
38
|
|
48
|
-
=== the hello world filesystem in
|
39
|
+
=== the hello world filesystem in 16 LOC
|
49
40
|
|
50
41
|
require 'rfusefs'
|
51
42
|
|
@@ -59,12 +50,13 @@ Happy Filesystem Hacking!
|
|
59
50
|
def read_file(path)
|
60
51
|
"Hello, World!\n"
|
61
52
|
end
|
53
|
+
def size(path)
|
54
|
+
read_file(path).size
|
55
|
+
end
|
62
56
|
end
|
63
57
|
|
64
|
-
hellodir = HelloDir.new
|
65
|
-
|
66
58
|
# Usage: #{$0} mountpoint [mount_options]
|
67
|
-
FuseFS.
|
59
|
+
FuseFS.main() { |options| HelloDir.new }
|
68
60
|
|
69
61
|
== REQUIREMENTS:
|
70
62
|
|
data/Rakefile
CHANGED
data/lib/fuse/fusedir.rb
CHANGED
@@ -196,6 +196,21 @@ module FuseFS
|
|
196
196
|
# @abstract FuseFS api
|
197
197
|
def raw_close(path,raw=nil);end
|
198
198
|
|
199
|
+
# RFuseFS extension.
|
200
|
+
# Extended attributes. These will be set/retrieved/removed directly
|
201
|
+
# @param [String] path
|
202
|
+
# @return [Hash] extended attributes for this path
|
203
|
+
# @abstract FuseFS api
|
204
|
+
def xattr(path); return {}; end
|
205
|
+
|
206
|
+
# RFuseFS extension.
|
207
|
+
# Called when the filesystem is mounted
|
208
|
+
def mounted();end
|
209
|
+
|
210
|
+
# RFuseFS extension.
|
211
|
+
# Called when the filesystem is unmounted
|
212
|
+
def unmounted();end
|
213
|
+
|
199
214
|
end
|
200
215
|
|
201
216
|
DEFAULT_FS = FuseDir.new()
|
data/lib/fuse/rfusefs-fuse.rb
CHANGED
@@ -371,17 +371,27 @@ module FuseFS
|
|
371
371
|
#def link(path,as)
|
372
372
|
#end
|
373
373
|
|
374
|
-
|
375
|
-
|
374
|
+
def setxattr(ctx,path,name,value)
|
375
|
+
return wrap_context(ctx,__method__,path,name,value) if ctx
|
376
|
+
@root.xattr(path)[name]=value
|
377
|
+
end
|
376
378
|
|
377
|
-
|
378
|
-
|
379
|
+
def getxattr(ctx,path,name)
|
380
|
+
return wrap_context(ctx,__method__,path,name) if ctx
|
381
|
+
result = @root.xattr(path)[name]
|
382
|
+
raise Errno::ENODATA.new("No attribute #{name}") unless result
|
383
|
+
result
|
384
|
+
end
|
379
385
|
|
380
|
-
|
381
|
-
|
386
|
+
def listxattr(ctx,path)
|
387
|
+
return wrap_context(ctx,__method__,path) if ctx
|
388
|
+
@root.xattr(path).keys
|
389
|
+
end
|
382
390
|
|
383
|
-
|
384
|
-
|
391
|
+
def removexattr(ctx,path,name)
|
392
|
+
return wrap_context(ctx,__method__,path,name) if ctx
|
393
|
+
@root.xattr(path).delete(name)
|
394
|
+
end
|
385
395
|
|
386
396
|
#def opendir(path,ffi)
|
387
397
|
#end
|
@@ -396,6 +406,14 @@ module FuseFS
|
|
396
406
|
#def statfs(path)
|
397
407
|
#end
|
398
408
|
|
409
|
+
def mounted()
|
410
|
+
@root.mounted()
|
411
|
+
end
|
412
|
+
|
413
|
+
def unmounted()
|
414
|
+
@root.unmounted()
|
415
|
+
end
|
416
|
+
|
399
417
|
def self.context(ctx,&block)
|
400
418
|
begin
|
401
419
|
Thread.current[:fusefs_reader_uid] = ctx.uid
|
data/lib/fusefs/metadir.rb
CHANGED
@@ -189,7 +189,8 @@ module FuseFS
|
|
189
189
|
end
|
190
190
|
|
191
191
|
default_methods = FuseDir.public_instance_methods.select { |m|
|
192
|
-
!
|
192
|
+
![:mounted,:unmounted].include?(m) &&
|
193
|
+
!self.public_method_defined?(m) && FuseDir.instance_method(m).owner == FuseDir
|
193
194
|
}
|
194
195
|
|
195
196
|
default_methods.each do |m|
|
data/lib/fusefs/pathmapper.rb
CHANGED
@@ -1,10 +1,78 @@
|
|
1
1
|
|
2
2
|
module FuseFS
|
3
3
|
|
4
|
-
# A FuseFS that maps files from
|
4
|
+
# A FuseFS that maps files from their original location into a new path
|
5
5
|
# eg tagged audio files can be mapped by title etc...
|
6
|
+
#
|
6
7
|
class PathMapperFS < FuseDir
|
7
|
-
|
8
|
+
|
9
|
+
# Represents a mappted file or directory
|
10
|
+
class MNode
|
11
|
+
|
12
|
+
# @return [Hash<String,MNode>] list of files in a directory, nil for file nodes
|
13
|
+
attr_reader :files
|
14
|
+
|
15
|
+
# @return [MNode] parent directory
|
16
|
+
attr_reader :parent
|
17
|
+
|
18
|
+
# @return [Hash] metadata for this node
|
19
|
+
attr_reader :options
|
20
|
+
|
21
|
+
# @return [String] path to backing file, or nil for directory nodes
|
22
|
+
attr_reader :real_path
|
23
|
+
|
24
|
+
# @!visibility private
|
25
|
+
def initialize(parent_dir)
|
26
|
+
@parent = parent_dir
|
27
|
+
@files = {}
|
28
|
+
@options = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
# @!visibility private
|
32
|
+
def init_file(real_path,options)
|
33
|
+
@options = options
|
34
|
+
@real_path = real_path
|
35
|
+
@files = nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# @return [Boolean] true if node represents a file, otherwise false
|
39
|
+
def file?
|
40
|
+
real_path && true
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [Boolean] true if node represents a directory, otherwise false
|
44
|
+
def directory?
|
45
|
+
files && true
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Boolean] true if node is the root directory
|
49
|
+
def root?
|
50
|
+
@parent.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Compatibility and convenience method
|
54
|
+
# @param [:pm_real_path,String,Symbol] key
|
55
|
+
# @return [String] {#real_path} if key == :pm_real_path
|
56
|
+
# @return [MNode] the node representing the file named key
|
57
|
+
# @return [Object] shortcut for {#options}[key]
|
58
|
+
def[](key)
|
59
|
+
case key
|
60
|
+
when :pm_real_path
|
61
|
+
real_path
|
62
|
+
when String
|
63
|
+
files[key]
|
64
|
+
else
|
65
|
+
options[key]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# Convenience method to set metadata into {#options}
|
70
|
+
def[]=(key,value)
|
71
|
+
options[key]=value
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Convert FuseFS raw_mode strings to IO open mode strings
|
8
76
|
def self.open_mode(raw_mode)
|
9
77
|
case raw_mode
|
10
78
|
when "r"
|
@@ -12,107 +80,163 @@ module FuseFS
|
|
12
80
|
when "ra"
|
13
81
|
"r" #not really sensible..
|
14
82
|
when "rw"
|
15
|
-
"
|
83
|
+
"r+"
|
16
84
|
when "rwa"
|
17
85
|
"a+"
|
18
|
-
when
|
86
|
+
when "w"
|
19
87
|
"w"
|
20
88
|
when "wa"
|
21
89
|
"a"
|
22
90
|
end
|
23
91
|
end
|
24
|
-
|
25
|
-
#
|
26
|
-
#
|
92
|
+
|
93
|
+
# should raw file access should be used - useful for binary files
|
94
|
+
# @return [Boolean]
|
95
|
+
# default is false
|
96
|
+
attr_accessor :use_raw_file_access
|
97
|
+
|
98
|
+
# should filesystem support writing through to the real files
|
99
|
+
# @return [Boolean]
|
100
|
+
# default is false
|
101
|
+
attr_accessor :allow_write
|
102
|
+
|
103
|
+
# Creates a new Path Mapper filesystem over an existing directory
|
104
|
+
# @param [String] dir
|
105
|
+
# @param [Hash] options
|
106
|
+
# @yieldparam [String] file path to map
|
107
|
+
# @yieldreturn [String]
|
108
|
+
# @see #initialize
|
109
|
+
# @see #map_directory
|
27
110
|
def PathMapperFS.create(dir,options={ },&block)
|
28
111
|
pm_fs = PathMapperFS.new(options)
|
29
|
-
pm_fs.
|
30
|
-
block.call(file)
|
31
|
-
end
|
112
|
+
pm_fs.map_directory(dir,&block)
|
32
113
|
return pm_fs
|
33
114
|
end
|
34
115
|
|
116
|
+
# Create a new Path Mapper filesystem
|
117
|
+
# @param [Hash] options
|
118
|
+
# @option options [Boolean] :use_raw_file_access
|
119
|
+
# @option options [Boolean] :allow_write
|
35
120
|
def initialize(options = { })
|
36
|
-
@root =
|
121
|
+
@root = MNode.new(nil)
|
37
122
|
@use_raw_file_access = options[:use_raw_file_access]
|
38
123
|
@allow_write = options[:allow_write]
|
39
124
|
end
|
125
|
+
|
126
|
+
# Recursively find all files and map according to the given block
|
127
|
+
# @param [String...] dirs directories to list
|
128
|
+
# @yieldparam [String] file path to map
|
129
|
+
# @yieldreturn [String] the mapped path
|
130
|
+
# @yieldreturn nil to skip mapping this file
|
131
|
+
def map_directory(*dirs)
|
132
|
+
require 'find'
|
133
|
+
Find.find(*dirs) do |file|
|
134
|
+
new_path = yield file
|
135
|
+
map_file(file,new_path) if new_path
|
136
|
+
end
|
137
|
+
end
|
138
|
+
alias :mapDirectory :map_directory
|
40
139
|
|
41
|
-
|
140
|
+
|
141
|
+
# Add (or replace) a mapped file
|
42
142
|
#
|
43
|
-
#
|
44
|
-
|
143
|
+
# @param [String] real_path pointing at the real file location
|
144
|
+
# @param [String] new_path the mapped path
|
145
|
+
# @param [Hash<Symbol,Object>] options metadata for this path
|
146
|
+
# @option options [Hash<String,String>] :xattr hash to be used as extended attributes
|
147
|
+
# @return [MNode]
|
148
|
+
# a node representing the mapped path. See {#node}
|
149
|
+
def map_file(real_path,new_path,options = {})
|
45
150
|
#split path into components
|
46
|
-
components = new_path.scan(/[^\/]+/)
|
151
|
+
components = new_path.to_s.scan(/[^\/]+/)
|
47
152
|
|
48
153
|
#create a hash of hashes to represent our directory structure
|
49
|
-
new_file = components.inject(@root) { |
|
50
|
-
|
154
|
+
new_file = components.inject(@root) { |parent_dir, file|
|
155
|
+
parent_dir.files[file] ||= MNode.new(parent_dir)
|
51
156
|
}
|
52
|
-
new_file
|
157
|
+
new_file.init_file(real_path,options)
|
158
|
+
|
53
159
|
return new_file
|
54
160
|
end
|
161
|
+
alias :mapFile :map_file
|
162
|
+
|
163
|
+
# Retrieve in memory node for a mapped path
|
164
|
+
#
|
165
|
+
# @param [String] path
|
166
|
+
# @return [MNode] in memory node at path
|
167
|
+
# @return nil if path does not exist in the filesystem
|
168
|
+
def node(path)
|
169
|
+
path_components = scan_path(path)
|
55
170
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
mapFile(file,new_path) if new_path
|
62
|
-
end
|
171
|
+
#not actually injecting anything here, we're just following the hash of hashes...
|
172
|
+
path_components.inject(@root) { |dir,file|
|
173
|
+
break unless dir.files[file]
|
174
|
+
dir.files[file]
|
175
|
+
}
|
63
176
|
end
|
64
177
|
|
65
178
|
# Takes a mapped file name and returns the original real_path
|
66
179
|
def unmap(path)
|
67
|
-
|
68
|
-
|
180
|
+
node = node(path)
|
181
|
+
(node && node.file?) ? node.real_path : nil
|
69
182
|
end
|
183
|
+
|
184
|
+
# Deletes files and directories.
|
185
|
+
# Yields each {#node} in the filesystem and deletes it if the block returns true
|
186
|
+
#
|
187
|
+
# Useful if your filesystem is periodically remapping the entire contents and you need
|
188
|
+
# to delete entries that have not been touched in the latest scan
|
189
|
+
#
|
190
|
+
# @yieldparam [Hash] filesystem node
|
191
|
+
# @yieldreturn [true,false] should this node be deleted
|
192
|
+
def cleanup(&block)
|
193
|
+
recursive_cleanup(@root,&block)
|
194
|
+
end
|
195
|
+
|
70
196
|
|
71
|
-
#
|
72
|
-
# See FuseFS API.txt
|
197
|
+
# @!visibility private
|
73
198
|
def directory?(path)
|
74
199
|
possible_dir = node(path)
|
75
|
-
possible_dir &&
|
200
|
+
possible_dir && possible_dir.directory?
|
76
201
|
end
|
77
202
|
|
78
|
-
#
|
79
|
-
# expects to be called only if directory? returns true
|
203
|
+
# @!visibility private
|
80
204
|
def contents(path)
|
81
|
-
node(path).keys
|
205
|
+
node(path).files.keys
|
82
206
|
end
|
83
207
|
|
84
|
-
#
|
208
|
+
# @!visibility private
|
85
209
|
def file?(path)
|
86
210
|
filename = unmap(path)
|
87
211
|
filename && File.file?(filename)
|
88
212
|
end
|
89
213
|
|
90
|
-
#
|
214
|
+
# @!visibility private
|
91
215
|
# only called if option :raw_reads is not set
|
92
216
|
def read_file(path)
|
93
217
|
IO.read(unmap(path))
|
94
218
|
end
|
95
219
|
|
220
|
+
# @!visibility private
|
96
221
|
# We can only write to existing files
|
97
222
|
# because otherwise we don't have anything to back it
|
98
223
|
def can_write?(path)
|
99
224
|
@allow_write && file?(path)
|
100
225
|
end
|
101
226
|
|
102
|
-
#
|
103
|
-
# and we don't open the file for writing
|
227
|
+
# @!visibility private
|
104
228
|
def write_to(path,contents)
|
105
|
-
File.open(path) do |f|
|
229
|
+
File.open(unmap(path),"w") do |f|
|
106
230
|
f.print(contents)
|
107
231
|
end
|
108
232
|
end
|
109
233
|
|
110
|
-
#
|
234
|
+
# @!visibility private
|
111
235
|
def size(path)
|
112
236
|
File.size(unmap(path))
|
113
237
|
end
|
114
238
|
|
115
|
-
#
|
239
|
+
# @!visibility private
|
116
240
|
def times(path)
|
117
241
|
realpath = unmap(path)
|
118
242
|
if (realpath)
|
@@ -124,7 +248,12 @@ module FuseFS
|
|
124
248
|
end
|
125
249
|
end
|
126
250
|
|
127
|
-
#
|
251
|
+
# @!visibility private
|
252
|
+
def xattr(path)
|
253
|
+
result = node(path).options[:xattr] || {}
|
254
|
+
end
|
255
|
+
|
256
|
+
# @!visibility private
|
128
257
|
# Will create, store and return a File object for the underlying file
|
129
258
|
# for subsequent use with the raw_read/raw_close methods
|
130
259
|
# expects file? to return true before this method is called
|
@@ -132,7 +261,7 @@ module FuseFS
|
|
132
261
|
|
133
262
|
return false unless @use_raw_file_access
|
134
263
|
|
135
|
-
return false if mode.include?("w") && (!@
|
264
|
+
return false if mode.include?("w") && (!@allow_write)
|
136
265
|
|
137
266
|
@openfiles ||= Hash.new() unless rfusefs
|
138
267
|
|
@@ -154,21 +283,21 @@ module FuseFS
|
|
154
283
|
return file
|
155
284
|
end
|
156
285
|
|
157
|
-
#
|
286
|
+
# @!visibility private
|
158
287
|
def raw_read(path,off,sz,file=nil)
|
159
288
|
file = @openfiles[path] unless file
|
160
289
|
file.sysseek(off)
|
161
290
|
file.sysread(sz)
|
162
291
|
end
|
163
292
|
|
164
|
-
#
|
293
|
+
# @!visibility private
|
165
294
|
def raw_write(path,offset,sz,buf,file=nil)
|
166
295
|
file = @openfiles[path] unless file
|
167
|
-
file.sysseek(
|
296
|
+
file.sysseek(offset)
|
168
297
|
file.syswrite(buf[0,sz])
|
169
298
|
end
|
170
299
|
|
171
|
-
#
|
300
|
+
# @!visibility private
|
172
301
|
def raw_close(path,file=nil)
|
173
302
|
unless file
|
174
303
|
file = @openfiles.delete(path)
|
@@ -177,16 +306,16 @@ module FuseFS
|
|
177
306
|
end
|
178
307
|
|
179
308
|
private
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
309
|
+
|
310
|
+
def recursive_cleanup(dir_node,&block)
|
311
|
+
dir_node.files.delete_if do |path,child|
|
312
|
+
if child.file?
|
313
|
+
yield child
|
314
|
+
else
|
315
|
+
recursive_cleanup(child,&block)
|
316
|
+
child.files.size == 0
|
317
|
+
end
|
318
|
+
end
|
190
319
|
end
|
191
320
|
end
|
192
321
|
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'fusefs/pathmapper'
|
2
|
+
require 'sqlite3'
|
3
|
+
require 'rb-inotify'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
module FuseFS
|
7
|
+
|
8
|
+
class SqliteMapperFS < PathMapperFS
|
9
|
+
|
10
|
+
# The database connection
|
11
|
+
attr_reader :db
|
12
|
+
|
13
|
+
# Maintains a count of the number of times through the scan loop
|
14
|
+
attr_reader :scan_id
|
15
|
+
|
16
|
+
#
|
17
|
+
#
|
18
|
+
# @param [String] db_path Path to Sqlite database
|
19
|
+
# @param [String] sql query
|
20
|
+
# @param [Hash] options see {PathMapperFS#initialize}
|
21
|
+
# @yieldparam [Row] row to map
|
22
|
+
# @yieldreturn [String,String,Hash<Symbol,Object>] newpath, realpath, options
|
23
|
+
# * newpath - the mapped path
|
24
|
+
# * realpath - path to the real file
|
25
|
+
# * options - additional information to store with this path
|
26
|
+
def initialize(db_path,sql,options = { },&row_mapper)
|
27
|
+
@db_path = db_path.to_s
|
28
|
+
@sql = sql.to_s
|
29
|
+
define_singleton_method(:map_row,row_mapper) if block_given?
|
30
|
+
super(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Maps a row into a new filepath
|
34
|
+
#
|
35
|
+
# @param [Hash] row sqlite result hash for a row
|
36
|
+
# @return [String,String,Hash<Symbol,Object>] newpath, realpath, options
|
37
|
+
# * newpath - the mapped path
|
38
|
+
# * realpath - path to the real file
|
39
|
+
# * options - additional information to store with this path
|
40
|
+
# @abstract
|
41
|
+
def map_row(row)
|
42
|
+
raise NotImplementedError, "abstract method #{__method__} not implemented"
|
43
|
+
end
|
44
|
+
|
45
|
+
# FuseFS callback when the filesystem is mounted
|
46
|
+
# performs the initial scan and starts watching the database for changes
|
47
|
+
# @api FuseFS
|
48
|
+
def mounted()
|
49
|
+
@mutex = Mutex.new
|
50
|
+
@cv = ConditionVariable.new
|
51
|
+
@mounted = true
|
52
|
+
|
53
|
+
notifier = start_notifier
|
54
|
+
|
55
|
+
|
56
|
+
@scan_thread = Thread.new() do
|
57
|
+
scan_loop(notifier)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# FuseFS callback when filesystem is unmounted
|
62
|
+
#
|
63
|
+
# Stops the database watching threads
|
64
|
+
# @api FuseFS
|
65
|
+
def unmounted()
|
66
|
+
@mounted = false
|
67
|
+
@mutex.synchronize { @cv.signal }
|
68
|
+
@scan_thread.join
|
69
|
+
end
|
70
|
+
|
71
|
+
# Executes the sql query and passes each row to map_row (or the block passed in {#initialize})
|
72
|
+
#
|
73
|
+
# Subclasses can override this method for pre/post scan processing, calling super as required
|
74
|
+
def scan()
|
75
|
+
db.execute(@sql) do |row|
|
76
|
+
new_path, real_path, options = map_row(row)
|
77
|
+
options ||= {}
|
78
|
+
options[:sqlite_scan_id] = @scan_id
|
79
|
+
map_file(new_path, real_path, options)
|
80
|
+
end
|
81
|
+
cleanup() { |file_node| file_node.options[:sqlite_scan_id] != @scan_id }
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def start_notifier
|
87
|
+
notifier = INotify::Notifier.new()
|
88
|
+
modified = false
|
89
|
+
notifier.watch(@db_path,:modify, :close_write) do |event|
|
90
|
+
modified = true if event.flags.include?(:modify)
|
91
|
+
if event.flags.include?(:close_write) && modified
|
92
|
+
@mutex.synchronize {@cv.signal}
|
93
|
+
modified = false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
Thread.new { notifier.run }
|
98
|
+
|
99
|
+
notifier
|
100
|
+
end
|
101
|
+
|
102
|
+
def scan_loop(notifier)
|
103
|
+
@mutex.synchronize() do
|
104
|
+
@scan_id = 0
|
105
|
+
while @mounted
|
106
|
+
begin
|
107
|
+
@db = SQLite3::Database.new(@db_path,:readonly => true)
|
108
|
+
@db.results_as_hash = true
|
109
|
+
@db.busy_timeout(10000)
|
110
|
+
@scan_id = @scan_id + 1
|
111
|
+
scan()
|
112
|
+
rescue StandardError => e
|
113
|
+
puts e
|
114
|
+
puts e.backtrace.join("\n")
|
115
|
+
ensure
|
116
|
+
@db.close unless @db.closed?
|
117
|
+
@db = nil
|
118
|
+
end
|
119
|
+
@cv.wait(@mutex)
|
120
|
+
end
|
121
|
+
notifier.stop
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
data/lib/rfusefs/version.rb
CHANGED