io 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,10 @@
1
+ require 'rake_ext'
2
+
3
+ project(
4
+ name: "io",
5
+ gem: true,
6
+ summary: "Virtual File System",
7
+
8
+ author: "Alexey Petrushin",
9
+ homepage: "http://github.com/alexeypetrushin/vfs"
10
+ )
@@ -0,0 +1,20 @@
1
+ require 'fileutils'
2
+ require 'set'
3
+
4
+ %w(
5
+ path
6
+ error
7
+
8
+ entries/entry
9
+ entries/file
10
+ entries/dir
11
+ entries/universal_entry
12
+
13
+ entry_proxy
14
+
15
+ drivers/local
16
+
17
+ integration
18
+
19
+ vfs
20
+ ).each{|f| require "vfs/#{f}"}
@@ -0,0 +1,175 @@
1
+ require 'tempfile'
2
+
3
+ module Vfs
4
+ module Drivers
5
+ class Local
6
+ class Writer
7
+ def initialize out; @out = out end
8
+
9
+ def write data; @out.write data end
10
+ end
11
+
12
+ DEFAULT_BUFFER = 1000 * 1024
13
+
14
+ def initialize root = ''
15
+ @root = root
16
+ end
17
+
18
+ def open &block
19
+ block.call self if block
20
+ end
21
+ def close; end
22
+
23
+ attr_writer :buffer
24
+ def buffer
25
+ @buffer || DEFAULT_BUFFER
26
+ end
27
+
28
+ #
29
+ # Attributes
30
+ #
31
+ def attributes path
32
+ path = with_root path
33
+
34
+ stat = ::File.stat path
35
+ attrs = {}
36
+ attrs[:file] = !!stat.file?
37
+ attrs[:dir] = !!stat.directory?
38
+
39
+ # attributes special for file system
40
+ attrs[:created_at] = stat.ctime
41
+ attrs[:updated_at] = stat.mtime
42
+ attrs[:size] = stat.size if attrs[:file]
43
+ attrs
44
+ rescue Errno::ENOTDIR
45
+ nil
46
+ rescue Errno::ENOENT
47
+ nil
48
+ end
49
+
50
+ def set_attributes path, attrs
51
+ # TODO2 set attributes
52
+ not_implemented
53
+ end
54
+
55
+
56
+ #
57
+ # File
58
+ #
59
+ def read_file path, &block
60
+ path = with_root path
61
+ ::File.open path, 'r' do |is|
62
+ while buff = is.gets(self.buffer || DEFAULT_BUFFER)
63
+ block.call buff
64
+ end
65
+ end
66
+ end
67
+
68
+ def write_file original_path, append, &block
69
+ path = with_root original_path
70
+
71
+ option = append ? 'a' : 'w'
72
+ ::File.open path, option do |out|
73
+ block.call Writer.new(out)
74
+ end
75
+ end
76
+
77
+ def delete_file path
78
+ path = with_root path
79
+ ::File.delete path
80
+ end
81
+
82
+ # def move_file from, to
83
+ # FileUtils.mv from, to
84
+ # end
85
+
86
+
87
+ #
88
+ # Dir
89
+ #
90
+ def create_dir path
91
+ path = with_root path
92
+ ::Dir.mkdir path
93
+ end
94
+
95
+ def delete_dir original_path
96
+ path = with_root original_path
97
+ ::FileUtils.rm_r path
98
+ end
99
+
100
+ def each_entry path, query, &block
101
+ path = with_root path
102
+
103
+ if query
104
+ path_with_trailing_slash = path == '/' ? path : "#{path}/"
105
+ ::Dir["#{path_with_trailing_slash}#{query}"].each do |absolute_path|
106
+ name = absolute_path.sub path_with_trailing_slash, ''
107
+ block.call name, ->{::File.directory?(absolute_path) ? :dir : :file}
108
+ # if ::File.directory? absolute_path
109
+ # block.call relative_path, :dir
110
+ # else
111
+ # block.call relative_path, :file
112
+ # end
113
+ end
114
+ else
115
+ ::Dir.foreach path do |name|
116
+ next if name == '.' or name == '..'
117
+ block.call name, ->{::File.directory?("#{path}/#{name}") ? :dir : :file}
118
+ # if ::File.directory? "#{path}/#{relative_name}"
119
+ # block.call relative_name, :dir
120
+ # else
121
+ # block.call relative_name, :file
122
+ # end
123
+ end
124
+ end
125
+ end
126
+
127
+ # def efficient_dir_copy from, to, override
128
+ # return false if override # FileUtils.cp_r doesn't support this behaviour
129
+ #
130
+ # from.driver.open_fs do |from_fs|
131
+ # to.driver.open_fs do |to_fs|
132
+ # if from_fs.local? and to_fs.local?
133
+ # FileUtils.cp_r from.path, to.path
134
+ # true
135
+ # else
136
+ # false
137
+ # end
138
+ # end
139
+ # end
140
+ # end
141
+
142
+ #
143
+ # Other
144
+ #
145
+ def local?; true end
146
+
147
+ def tmp &block
148
+ path = "/tmp/#{rand(10**6)}"
149
+ # tmp_dir = "#{::Dir.tmpdir}/#{rand(10**6)}"
150
+ if block
151
+ begin
152
+ ::FileUtils.mkdir_p with_root(path)
153
+ block.call path
154
+ ensure
155
+ ::FileUtils.rm_r with_root(path) if ::File.exist? with_root(path)
156
+ end
157
+ else
158
+ ::FileUtils.mkdir_p with_root(path)
159
+ path
160
+ end
161
+ end
162
+
163
+ def to_s; '' end
164
+
165
+ protected
166
+ def root
167
+ @root || raise('root not defined!')
168
+ end
169
+
170
+ def with_root path
171
+ path == '/' ? root : root + path
172
+ end
173
+ end
174
+ end
175
+ end
@@ -0,0 +1,169 @@
1
+ require 'rspec_ext'
2
+ require 'ruby_ext'
3
+
4
+ shared_examples_for 'vfs driver basic' do
5
+ it 'should respond to :local?' do
6
+ @driver.should respond_to(:local?)
7
+ end
8
+
9
+ it "should provide open method" do
10
+ @driver.open
11
+ @driver.open{'result'}.should == 'result'
12
+ end
13
+ end
14
+
15
+ shared_examples_for 'vfs driver attributes basic' do
16
+ it 'should have root dir' do
17
+ attrs = @driver.attributes('/')
18
+ attrs[:dir].should be_true
19
+ attrs[:file].should be_false
20
+ end
21
+
22
+ it "attributes should return nil if there's no entry" do
23
+ @driver.attributes('/non_existing_entry').should be_nil
24
+ end
25
+ end
26
+
27
+ shared_examples_for 'vfs driver files' do
28
+ it "file attributes" do
29
+ @driver.attributes('/file').should be_nil
30
+
31
+ @driver.write_file('/file', false){|w| w.write 'something'}
32
+ attrs = @driver.attributes('/file')
33
+ attrs[:file].should be_true
34
+ attrs[:dir].should be_false
35
+ end
36
+
37
+ it "read, write, append" do
38
+ # write
39
+ @driver.write_file('/file', false){|w| w.write 'something'}
40
+ @driver.attributes('/file')[:file].should == true
41
+
42
+ # read
43
+ data = ""
44
+ @driver.read_file('/file'){|buff| data << buff}
45
+ data.should == 'something'
46
+
47
+ # append
48
+ @driver.write_file('/file', true){|w| w.write ' another'}
49
+ data = ""
50
+ @driver.read_file('/file'){|buff| data << buff}
51
+ data.should == 'something another'
52
+ end
53
+
54
+ it "delete_file" do
55
+ @driver.write_file('/file', false){|w| w.write 'something'}
56
+ @driver.attributes('/file')[:file].should be_true
57
+ @driver.delete_file('/file')
58
+ @driver.attributes('/file').should be_nil
59
+ end
60
+ end
61
+
62
+ shared_examples_for 'vfs driver full attributes for files' do
63
+ it "attributes for files" do
64
+ @driver.write_file('/file', false){|w| w.write 'something'}
65
+ attrs = @driver.attributes('/file')
66
+ attrs[:file].should be_true
67
+ attrs[:dir].should be_false
68
+ attrs[:created_at].class.should == Time
69
+ attrs[:updated_at].class.should == Time
70
+ attrs[:size].should == 9
71
+ end
72
+ end
73
+
74
+ shared_examples_for 'vfs driver dirs' do
75
+ it "directory crud" do
76
+ @driver.attributes('/dir').should be_nil
77
+
78
+ @driver.create_dir('/dir')
79
+ attrs = @driver.attributes('/dir')
80
+ attrs[:file].should be_false
81
+ attrs[:dir].should be_true
82
+
83
+ @driver.delete_dir('/dir')
84
+ @driver.attributes('/dir').should be_nil
85
+ end
86
+
87
+ it 'should delete not-empty directories' do
88
+ @driver.create_dir('/dir')
89
+ @driver.create_dir('/dir/dir2')
90
+ @driver.write_file('/dir/dir2/file', false){|w| w.write 'something'}
91
+ @driver.attributes('/dir').should_not be_nil
92
+
93
+ @driver.delete_dir('/dir')
94
+ @driver.attributes('/dir').should be_nil
95
+ end
96
+
97
+ it 'each' do
98
+ -> {@driver.each_entry('/not_existing_dir', nil){|path, type| list[path] = type}}.should raise_error
99
+
100
+ @driver.create_dir '/dir'
101
+ @driver.create_dir('/dir/dir2')
102
+ @driver.write_file('/dir/file', false){|w| w.write 'something'}
103
+
104
+ list = {}
105
+ @driver.each_entry '/dir', nil do |path, type|
106
+ type = type.call if type.is_a? Proc
107
+ list[path] = type
108
+ end
109
+
110
+ list.should == {'dir2' => :dir, 'file' => :file}
111
+ end
112
+
113
+ # it "upload_directory & download_directory" do
114
+ # upload_path_check = "#{@remote_path}/dir2/file"
115
+ # check_attributes upload_path_check, nil
116
+ # @driver.upload_directory(@from_local, @remote_path)
117
+ # check_attributes upload_path_check, file: true, dir: false
118
+ #
119
+ # download_path_check = "#{@to_local}/dir2/file"
120
+ # File.exist?(download_path_check).should be_false
121
+ # @driver.download_directory(@remote_path, @to_local)
122
+ # File.exist?(download_path_check).should be_true
123
+ # end
124
+ end
125
+
126
+ shared_examples_for 'vfs driver query' do
127
+ it 'each with query' do
128
+ @driver.create_dir '/dir'
129
+ @driver.create_dir('/dir/dir_a')
130
+ @driver.create_dir('/dir/dir_b')
131
+ @driver.write_file('/dir/file_a', false){|w| w.write 'something'}
132
+
133
+ list = {}
134
+ @driver.each_entry '/dir', '*_a' do |path, type|
135
+ type = type.call if type.is_a? Proc
136
+ list[path] = type
137
+ end
138
+
139
+ list.should == {'dir_a' => :dir, 'file_a' => :file}
140
+ end
141
+ end
142
+
143
+ shared_examples_for 'vfs driver full attributes for dirs' do
144
+ it "attributes for dirs" do
145
+ @driver.create_dir('/dir')
146
+ attrs = @driver.attributes('/dir')
147
+ attrs[:file].should be_false
148
+ attrs[:dir].should be_true
149
+ attrs[:created_at].class.should == Time
150
+ attrs[:updated_at].class.should == Time
151
+ attrs.should_not include(:size)
152
+ end
153
+ end
154
+
155
+ shared_examples_for 'vfs driver tmp dir' do
156
+ it "tmp dir" do
157
+ dir = @driver.tmp
158
+ @driver.attributes(dir).should_not be_nil
159
+ @driver.delete_dir dir
160
+ @driver.attributes(dir).should be_nil
161
+
162
+ dir = nil
163
+ @driver.tmp do |tmp_dir|
164
+ dir = tmp_dir
165
+ @driver.attributes(dir).should_not be_nil
166
+ end
167
+ @driver.attributes(dir).should be_nil
168
+ end
169
+ end
@@ -0,0 +1,253 @@
1
+ module Vfs
2
+ class Dir < Entry
3
+ #
4
+ # Container
5
+ #
6
+ def [] path
7
+ path = path.to_s
8
+ if path =~ /.+[\/]$/
9
+ path = path.sub /\/$/, ''
10
+ dir path
11
+ else
12
+ entry path
13
+ end
14
+ end
15
+ alias_method :/, :[]
16
+
17
+
18
+ #
19
+ # Attributes
20
+ #
21
+ alias_method :exist?, :dir?
22
+
23
+
24
+ #
25
+ # CRUD
26
+ #
27
+ def create options = {}
28
+ driver.open do
29
+ try = 0
30
+ begin
31
+ try += 1
32
+ driver.create_dir path
33
+ rescue StandardError => error
34
+ entry = self.entry
35
+ attrs = entry.get
36
+ if attrs and attrs[:file] #entry.exist?
37
+ entry.destroy
38
+ elsif attrs and attrs[:dir]
39
+ # dir already exist, no need to recreate it
40
+ return self
41
+ else
42
+ parent = self.parent
43
+ if parent.exist?
44
+ # some unknown error
45
+ raise error
46
+ else
47
+ parent.create(options)
48
+ end
49
+ end
50
+
51
+ try < 2 ? retry : raise(error)
52
+ end
53
+ end
54
+ self
55
+ end
56
+
57
+ def destroy options = {}
58
+ destroy_entry :dir, :file
59
+ end
60
+
61
+
62
+ #
63
+ # Content
64
+ #
65
+ def entries *args, &block
66
+ raise "invalid arguments #{args.inspect}!" if args.size > 2
67
+ options = args.last.is_a?(Hash) ? args.pop : {}
68
+ query = args.first
69
+ options[:bang] = true unless options.include? :bang
70
+ filter = options[:filter]
71
+ type_required = options[:type]
72
+
73
+ driver.open do
74
+ begin
75
+ list = []
76
+ # query option is optional and supported only for some drivers (local driver for example)
77
+ driver.each_entry path, query do |name, type|
78
+ # for performance reasons some drivers may return the type of entry as
79
+ # optionally evaluated callback.
80
+ type = type.call if (filter or type_required) and type.is_a?(Proc)
81
+
82
+ next if filter and (filter != type)
83
+
84
+ entry = if type == :dir
85
+ dir(name)
86
+ elsif type == :file
87
+ file(name)
88
+ else
89
+ entry(name)
90
+ end
91
+ block ? block.call(entry) : (list << entry)
92
+ end
93
+ block ? nil : list
94
+ rescue StandardError => error
95
+ attrs = get
96
+ if attrs and attrs[:file]
97
+ raise Error, "can't query entries on File ('#{self}')!"
98
+ elsif attrs and attrs[:dir]
99
+ # some unknown error
100
+ raise error
101
+ else
102
+ # TODO2 remove :bang
103
+ raise Error, "'#{self}' not exist!" if options[:bang]
104
+ []
105
+ end
106
+ end
107
+ end
108
+ end
109
+ alias_method :each, :entries
110
+
111
+ def files *args, &block
112
+ options = args.last.is_a?(Hash) ? args.pop : {}
113
+
114
+ options[:filter] = :file
115
+ args << options
116
+ entries *args, &block
117
+ end
118
+
119
+ def dirs *args, &block
120
+ options = args.last.is_a?(Hash) ? args.pop : {}
121
+
122
+ options[:filter] = :dir
123
+ args << options
124
+ entries *args, &block
125
+ end
126
+
127
+ def include? name
128
+ entry[name].exist?
129
+ end
130
+ alias_method :has?, :include?
131
+
132
+ def empty?
133
+ catch :break do
134
+ entries{|e| throw :break, false}
135
+ true
136
+ end
137
+ end
138
+
139
+
140
+ #
141
+ # Transfers
142
+ #
143
+ def copy_to to, options = {}
144
+ options[:bang] = true unless options.include? :bang
145
+
146
+ raise Error, "invalid argument, destination should be a Entry (#{to})!" unless to.is_a? Entry
147
+ raise Error, "you can't copy to itself" if self == to
148
+
149
+ target = if to.is_a? File
150
+ to.dir
151
+ elsif to.is_a? Dir
152
+ to.dir #(name)
153
+ elsif to.is_a? UniversalEntry
154
+ # raise "can't copy Dir to File ('#{self}')!" if to.file? and !options[:override]
155
+ to.dir #.create
156
+ else
157
+ raise "can't copy to unknown Entry!"
158
+ end
159
+
160
+ # efficient_dir_copy(target, options) || unefficient_dir_copy(target, options)
161
+ unefficient_dir_copy(target, options)
162
+
163
+ target
164
+ end
165
+
166
+ def move_to to, options = {}
167
+ copy_to to, options
168
+ destroy options
169
+ to
170
+ end
171
+
172
+ # class << self
173
+ # attr_accessor :dont_use_efficient_dir_copy
174
+ # end
175
+
176
+ protected
177
+ def unefficient_dir_copy to, options
178
+ to.create options
179
+ entries options.merge(type: true) do |e|
180
+ if e.is_a? Dir
181
+ e.copy_to to.dir(e.name), options
182
+ elsif e.is_a? File
183
+ e.copy_to to.file(e.name), options
184
+ else
185
+ raise 'internal error'
186
+ end
187
+ end
188
+ end
189
+
190
+
191
+ # def efficient_dir_copy to, options
192
+ # return false if self.class.dont_use_efficient_dir_copy
193
+ #
194
+ # driver.open do
195
+ # try = 0
196
+ # begin
197
+ # try += 1
198
+ # self.class.efficient_dir_copy(self, to, options[:override])
199
+ # rescue StandardError => error
200
+ # unknown_errors = 0
201
+ #
202
+ # attrs = get
203
+ # if attrs and attrs[:file]
204
+ # raise Error, "can't copy File as a Dir ('#{self}')!"
205
+ # elsif attrs and attrs[:dir]
206
+ # # some unknown error (but it also maybe caused by to be fixed error in 'to')
207
+ # unknown_errors += 1
208
+ # else
209
+ # raise Error, "'#{self}' not exist!" if options[:bang]
210
+ # return true
211
+ # end
212
+ #
213
+ # attrs = to.get
214
+ # if attrs and attrs[:file]
215
+ # if options[:override]
216
+ # to.destroy
217
+ # else
218
+ # raise Vfs::Error, "entry #{to} already exist!"
219
+ # end
220
+ # elsif attrs and attrs[:dir]
221
+ # unknown_errors += 1
222
+ # # if options[:override]
223
+ # # to.destroy
224
+ # # else
225
+ # # dir_already_exist = true
226
+ # # # raise Vfs::Error, "entry #{to} already exist!"
227
+ # # end
228
+ # else # parent not exist
229
+ # parent = to.parent
230
+ # if parent.exist?
231
+ # # some unknown error (but it also maybe caused by already fixed error in 'from')
232
+ # unknown_errors += 1
233
+ # else
234
+ # parent.create(options)
235
+ # end
236
+ # end
237
+ #
238
+ # raise error if unknown_errors > 1
239
+ # try < 2 ? retry : raise(error)
240
+ # end
241
+ # end
242
+ # end
243
+ #
244
+ # def self.efficient_dir_copy from, to, override
245
+ # from.driver.open{
246
+ # driver.respond_to?(:efficient_dir_copy) and driver.efficient_dir_copy(from, to, override)
247
+ # } or
248
+ # to.driver.open{
249
+ # driver.respond_to?(:efficient_dir_copy) and driver.efficient_dir_copy(from, to, override)
250
+ # }
251
+ # end
252
+ end
253
+ end