These are some embryonic ideas that generated after a discussion with
Wichael Witrant, author of path.rb.
This is about accessing and modifying file system entities (for lack
of a better term), such as files, directories, fifos etc., either
locally or (for example) via ftp or http, in an unified way.
It is also about really treating such entities just like objects in an
load-modify-commit (or rollback) cycle, like for file I/O (you might
call this ``filesystem I/O''), rather than doing a lot of atomic
modify-commit operations (e.g. File.delete('file.txt')) and constantly
keeping track of what is on disk.
It is *not* about accessing the content of the entity, and the stream
discussion we had a while ago, though it might be related.
Do you think there is usefulness to pursue this direction?
require 'ftools'
require 'uri'
# This class represents a file system entity, which can be a (local or
# remote) file, directory, fifo, link...
# Currently only supports local (file://) files and directories.
class FSE
def initialize(uri)
@current_uri = URI.parse uri
@final_uri = @current_uri.dup
exists? ? @ftype = File.ftype(@current_uri.path) : @ftype = 'file'
end
# Mark the file for deletion at next _sync_ call.
def delete
@delete = true
end
# Clear the delete mark if it was set.
def undelete
@delete = false
end
# Returns true if the entity the instance was initialized exists now
# (either because it existed before or because we called the _sync_
# method) or not yet.
#
# passwd_file = "/etc/passwd"
# p passwd_file.exists?
# true
# new_file = "/home/joe/unnamed.txt"
# p new_file.exists
# false
# new_file.sync
# p new_file.exists?
# true
def exists?
FileTest.exists? @current_uri.path
end
# Set the path the entity will have after the next sync, either as a
# moved or as a copied entity.
#
# config_file = FSE.new("/etc/config.rc")
# config_file.copying = true
# config_file.path = "/home/joe/.confrc"
# p config_file.path
# "/home/joe/.confrc"
# p config_file.current_path
# "/etc/config"
# config_file.sync
# p config_file.path
# "/home/joe/.confrc"
# p config_file.current_path
# "/home/joe/.confrc"
def path=(apath)
@final_uri.path = apath
end
# Returns the final path of the entity, i.e. the path it will have
# after the next _sync_. This may from the path of the entity on
# the disk (_current_path_), if iet has not been _sync_ed yet.
def path
@final_uri.path
end
# Current path of the entity. It can only be read. To move an
# entity, use the _path=_ method, then apply changes with _sync_.
def current_path
@current_uri.path
end
# States whether we are working on a copy of the entity we passed in
# at initialization time, or changes such as rename or move should
# be applied to the original entity.
def copying=(boolean)
@copying = boolean
end
# Define the type of this entity, *if* it was created from scratch
# and was not _sync_ed yet. The type of an existing entity (either
# because it existed before or because we _sync_ed it to disk
# already) cannot be changed.
def ftype=(atype)
raise "Cannot change the type of an existing filesystem entity." if exists?
raise "Unknown file type." unless ['file', 'directory'].include?(atype)
@ftype = atype
end
# Apply pending changes to the entity.
def sync
if @delete
delete_path(@current_uri.path)
return
end
if exists?
if @current_uri != @final_uri
@copying ? File.copy(@current_uri.path, @final_uri.path) : File.mv(@current_uri.path, @final_uri.path)
@current_uri = @final_uri.dup
end
else
case @ftype
when 'file'
system('touch', @final_uri.path)
when 'directory'
Dir.mkdir(@final_uri.path)
end
end
end
private
def delete_path(path)
if FileTest.directory?(path)
Dir.rmdir(path)
else
File.delete(path)
end
end
end
if $0 == __FILE__
require 'Lapidary/TestCase'
class TestFSE < Lapidary::TestCase
def initialize(*args)
super(*args)
Dir.mkdir 'test' unless FileTest.exists? 'test'
system('rm -rf test/*')
end
def testCopy
# test whether anything gets copied
file = FSE.new('/etc/passwd')
file.copying = true
file.path = 'test/passwd'
file.sync
assert FileTest.exists? 'test/passwd'
# test if what was copied matches what we started with
source_content = File.open('/etc/passwd') {|f| f.read}
dest_content = File.open('test/passwd') {|f| f.read}
assert source_content, dest_content
# cleanup
File.delete 'test/passwd'
end
def testMove
# copy something
file = FSE.new('/etc/passwd')
file.copying = true
file.path = 'test/passwd'
file.sync
# now move it
new_file = FSE.new('test/passwd')
file.copying = false
file.path = 'test/foo'
file.sync
# test whether anything was moved
assert FileTest.exists? 'test/foo'
# cleanup
File.delete 'test/foo'
end
def testCreateFile
f = FSE.new('test/entity')
f.ftype = 'file'
f.sync
assert FileTest.exists? 'test/entity'
assert FileTest.file? 'test/entity'
File.delete 'test/entity'
end
def testCreateDir
f = FSE.new('test/sampledir')
f.ftype = 'directory'
f.sync
assert FileTest.exists? 'test/sampledir'
assert FileTest.directory? 'test/sampledir'
Dir.rmdir('test/sampledir')
end
def testDelete
f = FSE.new('test/entity')
f.ftype = 'file'
f.sync
f.delete
assert FileTest.exists? 'test/entity'
f.sync
assert !FileTest.exists?('test/entity')
end
def testUndelete
f = FSE.new('test/entity')
f.ftype = 'file'
f.sync
f.delete
f.undelete
f.sync
assert FileTest.exists? 'test/entity'
f.delete
f.sync
end
end
require 'Lapidary/UI/Console/TestRunner'
Lapidary::UI::Console::TestRunner.run(TestFSE)
end