io 0.0.1

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.
@@ -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