$LOAD_PATH << File.expand_path("#{__FILE__}/../..")├── docs ├── ssh_deployment │ └── app │ │ └── app.rb ├── s3_backup │ └── app │ │ └── files │ │ └── bos.png ├── site │ ├── public │ │ ├── fonts │ │ │ ├── aller-bold.eot │ │ │ ├── aller-bold.ttf │ │ │ ├── aller-bold.woff │ │ │ ├── aller-light.eot │ │ │ ├── aller-light.ttf │ │ │ ├── aller-light.woff │ │ │ ├── novecento-bold.eot │ │ │ ├── novecento-bold.ttf │ │ │ └── novecento-bold.woff │ │ └── stylesheets │ │ │ └── normalize.css │ ├── index.html │ ├── 404.html │ ├── s3_basics.html │ ├── ssh_basics.html │ ├── s3_sandbox.html │ ├── ssh_sandbox.html │ ├── s3_backup.html │ ├── ssh_deployment.html │ ├── docco.css │ └── basics.html ├── s3_basics.rb ├── ssh_basics.rb ├── s3_sandbox.rb ├── ssh_sandbox.rb ├── s3_backup.rb ├── ssh_deployment.rb └── basics.rb ├── spec ├── storages │ ├── local_spec │ │ └── emptygit │ └── local_spec.rb ├── misc_spec.rb ├── spec_helper.rb ├── entry_spec.rb ├── container_spec.rb ├── universal_entry_spec.rb ├── path_spec.rb ├── file_spec.rb └── dir_spec.rb ├── lib ├── vfs │ ├── error.rb │ ├── entries │ │ ├── universal_entry.rb │ │ ├── entry.rb │ │ ├── file.rb │ │ └── dir.rb │ ├── integration.rb │ ├── vfs.rb │ ├── entry_proxy.rb │ ├── path.rb │ └── drivers │ │ ├── local.rb │ │ └── specification.rb └── vfs.rb ├── .gitignore ├── old ├── hash_fs_spec.rb └── hash_fs.rb ├── gemspec ├── todo.md └── readme.md /docs/ssh_deployment/app/app.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /spec/storages/local_spec/emptygit: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/vfs/error.rb: -------------------------------------------------------------------------------- 1 | module Vfs 2 | class Error < StandardError 3 | end 4 | end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tmp/* 2 | spec/config.yml 3 | 4 | *~ 5 | .DS_Store 6 | .idea 7 | .idea/**/* 8 | Thumbs.db -------------------------------------------------------------------------------- /docs/s3_backup/app/files/bos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al6x/vfs/HEAD/docs/s3_backup/app/files/bos.png -------------------------------------------------------------------------------- /docs/site/public/fonts/aller-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al6x/vfs/HEAD/docs/site/public/fonts/aller-bold.eot -------------------------------------------------------------------------------- /docs/site/public/fonts/aller-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al6x/vfs/HEAD/docs/site/public/fonts/aller-bold.ttf -------------------------------------------------------------------------------- /docs/site/public/fonts/aller-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al6x/vfs/HEAD/docs/site/public/fonts/aller-bold.woff -------------------------------------------------------------------------------- /docs/site/public/fonts/aller-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al6x/vfs/HEAD/docs/site/public/fonts/aller-light.eot -------------------------------------------------------------------------------- /docs/site/public/fonts/aller-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al6x/vfs/HEAD/docs/site/public/fonts/aller-light.ttf -------------------------------------------------------------------------------- /docs/site/public/fonts/aller-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al6x/vfs/HEAD/docs/site/public/fonts/aller-light.woff -------------------------------------------------------------------------------- /docs/site/public/fonts/novecento-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al6x/vfs/HEAD/docs/site/public/fonts/novecento-bold.eot -------------------------------------------------------------------------------- /docs/site/public/fonts/novecento-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al6x/vfs/HEAD/docs/site/public/fonts/novecento-bold.ttf -------------------------------------------------------------------------------- /docs/site/public/fonts/novecento-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/al6x/vfs/HEAD/docs/site/public/fonts/novecento-bold.woff -------------------------------------------------------------------------------- /docs/site/index.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 |Please follow this link.
7 | 8 | -------------------------------------------------------------------------------- /docs/site/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |Page not exists, redirecting to home.
7 | 8 | -------------------------------------------------------------------------------- /old/hash_fs_spec.rb: -------------------------------------------------------------------------------- 1 | require 'vfs/drivers/hash_fs' 2 | require 'vfs/drivers/specification' 3 | 4 | describe Vfs::Drivers::HashFs do 5 | it_should_behave_like "vfs driver" 6 | 7 | before do 8 | @driver = Vfs::Drivers::HashFs.new 9 | end 10 | end -------------------------------------------------------------------------------- /gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |s| 2 | s.name = 'vfs' 3 | s.version = '0.5.1' 4 | s.summary = "Virtual File System - simple and unified API over different storages (Local, S3, SFTP, ...)" 5 | s.files = Dir.glob('lib/**/*.rb') 6 | s.authors = ["Alexey Petrushin"] 7 | s.homepage = 'http://alexeypetrushin.github.io/vfs' 8 | end -------------------------------------------------------------------------------- /lib/vfs/entries/universal_entry.rb: -------------------------------------------------------------------------------- 1 | module Vfs 2 | class UniversalEntry < Entry 3 | # Attributes. 4 | 5 | def exist? 6 | !!get 7 | end 8 | 9 | def copy_to to, options = {} 10 | from = file? ? to_file : to_dir 11 | from.copy_to to, options 12 | end 13 | 14 | # CRUD. 15 | 16 | def delete 17 | delete_entry 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /spec/misc_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Miscellaneous' do 4 | with_test_dir 5 | 6 | it "File should respond to :to_file, :to_entry" do 7 | path = "#{test_dir.path}/file" 8 | File.open(path, 'w'){|f| f.write 'something'} 9 | 10 | file = nil 11 | begin 12 | file = File.open path 13 | file.to_file.path.should == file.path 14 | file.to_entry.path.should == file.path 15 | ensure 16 | file.close 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'vfs' 2 | 3 | RSpec::Core::ExampleGroup.class_eval do 4 | def self.with_test_dir 5 | before do 6 | @test_dir = "/tmp/test_dir".to_dir 7 | 8 | FileUtils.rm_r test_dir.path if File.exist? test_dir.path 9 | FileUtils.mkdir_p test_dir.path 10 | end 11 | 12 | after do 13 | FileUtils.rm_r test_dir.path if File.exist? test_dir.path 14 | @test_dir = nil 15 | end 16 | end 17 | 18 | def test_dir 19 | @test_dir 20 | end 21 | end -------------------------------------------------------------------------------- /lib/vfs.rb: -------------------------------------------------------------------------------- 1 | module Vfs 2 | autoload :Path, 'vfs/path' 3 | autoload :Error, 'vfs/error' 4 | 5 | autoload :Entry, 'vfs/entries/entry' 6 | autoload :File, 'vfs/entries/file' 7 | autoload :Dir, 'vfs/entries/dir' 8 | autoload :UniversalEntry, 'vfs/entries/universal_entry' 9 | 10 | autoload :EntryProxy, 'vfs/entry_proxy' 11 | 12 | module Drivers 13 | autoload :Local, 'vfs/drivers/local' 14 | end 15 | end 16 | 17 | require 'vfs/vfs' 18 | require 'vfs/integration' -------------------------------------------------------------------------------- /docs/s3_basics.rb: -------------------------------------------------------------------------------- 1 | # Example of [Virtual File System](index.html) working with AWS S3. 2 | # 3 | # This is exactly the same [basic example](basics.html) but this time 4 | # we using S3 as storage instead of local file system. 5 | 6 | # Adding examples folder to load paths. 7 | $LOAD_PATH << File.expand_path("#{__FILE__}/../..") 8 | 9 | # Connecting to S3 and preparing sandbox. You may take a look at 10 | # the [docs/s3_sandbox.rb](s3_sandbox.html) to see the actual code. 11 | require 'docs/s3_sandbox' 12 | 13 | # Now we just executig [basic example](basics.html) 14 | # but with the `$storage` set to AWS S3. 15 | require 'docs/basics' -------------------------------------------------------------------------------- /docs/ssh_basics.rb: -------------------------------------------------------------------------------- 1 | # Example of [Virtual File System](index.html) working with SFTP. 2 | # 3 | # This is exactly the same [basic example](basics.html) but this time 4 | # we using SFTP as storage instead of local file system. 5 | 6 | # Adding examples folder to load paths. 7 | $LOAD_PATH << File.expand_path("#{__FILE__}/../..") 8 | 9 | # Connecting to SFTP and preparing sandbox. You may take a look at 10 | # the [docs/ssh_sandbox.rb](ssh_sandbox.html) to see the actual code. 11 | require 'docs/ssh_sandbox' 12 | 13 | # Now we just executig [basic example](basics.html) 14 | # but with the `$storage` set to SFTP. 15 | require 'docs/basics' -------------------------------------------------------------------------------- /lib/vfs/integration.rb: -------------------------------------------------------------------------------- 1 | class String 2 | def to_entry_on driver = nil 3 | path = self 4 | driver ||= Vfs.default_driver 5 | 6 | path = "./#{path}" unless path =~ /^[\/\.\~]/ 7 | Vfs::Entry.new(driver, path).entry 8 | end 9 | alias_method :to_entry, :to_entry_on 10 | 11 | def to_file_on driver = nil 12 | to_entry_on(driver).file 13 | end 14 | alias_method :to_file, :to_file_on 15 | 16 | def to_dir_on driver = nil 17 | to_entry_on(driver).dir 18 | end 19 | alias_method :to_dir, :to_dir_on 20 | end 21 | 22 | class File 23 | def to_entry 24 | path.to_entry 25 | end 26 | 27 | def to_file 28 | path.to_file 29 | end 30 | end -------------------------------------------------------------------------------- /todo.md: -------------------------------------------------------------------------------- 1 | - add drivers: Hadoop DFS, MongoDB, Amazon S3 2 | - remove :host from Vfs to Vos 3 | - efficient version of :copy for files and dirs (there's a problem thought, FileUtils.cp_r overrides files silntly, don't know how to do it)? 4 | - efficient (not copy/delete) versions of move_to, rename 5 | - handy :chmod helpers (via attributes) 6 | - add drivers: remote FS over HTTP? 7 | 8 | # Done: 9 | 10 | - refactor specs with :fakefs (rejected, there are bugs in fakefs) 11 | - glob search for directories: Dir['**/*.yml'] 12 | - Vos: Dir.bash 13 | - File.append 14 | - list of entries/files/dirs 15 | - support for efficient copy for Local and SSH drivers 16 | 17 | # Article 18 | 19 | './result.html'.to_file.write response.body -------------------------------------------------------------------------------- /lib/vfs/vfs.rb: -------------------------------------------------------------------------------- 1 | module Vfs 2 | class << self 3 | def default_driver 4 | ::Vfs::Drivers::Local.new 5 | end 6 | 7 | def to_entry 8 | '/'.to_entry 9 | end 10 | 11 | def to_file 12 | to_entry.file 13 | end 14 | 15 | def to_dir 16 | to_entry.dir 17 | end 18 | 19 | # def [] path 20 | # to_entry[path] 21 | # end 22 | # alias_method :/, :[] 23 | 24 | %w( 25 | entry dir file 26 | entries dirs files 27 | [] / 28 | tmp 29 | ).each do |m| 30 | script = <<-RUBY 31 | def #{m} *a, &b 32 | to_entry.#{m} *a, &b 33 | end 34 | RUBY 35 | eval script, binding, __FILE__, __LINE__ 36 | end 37 | end 38 | end -------------------------------------------------------------------------------- /spec/storages/local_spec.rb: -------------------------------------------------------------------------------- 1 | require 'vfs/drivers/local' 2 | require 'vfs/drivers/specification' 3 | 4 | describe Vfs::Drivers::Local do 5 | with_test_dir 6 | 7 | before do 8 | @driver = Vfs::Drivers::Local.new root: test_dir.to_s 9 | @driver.open 10 | end 11 | 12 | after do 13 | @driver.close 14 | end 15 | 16 | it_should_behave_like 'vfs driver basic' 17 | it_should_behave_like 'vfs driver attributes basic' 18 | it_should_behave_like 'vfs driver files' 19 | it_should_behave_like 'vfs driver full attributes for files' 20 | it_should_behave_like 'vfs driver dirs' 21 | it_should_behave_like 'vfs driver full attributes for dirs' 22 | it_should_behave_like 'vfs driver query' 23 | it_should_behave_like 'vfs driver tmp dir' 24 | end -------------------------------------------------------------------------------- /docs/s3_sandbox.rb: -------------------------------------------------------------------------------- 1 | # Example of using AWS S3 as a storage for [Virtual File System](index.html) 2 | 3 | # To use S3 we need the S3 driver, You need 'vos' and 'aws-sdk' gems installed. 4 | # 5 | # gem install vos aws-sdk 6 | # 7 | require 'vfs' 8 | require 'vos' 9 | require 'vos/drivers/s3' 10 | 11 | # Initializing S3 driver, You need to provide Your AWS credentials. 12 | driver = Vos::Drivers::S3.new \ 13 | access_key_id: 'xxx', 14 | secret_access_key: 'xxx', 15 | bucket: 'xxx' 16 | 17 | # After creating driver we can create storage. 18 | box = Vos::Box.new driver 19 | 20 | # Preparing temporary dir (actually, S3 has no dirs, but it can mimic it) 21 | # for sandbox and cleaning it before starting. 22 | $sandbox = box['/tmp/vfs_sandbox'].to_dir.delete -------------------------------------------------------------------------------- /docs/ssh_sandbox.rb: -------------------------------------------------------------------------------- 1 | # Example of using SFTP as a storage for [Virtual File System](vfs) 2 | 3 | # To use SSH/SFTP we need the SSH driver, You need 'vos', 'net-ssh' and 'net-sftp' gems installed. 4 | # 5 | # gem install vos net-ssh net-sftp 6 | # 7 | require 'vfs' 8 | require 'vos' 9 | require 'vos/drivers/ssh' 10 | 11 | # Initializing SSH driver, if You can connect to server using identity file provide host only. 12 | # 13 | # If the connection requires login and password You need to provide it: 14 | # 15 | # driver = Vos::Drivers::Ssh.new \ 16 | # host: 'xxx.com', 17 | # user: 'xxx', 18 | # password: 'xxx' 19 | # 20 | driver = Vos::Drivers::Ssh.new host: 'xxx.com' 21 | 22 | # After creating driver we can create storage. 23 | box = Vos::Box.new driver 24 | 25 | # Preparing temporary dir for sample and cleaning it before starting. 26 | $sandbox = box['/tmp/vfs_sandbox'].to_dir.delete -------------------------------------------------------------------------------- /spec/entry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Entry' do 4 | with_test_dir 5 | 6 | before do 7 | @path = test_dir['a/b/c'] 8 | end 9 | 10 | it "name" do 11 | @path.name.should == 'c' 12 | end 13 | 14 | it "string integration" do 15 | '/'.to_entry.path.should == '/' 16 | 'a'.to_entry.path.should == "./a" 17 | end 18 | 19 | it 'tmp' do 20 | tmp = test_dir.tmp 21 | tmp.should be_dir 22 | tmp.delete 23 | 24 | tmp = nil 25 | test_dir.tmp do |path| 26 | tmp = path 27 | tmp.should be_dir 28 | end 29 | tmp.should_not exist 30 | end 31 | 32 | it 'should respond to local?' do 33 | test_dir.should respond_to(:local?) 34 | end 35 | 36 | it 'created_at, updated_at, size' do 37 | file = test_dir.file('file').write 'data' 38 | file.created_at.class.should == Time 39 | file.updated_at.class.should == Time 40 | file.size.should == 4 41 | end 42 | end -------------------------------------------------------------------------------- /docs/s3_backup.rb: -------------------------------------------------------------------------------- 1 | # Example of creating AWS S3 Backup with [Virtual File System](index.html). 2 | # 3 | # In this example we uploading sample files to S3 and then 4 | # copying it back to local folder. 5 | 6 | # Connecting to S3 and preparing sandbox. You may take a look at 7 | # the [docs/s3_sandbox.rb](s3_sandbox.html) to see the actual code. 8 | $LOAD_PATH << File.expand_path("#{__FILE__}/../..") 9 | require 'docs/s3_sandbox' 10 | s3 = $sandbox 11 | 12 | # Preparing sample files located in our local folder in 13 | # current directory. 14 | current_dir = __FILE__.to_entry.parent 15 | sample_files = current_dir['s3_backup/app'] 16 | 17 | # Uploading sample files to S3. 18 | sample_files.copy_to s3['app'] 19 | p s3['app/files/bos.png'].exist? # => true 20 | 21 | # Preparing local storage for S3 backup. 22 | local_backup = '/tmp/vfs_sandbox/backup'.to_dir.delete 23 | 24 | # Copying files from S3 to local backup directory. 25 | s3['app'].copy_to local_backup['app'] 26 | p local_backup['app/files/bos.png'].exist? # => true -------------------------------------------------------------------------------- /spec/container_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'Container' do 4 | with_test_dir 5 | 6 | it "should threat paths as UniversalEntry except it ends with '/'" do 7 | test_dir.should_receive(:entry).with('tmp/a/b') 8 | test_dir['tmp/a/b'] 9 | 10 | test_dir.should_receive(:dir).with('tmp/a/b') 11 | test_dir['tmp/a/b/'] 12 | end 13 | 14 | it '/' do 15 | test_dir[:some_path].should == test_dir / :some_path 16 | test_dir[:some_path][:another_path].should == test_dir / :some_path / :another_path 17 | end 18 | 19 | it "UniversalEntry should be wrapped inside of proxy, Dir and File should not" do 20 | -> {test_dir.dir.proxy?}.should raise_error(NoMethodError) 21 | -> {test_dir.file.proxy?}.should raise_error(NoMethodError) 22 | test_dir.entry.proxy?.should be_true 23 | end 24 | 25 | it "sometimes it also should inexplicitly guess that path is a Dir instead of UniversalEntry (but still wrap it inside of Proxy)" do 26 | 27 | dir = test_dir['tmp/a/..'] 28 | dir.proxy?.should be_true 29 | dir.should be_a(Vfs::Dir) 30 | end 31 | end -------------------------------------------------------------------------------- /lib/vfs/entry_proxy.rb: -------------------------------------------------------------------------------- 1 | # It allows dynamically (magically) switching between UniversalEntry/Dir/File. 2 | module Vfs 3 | class EntryProxy < BasicObject 4 | attr_reader :_target 5 | 6 | def initialize entry 7 | raise 'something wrong happening here!' if entry.respond_to?(:proxy?) and entry.proxy? 8 | self._target = entry 9 | end 10 | 11 | def proxy? 12 | true 13 | end 14 | 15 | protected :==, :equal?, :!, :!= 16 | protected 17 | attr_writer :_target 18 | 19 | def respond_to? m 20 | super or 21 | ::Vfs::UniversalEntry.method_defined?(m) or 22 | ::Vfs::Dir.method_defined?(m) or 23 | ::Vfs::File.method_defined?(m) 24 | end 25 | 26 | def method_missing m, *a, &b 27 | unless _target.respond_to? m 28 | if ::Vfs::UniversalEntry.method_defined? m 29 | self.target = _target.entry 30 | elsif ::Vfs::Dir.method_defined? m 31 | self._target = _target.dir 32 | elsif ::Vfs::File.method_defined? m 33 | self._target = _target.file 34 | end 35 | end 36 | 37 | _target.send m, *a, &b 38 | end 39 | end 40 | end -------------------------------------------------------------------------------- /docs/ssh_deployment.rb: -------------------------------------------------------------------------------- 1 | # Example of Application Deployment using [Virtual File System](index.html). 2 | # 3 | # In this example we uploading sample app files to remote server, 4 | # write database configuration file and restart the server on remote machine. 5 | 6 | # Adding examples folder to load paths. 7 | $LOAD_PATH << File.expand_path("#{__FILE__}/../..") 8 | 9 | # Connecting to SFTP and preparing sandbox. You may take a look at 10 | # the [docs/ssh_sandbox.rb](ssh_sandbox.html) to see the actual code. 11 | require 'docs/ssh_sandbox' 12 | sandbox = $sandbox 13 | 14 | # Preparing sample files located in our local folder in 15 | # current directory. 16 | current_dir = __FILE__.to_entry.parent 17 | sample_app = current_dir['ssh_deployment/app'] 18 | 19 | # Copying application files to remote machine. 20 | app = sandbox['apps/app'] 21 | sample_app.copy_to app 22 | p app['app.rb'].exist? # => true 23 | 24 | # Writing database configuration file. 25 | config = app['config.yml'] 26 | config.write "database: mysql" 27 | config.append "name: app_production" 28 | p app['config.yml'].exist? # => true 29 | 30 | # Updating gems and restarting the server. 31 | p app.bash("echo 'bundle install'") # => bundle install 32 | p app.bash("echo 'server start'") # => server start -------------------------------------------------------------------------------- /spec/universal_entry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe 'UniversalEntry' do 4 | with_test_dir 5 | 6 | before do 7 | @path = test_dir['a/b/c'] 8 | end 9 | 10 | describe 'existence' do 11 | it "should check both files and dirs" do 12 | @path.should_not exist 13 | @path.dir.create 14 | @path.should be_dir 15 | @path.should exist 16 | 17 | @path.file.create 18 | @path.should be_file 19 | @path.should exist 20 | end 21 | end 22 | 23 | describe 'deleting' do 24 | it "should delete both files and dirs" do 25 | @path.dir.create 26 | @path.should be_dir 27 | @path.entry.delete 28 | @path.should_not exist 29 | 30 | @path.file.create 31 | @path.should be_file 32 | @path.entry.delete 33 | @path.should_not exist 34 | end 35 | 36 | it "shouldn't raise if file not exist" do 37 | @path.delete 38 | end 39 | end 40 | 41 | describe 'copy_to' do 42 | before do 43 | @from = @path.dir 44 | @from.create 45 | @from.file('file').write 'something' 46 | @from.dir('dir').create.tap do |dir| 47 | dir.file('file2').write 'something2' 48 | end 49 | 50 | @to = test_dir['to'] 51 | end 52 | 53 | it "shoud copy dir" do 54 | @from.entry.copy_to @to 55 | @to['dir/file2'].file?.should be_true 56 | end 57 | 58 | it "should copy file" do 59 | @from['file'].entry.copy_to @to 60 | @to.file.should be_true 61 | end 62 | 63 | it "should raise if entry not exist" do 64 | -> {@from['non existing'].entry.copy_to @to}.should raise_error(/not exist/) 65 | end 66 | end 67 | 68 | describe 'move_to' 69 | end -------------------------------------------------------------------------------- /spec/path_spec.rb: -------------------------------------------------------------------------------- 1 | require 'spec_helper' 2 | 3 | describe "Path" do 4 | before(:all){Path = Vfs::Path} 5 | after(:all){Object.send :remove_const, :Path} 6 | 7 | it 'validations' do 8 | %w( 9 | / 10 | /a 11 | /a/b/c 12 | /a/../c 13 | /a/... 14 | ~/a 15 | ./a 16 | /~a 17 | /a~b 18 | /a.b~ 19 | ).each{|path| Path.should be_valid(path)} 20 | 21 | special = [''] 22 | (%w( 23 | /a/~/c 24 | /a/./c 25 | /a/ 26 | ~/ 27 | ./ 28 | ) + special).each{|path| Path.should_not be_valid(path)} 29 | end 30 | 31 | # it 'tmp' do 32 | # (Path.new('.') + '..').should == './..' 33 | # end 34 | 35 | it 'normalize' do 36 | special = ['/a/../..', nil] 37 | (%w( 38 | /a /a 39 | ~/a ~/a 40 | ./a ./a 41 | /a/../c /c 42 | / / 43 | ~ ~ 44 | /a~/b /a~/b 45 | . . 46 | ) + special).each_slice(2) do |path, normalized_path| 47 | Path.normalize(path).should == normalized_path 48 | end 49 | end 50 | 51 | it "+" do 52 | special = [ 53 | '/a', '../..', nil, 54 | '/', '..', nil, 55 | '.', '..', './..', 56 | ] 57 | (%w( 58 | / /a /a 59 | / ~/a ~/a 60 | / ./a ./a 61 | /a b/c /a/b/c 62 | /a/b/c ../d /a/b/d 63 | ) + special).each_slice(3) do |base, path, sum| 64 | (Path.new(base) + path).should == sum 65 | end 66 | end 67 | 68 | it 'parent' do 69 | special = [ 70 | '/', nil, 71 | '~', nil, 72 | '.', './..' 73 | ] 74 | (%w( 75 | /a/b/c /a/b 76 | ./a/b/c ./a/b 77 | ~/a/b/c ~/a/b 78 | ) + special).each_slice(2) do |path, parent| 79 | Path.new(path).parent.should == parent 80 | end 81 | end 82 | 83 | it "should raise error if current dir outside of root" do 84 | -> {Path.new('/a/../..')}.should raise_error(/outside.*root/) 85 | end 86 | 87 | it "should guess if current dir is a dir" do 88 | [ 89 | '/a', false, 90 | '/', true, 91 | '~', true, 92 | '.', true, 93 | '/a/..', true, 94 | '/a/../b', false, 95 | ].each_slice 2 do |path, result| 96 | Path.new(path).probably_dir?.should == result 97 | end 98 | 99 | path = Path.new('/a/b/c') 100 | [ 101 | path, false, 102 | (path + '..'), true, 103 | path.parent, true, 104 | 105 | (path + '/'), true, 106 | (path + '/a'), false, 107 | ].each_slice 2 do |path, result| 108 | path.probably_dir?.should == result 109 | end 110 | end 111 | 112 | it 'name' do 113 | %w( 114 | /a a 115 | /a/b/c c 116 | / / 117 | ~ ~ 118 | . . 119 | ).each_slice 2 do |path, name| 120 | Path.new(path).name.should == name 121 | end 122 | end 123 | 124 | it 'to_s' do 125 | Path.new.to_s.class.should == String 126 | end 127 | end -------------------------------------------------------------------------------- /lib/vfs/path.rb: -------------------------------------------------------------------------------- 1 | module Vfs 2 | class Path < String 3 | def initialize path = '/', options = {} 4 | if options[:skip_normalization] 5 | super path 6 | @probably_dir = options[:probably_dir] 7 | else 8 | Path.validate! path 9 | path, probably_dir = Path.normalize_to_string path 10 | raise "invalid path '#{path}' (you are outside of the root)!" unless path 11 | super path 12 | @probably_dir = probably_dir 13 | end 14 | end 15 | 16 | def + path = '' 17 | path = path.to_s 18 | Path.validate! path, false 19 | 20 | if Path.absolute?(path) 21 | Path.normalize path 22 | elsif path.empty? 23 | self 24 | else 25 | Path.normalize "#{self}#{'/' unless self == '/'}#{path}" 26 | end 27 | end 28 | 29 | def parent 30 | self + '..' 31 | end 32 | 33 | def probably_dir? 34 | !!@probably_dir 35 | end 36 | 37 | def name 38 | unless @name 39 | root = self[0..0] 40 | @name ||= split('/').last || root 41 | end 42 | @name 43 | end 44 | 45 | class << self 46 | def absolute? path 47 | path =~ /^[\/~\/]|^\.$|^\.\// 48 | end 49 | 50 | def valid? path, forbid_relative = true, &block 51 | result, err = if forbid_relative and !absolute?(path) 52 | [false, "path must be started with '/', or '.'"] 53 | elsif path =~ /.+\/~$|.+\/$|\/\.$/ 54 | [false, "path can't be ended with '/', '/~', or '/.'"] 55 | elsif path =~ /\/\/|\/~\/|\/\.\// 56 | [false, "path can't include '/./', '/~/', '//' combinations!"] 57 | # elsif path =~ /.+[~]|\/\.\// 58 | # [false, "'~', or '.' can be present only at the begining of string"] 59 | else 60 | [true, nil] 61 | end 62 | 63 | block.call err if block and !result and err 64 | result 65 | end 66 | 67 | def normalize path 68 | path, probably_dir = normalize_to_string path 69 | unless path 70 | nil 71 | else 72 | Path.new(path, skip_normalization: true, probably_dir: probably_dir) 73 | end 74 | end 75 | 76 | def validate! path, forbid_relative = true 77 | valid?(path, forbid_relative){|error| raise "invalid path '#{path}' (#{error})!"} 78 | end 79 | 80 | def normalize_to_string path 81 | root = path[0..0] 82 | result, probably_dir = [], false 83 | 84 | parts = path.split('/')[1..-1] 85 | if parts 86 | parts.each do |part| 87 | if part == '..' and (root != '.' or (root == '.' and result.size > 0)) 88 | return nil, false unless result.size > 0 89 | result.pop 90 | probably_dir ||= true 91 | else 92 | result << part 93 | probably_dir &&= false 94 | end 95 | end 96 | end 97 | normalized_path = result.join('/') 98 | 99 | probably_dir ||= true if normalized_path.empty? 100 | 101 | return "#{root}#{'/' unless root == '/' or normalized_path.empty?}#{normalized_path}", probably_dir 102 | end 103 | end 104 | 105 | # protected 106 | # def delete_dir_mark 107 | # path = path.to_s.sub(%r{/$}, '') 108 | # end 109 | # 110 | # 111 | # def root_path? path 112 | # path =~ /^[#{ROOT_SYMBOLS}]$/ 113 | # end 114 | # 115 | # def split_path path 116 | # path.split(/#{ROOT_SYMBOLS}/) 117 | # end 118 | # 119 | # def dir_mark? path 120 | # path =~ %r{/$} 121 | # end 122 | end 123 | end -------------------------------------------------------------------------------- /lib/vfs/entries/entry.rb: -------------------------------------------------------------------------------- 1 | module Vfs 2 | class Entry 3 | attr_reader :driver, :path, :path_cache 4 | 5 | def initialize *args 6 | if args.size == 1 and args.first.is_a? Entry 7 | entry = args.first 8 | @path_cache = entry.path_cache 9 | @driver, @path = entry.driver, entry.path 10 | else 11 | driver, path = *args 12 | @path_cache = Path.new path 13 | @driver, @path = driver, path_cache.to_s 14 | end 15 | raise "driver not defined!" unless self.driver 16 | end 17 | 18 | # Navigation. 19 | def parent 20 | Dir.new(driver, path_cache.parent) 21 | end 22 | 23 | # Transformations. 24 | 25 | def dir path = nil 26 | if path 27 | new_path = path_cache + path 28 | Dir.new driver, new_path 29 | else 30 | Dir.new self 31 | end 32 | end 33 | alias_method :to_dir, :dir 34 | 35 | def file path = nil 36 | if path 37 | new_path = path_cache + path 38 | File.new driver, new_path 39 | else 40 | File.new self 41 | end 42 | end 43 | alias_method :to_file, :file 44 | 45 | def entry path = nil 46 | entry = if path 47 | new_path = path_cache + path 48 | klass = new_path.probably_dir? ? Dir : UniversalEntry 49 | klass.new driver, new_path 50 | else 51 | UniversalEntry.new self 52 | end 53 | EntryProxy.new entry 54 | end 55 | alias_method :to_entry, :entry 56 | 57 | # Attributes. 58 | 59 | def get attr_name = nil 60 | attrs = driver.open{driver.attributes(path)} 61 | (attr_name and attrs) ? attrs[attr_name] : attrs 62 | end 63 | 64 | def set options 65 | # TODO2 set attributes 66 | not_implemented 67 | end 68 | 69 | def dir?; !!get(:dir) end 70 | def file?; !!get(:file) end 71 | def created_at; get :created_at end 72 | def updated_at; get :updated_at end 73 | 74 | # Miscellaneous. 75 | 76 | def name 77 | path_cache.name 78 | end 79 | 80 | def tmp &block 81 | driver.open do 82 | if block 83 | driver.tmp do |path| 84 | block.call Dir.new(driver, path) 85 | end 86 | else 87 | Dir.new driver, driver.tmp 88 | end 89 | end 90 | end 91 | 92 | def local? 93 | driver.local? 94 | end 95 | 96 | # Utils. 97 | 98 | def inspect 99 | "#{driver}#{':' unless driver.to_s.empty?}#{path}" 100 | end 101 | alias_method :to_s, :inspect 102 | 103 | def == other 104 | return false unless other.is_a? Entry 105 | driver == other.driver and path == other.path 106 | end 107 | 108 | def hash 109 | driver.hash + path.hash 110 | end 111 | 112 | def eql? other 113 | return false unless other.class == self.class 114 | driver.eql?(other.driver) and path.eql?(other.path) 115 | end 116 | 117 | def destroy *args; delete *args end 118 | def remove *args; delete *args end 119 | 120 | protected 121 | def delete_entry first = :file, second = :dir 122 | driver.open do 123 | begin 124 | driver.send :"delete_#{first}", path 125 | rescue StandardError => e 126 | attrs = get 127 | if attrs and attrs[first] 128 | # some unknown error 129 | raise e 130 | elsif attrs and attrs[second] 131 | driver.send :"delete_#{second}", path 132 | else 133 | # Do nothing, entry already not exist. 134 | end 135 | end 136 | end 137 | self 138 | end 139 | end 140 | end -------------------------------------------------------------------------------- /lib/vfs/entries/file.rb: -------------------------------------------------------------------------------- 1 | module Vfs 2 | class File < Entry 3 | # Attributes. 4 | alias_method :exist?, :file? 5 | 6 | # CRUD. 7 | 8 | def read options = {}, &block 9 | options[:bang] = true unless options.include? :bang 10 | driver.open do 11 | begin 12 | if block 13 | driver.read_file path, &block 14 | else 15 | data = "" 16 | driver.read_file(path){|buff| data << buff} 17 | data 18 | end 19 | rescue StandardError => e 20 | raise Vfs::Error, "can't read Dir #{self}!" if dir.exist? 21 | attrs = get 22 | if attrs and attrs[:file] 23 | # unknown internal error 24 | raise e 25 | elsif attrs and attrs[:dir] 26 | raise Error, "You are trying to read Dir '#{self}' as if it's a File!" 27 | else 28 | if options[:bang] 29 | raise Error, "file #{self} not exist!" 30 | else 31 | block ? block.call('') : '' 32 | end 33 | end 34 | end 35 | end 36 | end 37 | 38 | # def content options = {} 39 | # read options 40 | # end 41 | 42 | def create options = {} 43 | write '', options 44 | self 45 | end 46 | 47 | def write *args, &block 48 | if block 49 | options = args.first || {} 50 | else 51 | data, options = *args 52 | data ||= "" 53 | options ||= {} 54 | end 55 | raise "can't do :override and :append at the same time!" if options[:override] and options[:append] 56 | 57 | driver.open do 58 | try = 0 59 | begin 60 | try += 1 61 | if block 62 | driver.write_file(path, options[:append], &block) 63 | else 64 | driver.write_file(path, options[:append]){|writer| writer.write data} 65 | end 66 | rescue StandardError => error 67 | parent = self.parent 68 | if entry.exist? 69 | entry.delete 70 | elsif !parent.exist? 71 | parent.create(options) 72 | else 73 | # unknown error 74 | raise error 75 | end 76 | 77 | try < 2 ? retry : raise(error) 78 | end 79 | end 80 | self 81 | end 82 | 83 | def append *args, &block 84 | options = (args.last.is_a?(Hash) && args.pop) || {} 85 | options[:append] = true 86 | write(*(args << options), &block) 87 | end 88 | 89 | def update options = {}, &block 90 | data = read options 91 | write block.call(data), options 92 | end 93 | 94 | def delete 95 | delete_entry 96 | end 97 | 98 | # Transfers. 99 | 100 | def copy_to to, options = {} 101 | raise Error, "you can't copy to itself" if self == to 102 | 103 | target = if to.is_a? File 104 | to 105 | elsif to.is_a? Dir 106 | to.file #(name) 107 | elsif to.is_a? UniversalEntry 108 | to.file 109 | else 110 | raise "can't copy to unknown Entry!" 111 | end 112 | 113 | target.write options do |writer| 114 | read(options){|buff| writer.write buff} 115 | end 116 | 117 | target 118 | end 119 | 120 | def move_to to 121 | copy_to to 122 | delete 123 | to 124 | end 125 | 126 | # Extra Stuff. 127 | 128 | def render *args 129 | require 'tilt' 130 | 131 | args.unshift Object.new if args.size == 1 and args.first.is_a?(Hash) 132 | 133 | template = Tilt.new(path){read} 134 | template.render *args 135 | end 136 | 137 | def size; get :size end 138 | 139 | def basename 140 | ::File.basename(name, ::File.extname(name)) 141 | end 142 | 143 | def extension 144 | ::File.extname(name).sub(/^\./, '') 145 | end 146 | end 147 | end -------------------------------------------------------------------------------- /lib/vfs/drivers/local.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | require 'tempfile' 3 | 4 | module Vfs 5 | module Drivers 6 | class Local 7 | class Writer 8 | def initialize out; @out = out end 9 | 10 | def write data 11 | @out.write data 12 | end 13 | end 14 | 15 | DEFAULT_BUFFER = 1000 * 1024 16 | 17 | def initialize options = {} 18 | options = options.clone 19 | @root = options.delete(:root) || '' 20 | raise "invalid options #{options}" unless options.empty? 21 | end 22 | 23 | def open &block 24 | block.call self if block 25 | end 26 | def close; end 27 | 28 | attr_writer :buffer 29 | def buffer 30 | @buffer || DEFAULT_BUFFER 31 | end 32 | 33 | # Attributes. 34 | 35 | def attributes path 36 | path = with_root path 37 | 38 | stat = ::File.stat path 39 | attrs = {} 40 | attrs[:file] = !!stat.file? 41 | attrs[:dir] = !!stat.directory? 42 | 43 | # attributes special for file system 44 | attrs[:created_at] = stat.ctime 45 | attrs[:updated_at] = stat.mtime 46 | attrs[:size] = stat.size if attrs[:file] 47 | attrs 48 | rescue Errno::ENOTDIR 49 | nil 50 | rescue Errno::ENOENT 51 | nil 52 | end 53 | 54 | def set_attributes path, attrs 55 | # TODO2 set attributes. 56 | not_implemented 57 | end 58 | 59 | # File. 60 | 61 | def read_file path, &block 62 | path = with_root path 63 | ::File.open path, 'r' do |is| 64 | while buff = is.gets(self.buffer || DEFAULT_BUFFER) 65 | block.call buff 66 | end 67 | end 68 | end 69 | 70 | def write_file original_path, append, &block 71 | path = with_root original_path 72 | 73 | option = append ? 'a' : 'w' 74 | ::File.open path, "#{option}b" do |out| 75 | block.call Writer.new(out) 76 | end 77 | end 78 | 79 | def delete_file path 80 | path = with_root path 81 | ::File.delete path 82 | end 83 | 84 | # def move_file from, to 85 | # FileUtils.mv from, to 86 | # end 87 | 88 | # Dir. 89 | def create_dir path 90 | path = with_root path 91 | ::Dir.mkdir path 92 | end 93 | 94 | def delete_dir original_path 95 | path = with_root original_path 96 | ::FileUtils.rm_r path 97 | end 98 | 99 | def each_entry path, query, &block 100 | path = with_root path 101 | 102 | if query 103 | path_with_trailing_slash = path == '/' ? path : "#{path}/" 104 | ::Dir["#{path_with_trailing_slash}#{query}"].each do |absolute_path| 105 | name = absolute_path.sub path_with_trailing_slash, '' 106 | block.call name, ->{::File.directory?(absolute_path) ? :dir : :file} 107 | end 108 | else 109 | ::Dir.foreach path do |name| 110 | next if name == '.' or name == '..' 111 | block.call name, ->{::File.directory?("#{path}/#{name}") ? :dir : :file} 112 | end 113 | end 114 | end 115 | 116 | # def efficient_dir_copy from, to, override 117 | # return false if override # FileUtils.cp_r doesn't support this behaviour 118 | # 119 | # from.driver.open_fs do |from_fs| 120 | # to.driver.open_fs do |to_fs| 121 | # if from_fs.local? and to_fs.local? 122 | # FileUtils.cp_r from.path, to.path 123 | # true 124 | # else 125 | # false 126 | # end 127 | # end 128 | # end 129 | # end 130 | 131 | # Other. 132 | 133 | def local?; true end 134 | 135 | def tmp &block 136 | path = "/tmp/#{rand(10**6)}" 137 | if block 138 | begin 139 | ::FileUtils.mkdir_p with_root(path) 140 | block.call path 141 | ensure 142 | ::FileUtils.rm_r with_root(path) if ::File.exist? with_root(path) 143 | end 144 | else 145 | ::FileUtils.mkdir_p with_root(path) 146 | path 147 | end 148 | end 149 | 150 | def to_s; '' end 151 | 152 | protected 153 | def root 154 | @root || raise('root not defined!') 155 | end 156 | 157 | def with_root path 158 | path == '/' ? root : root + path 159 | end 160 | end 161 | end 162 | end -------------------------------------------------------------------------------- /docs/site/s3_basics.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |Example of Virtual File System working with AWS S3.
77 |This is exactly the same basic example but this time 78 | we using S3 as storage instead of local file system.
79 | 80 |Adding examples folder to load paths.
92 | 93 |$LOAD_PATH << File.expand_path("#{__FILE__}/../..")Connecting to S3 and preparing sandbox. You may take a look at 107 | the docs/s3_sandbox.rb to see the actual code.
108 | 109 |require 'docs/s3_sandbox'Now we just executig basic example
123 | but with the $storage set to AWS S3.
require 'docs/basics'Example of Virtual File System working with SFTP.
77 |This is exactly the same basic example but this time 78 | we using SFTP as storage instead of local file system.
79 | 80 |Adding examples folder to load paths.
92 | 93 |$LOAD_PATH << File.expand_path("#{__FILE__}/../..")Connecting to SFTP and preparing sandbox. You may take a look at 107 | the docs/ssh_sandbox.rb to see the actual code.
108 | 109 |require 'docs/ssh_sandbox'Now we just executig basic example
123 | but with the $storage set to SFTP.
require 'docs/basics'Example of using AWS S3 as a storage for Virtual File System
77 | 78 |To use S3 we need the S3 driver, You need ‘vos’ and ‘aws-sdk’ gems installed.
90 |gem install vos aws-sdk
91 |
92 | require 'vfs'
95 | require 'vos'
96 | require 'vos/drivers/s3'Initializing S3 driver, You need to provide Your AWS credentials.
108 | 109 |driver = Vos::Drivers::S3.new \
112 | access_key_id: 'xxx',
113 | secret_access_key: 'xxx',
114 | bucket: 'xxx'After creating driver we can create storage.
126 | 127 |box = Vos::Box.new driverPreparing temporary dir (actually, S3 has no dirs, but it can mimic it) 141 | for sandbox and cleaning it before starting.
142 | 143 |$sandbox = box['/tmp/vfs_sandbox'].to_dir.deleteExample of using SFTP as a storage for Virtual File System
77 | 78 |To use SSH/SFTP we need the SSH driver, You need ‘vos’, ‘net-ssh’ and ‘net-sftp’ gems installed.
90 |gem install vos net-ssh net-sftp
91 |
92 | require 'vfs'
95 | require 'vos'
96 | require 'vos/drivers/ssh'Initializing SSH driver, if You can connect to server using identity file provide host only.
108 |If the connection requires login and password You need to provide it:
109 |driver = Vos::Drivers::Ssh.new \
110 | host: 'xxx.com',
111 | user: 'xxx',
112 | password: 'xxx'
113 |
114 | driver = Vos::Drivers::Ssh.new host: 'xxx.com'After creating driver we can create storage.
128 | 129 |box = Vos::Box.new driverPreparing temporary dir for sample and cleaning it before starting.
143 | 144 |$sandbox = box['/tmp/vfs_sandbox'].to_dir.deleteExample of creating AWS S3 Backup with Virtual File System.
77 |In this example we uploading sample files to S3 and then 78 | copying it back to local folder.
79 | 80 |Connecting to S3 and preparing sandbox. You may take a look at 92 | the docs/s3_sandbox.rb to see the actual code.
93 | 94 |$LOAD_PATH << File.expand_path("#{__FILE__}/../..")
97 | require 'docs/s3_sandbox'
98 | s3 = $sandboxPreparing sample files located in our local folder in 110 | current directory.
111 | 112 |current_dir = __FILE__.to_entry.parent
115 | sample_files = current_dir['s3_backup/app']Uploading sample files to S3.
127 | 128 |sample_files.copy_to s3['app']
131 | p s3['app/files/bos.png'].exist? # => truePreparing local storage for S3 backup.
143 | 144 |local_backup = '/tmp/vfs_sandbox/backup'.to_dir.deleteCopying files from S3 to local backup directory.
158 | 159 |s3['app'].copy_to local_backup['app']
162 | p local_backup['app/files/bos.png'].exist? # => trueExample of Application Deployment using Virtual File System.
77 |In this example we uploading sample app files to remote server, 78 | write database configuration file and restart the server on remote machine.
79 | 80 |Adding examples folder to load paths.
92 | 93 |$LOAD_PATH << File.expand_path("#{__FILE__}/../..")Connecting to SFTP and preparing sandbox. You may take a look at 107 | the docs/ssh_sandbox.rb to see the actual code.
108 | 109 |require 'docs/ssh_sandbox'
112 | sandbox = $sandboxPreparing sample files located in our local folder in 124 | current directory.
125 | 126 |current_dir = __FILE__.to_entry.parent
129 | sample_app = current_dir['ssh_deployment/app']Copying application files to remote machine.
141 | 142 |app = sandbox['apps/app']
145 | sample_app.copy_to app
146 | p app['app.rb'].exist? # => trueWriting database configuration file.
158 | 159 |config = app['config.yml']
162 | config.write "database: mysql"
163 | config.append "name: app_production"
164 | p app['config.yml'].exist? # => trueUpdating gems and restarting the server.
176 | 177 |p app.bash("echo 'bundle install'") # => bundle install
180 | p app.bash("echo 'server start'") # => server startVirtual File System provides clean, simple and unified API over different storages 77 | (Local File System, AWS S3, SFTP, …).
78 |Such unified API is possible because although the API of storages are different the core 86 | concept are almost the same.
87 |Install Vfs with Rubygems:
88 |gem install vfs
89 | Once installed, You can proceed with the example below. It uses local file system as storage, 90 | there’s also S3 version and SFTP version 91 | (also S3 backup and SSH/SFTP deployment examples 92 | availiable).
93 |The project is hosted on GitHub. 94 | You can report bugs and discuss features on the 95 | issues page.
96 | 97 |Preparing sandbox for our sample and cleaning it before starting
122 | (ignore the $sandbox variable, it’s needed to reuse this code in S3 and SSH samples).
require 'vfs'
127 | sandbox = $sandbox || '/tmp/vfs_sandbox'.to_dir.deleteCreating simple Hello World project.
139 | 140 |project = sandbox['hello_world']Writing readme file (note that parent dirs where created automatically).
154 | 155 |project['readme.txt'].write 'My App'We can assign files and dirs to variables, now the readme variable refers to our readme.txt file.
readme = project['readme.txt']Let’s ensure that it’s all ok with our readme file and check its attributes.
184 | 185 |p readme.name # => readme.txt
188 | p [readme.basename, readme.extension] # => ['readme', 'txt']
189 | p readme.path # => /.../readme.txt
190 | p readme.exist? # => true
191 | p readme.file? # => true
192 | p readme.dir? # => false
193 | p readme.size # => 6
194 | p readme.created_at # => 2011-09-09 13:20:43 +0400
195 | p readme.updated_at # => 2011-09-09 13:20:43 +0400Reading - You can read all at once or do it sequentially (input stream 207 | will be automatically splitted into chunks of reasonable size).
208 | 209 |p readme.read # => "My shiny App"
212 | readme.read{|chunk| p chunk} # => "My shiny App"The same for writing - write all at once or do it sequentially 224 | (if there’s no file it will be created, if it exists it will be rewriten).
225 | 226 |readme.write "My App v2"
229 | readme.write{|stream| stream.write "My App v3"}
230 | p readme.read # => "My shiny App v3"Appending content to existing file.
242 | 243 |readme.append "How to install ..."
246 | p readme.size # => 27Copying and Moving. It also works exactly the same 258 | way if You copy or move files and dirs to other storages.
259 | 260 |readme.copy_to project['docs/readme.txt']
263 | p project['docs/readme.txt'].exist? # => true
264 | p readme.exist? # => true
265 |
266 | readme.move_to project['docs/readme.txt']
267 | p project['docs/readme.txt'].exist? # => true
268 | p readme.exist? # => falseLet’s add empty Rakefile to our project.
280 | 281 |project['Rakefile'].writeOperations with directories - checking our project exists and not empty.
295 | 296 |p project.exist? # => true
299 | p project.empty? # => falseListing dir content. There are two versions of methods - 311 | without block the result will be Array of Entries, with block 312 | it will iterate over directory sequentially.
313 | 314 |p project.entries # => [/.../docs, /.../Rakefile]
317 | p project.files # => [/.../Rakefile]
318 | p project.dirs # => [/.../docs]
319 | project.entries do |entry| # => ["docs", false]
320 | p [entry.name, entry.file?] # => ["Rakefile", true]
321 | end
322 | p project.include?('Rakefile') # => trueYou can also use glob (if storage support it).
334 | 335 |if project.driver.local?
338 | p project.entries('**/Rake*') # => [/.../Rakefile]
339 | p project['**/Rake*'] # => [/.../Rakefile]The result of dir listing is just an array of Entries, so 351 | You can use it to do interesting things. For example this code will 352 | calculates the size of sources in our project.
353 | 354 | project['**/*.rb'].collect(&:size).reduce(0, :+)
357 | endCopying and moving - let’s create another project by cloning our hello_world.
369 | 370 |project.copy_to sandbox['another_project']
373 | p sandbox['another_project'].entries # => [/.../docs, .../Rakefile]Cleaning sandbox.
385 | 386 |sandbox.deleteIn this example we covering basics of Virtual File System, if You are interesting You can also take 400 | a look at S3 backup and SSH/SFTP deployment examples.
401 | 402 |