Directory Synchronizer
This is a copy command to synchronize two directories.
The package includes the library dir-compare.rb,
which enables us to make arbitrary copy commands.
Put dir-compare.rb in the directory where Ruby can load.
The copy command is oocp.rb.
To synchronize the directories source-dir and target-dir,
do as follows:
ruby oocp.rb source-dir target-dir
Then the files in source-dir are copied in target-dir
under the condition:
source-dir and in target-dir, the one in source-dir is copied if it is newer than that in target-dir, or it is not copied (so they aren't synchronized in this case).
target-dir and not in source-dir is deleted.
source-dir and not in target-dir is copied.
.* or *~ is not copied.
"delete" means moving to trash directory.
This is where target-dir is. (For example,
if target-dir is abc/efg then the trash directory
is abc/trash.)
The trash directory can be set as the third parameter as follows:
ruby oocp.rb source-dir target-dir trash-dir
If the option -s is used, no files are copied but
the process which will be done is listed.
ruby oocp.rb -s source-dir target-dir
oocp.rb is a sample application script using
this library dir-compare.rb. Here we show
other sample scripts for using dir-compare.rb:
The followings are the classes defined in this library.
Copy Command: cp.rb
The next command copys files and directories recursively.
#!/usr/local/bin/ruby
require "dir-compare"
require "ftools"
class Cp < DirPairWalker
def file_file(s, t)
File.cp(s, t)
end
def file_non(s, t)
File.cp(s, t)
end
def dir_non(s, t)
File.mkpath(t)
end
end
source, target = ARGV
visitor = Cp.new
dc = DirCompare.new(visitor)
dc.run(source, target)
To make a copy command, create the new class
(Cp for example) which inherits
DirPairWalker. And create the object (dc)
by DirCompare.new with the parameter of the instance
of Cp. Then the comparing is done when we invoke
dc.run with the directories as the parameters.
The actual commands are
defined as the certain methods of Cp.
The methods file_file(s, t), file_non(s, t)
and dir_non(s, t)
are called with the source file name s and the target
file name t. This pair of (s, t) runs recursively
over all files and subdirectories of (source, taget).
If s and t exist then file_file(s, t) is called, and
if s exists and t doesn't exist then
file_non(s, t) is called.
If s is a direcotry and t doesn't exist,
dir_non(s, t) is called.
See the reference of DirPairWalker for the detail.
Synchronizer for two directories: dir-sync.rb
The next command compares two directories and updates all files in them.
#!/usr/local/bin/ruby
require "dir-compare"
require "ftools"
class DirSync < DirPairWalker
def file_lt_file(s, t)
File.cp(t, s)
end
def file_gt_file(s, t)
File.cp(s, t)
end
def file_non(s, t)
File.cp(s, t)
end
def non_file(s, t)
File.cp(t, s)
end
def dir_non(s, t)
File.mkpath(t)
end
def non_dir(s, t)
File.mkpath(s)
end
end
source, target = ARGV
visitor = DirSync.new
dc = DirCompare.new(visitor)
dc.run(source, target)
The methods file_lt_file(s, t) and file_gt_file(s, t) are invoked when s is older than t or newer than it respectively.
Display the intersection of two directories: dir-common.rb
The next command compares two directories and show common files.
#!/usr/local/bin/ruby
require "dir-compare"
require "ftools"
class DirCommon < DirPairWalker
def file_file(s, t)
puts "#{s} <-> #{t}"
end
def dir_file(s, t)
throw :prune
end
def dir_non(s, t)
throw :prune
end
def file_dir(s, t)
throw :prune
end
def non_dir(s, t)
throw :prune
end
end
source, target = ARGV
visitor = DirCommon.new
dc = DirCompare.new(visitor)
dc.run(source, target)
We use throw :prune to avoid vain search.
Remove directories: rm-r.rb
The next command removes the directory recursively.
#!/usr/local/bin/ruby
require "dir-compare"
require "ftools"
class Rm_r < DirWalker
def file(s)
File.unlink(s)
end
def dir_out(s)
Dir.unlink(s)
end
end
rm = Rm_r.new
dc = DirCompare.new(rm)
path = ARGV.shift
dc.run(path)
dir_out is invoked when the walker
leaves the directory. See DirWalker.
Over-write Move: mv-o.rb
This command moves the directory. That is, the command:
"mv-o.rb source target"
is almost equivalent to
"rm -r target; mv source target"
, but the files and directories only in
target
are kept without change.
If source and target are directories,
the command is also almost equivalent to
"cp -r source/* target; rm -r source"
, but it uses rename (File.mv) as far as possible for performance.
#!/usr/local/bin/ruby
require "dir-compare"
require "ftools"
class Mv_o < DirPairWalker
def initialize
@file_dir = false
end
def file_file(s, t)
File.mv(s, t)
end
def file_dir(s, t)
@file_dir = true
end
def file_dir_out(s, t)
@file_dir = false
Dir.unlink(t)
File.mv(s, t)
end
def file_non(s, t)
dir = File.dirname(t)
File.mkpath(dir) unless File.directory?(dir)
File.mv(s, t)
end
def non_file(s, t)
if @file_dir
File.unlink(t)
end
end
def dir_file(s, t)
File.unlink(t)
File.mv(s, t)
throw :prune
end
def dir_dir_out(s, t)
Dir.rmdir(s)
end
def dir_non(s, t)
dir = File.dirname(t)
File.mkpath(dir) unless File.directory?(dir)
File.mv(s, t)
throw :prune
end
end
visitor = Mv_o.new
dc = DirCompare.new(visitor)
source, target = ARGV
dc.compare(source, target)
The methods named *_out are invoked when the walker
leaves the directory. See DirPairWalker.
This is the visitor class of a directory, which is given to
DirCompare.new.
"visitor" is the object that has the definitions of its behavior
when it meets the files or directories.
The following methods are invoked by DirCompare#run.
file(s)Called when s is a file.
dir(s)Called when s is a directory.
dir_out(s)Called when s is a directory and the walker leaves there.
run(mod, sqf)
This method is called directly by DirCompare#run.
And this calls the above 3 methods.
sqf is the QuasiFile objects.
mod is :in when the walker goes into the directory,
is :out when it leaves and is :empty when it meets
the file.
This is the similar class to DirWalker.
The difference is that the parameters
s of the methods
are not String but QuasiFile - object.
This is the visitor class of two directories, which is given to
DirCompare.new.
"visitor" is the object that has the definitions of its behavior
when it meets the files or directories.
The following methods are invoked by DirCompare#run.
The order of calling is "depth-first" according to the subdirectories.
s (resp. t) represents the source (resp. target) files or directory.
dir_dir(s, t)Called when s and t are directories.
dir_dir_out(s, t)Called when s and t are directories and the walker leaves there.
dir_file(s, t)Called when s is a directory and t is a file.
dir_file_out(s, t)Called when s is a directory, t is a file and the walker leaves there.
dir_non(s, t)Called when s is a directory and t does not exist.
dir_non_out(s, t)Called when s is a directory, t does not exist and the walker leaves there.
file_dir(s, t)Called when s is a file and t is a directory.
file_dir_out(s, t)Called when s is a file, t is a directory and the walker leaves there.
file_file(s, t)Called by file_eq_file, file_gt_file and file_lt_file when s and t are files .
file_eq_file(s, t)Called when s and t are files and s is as new as t, and calls file_file again.
file_gt_file(s, t)Called when s and t are files and s is newer than t, and calls file_file again.
file_lt_file(s, t)Called when s and t are files and s is older than t, and calls file_file again.
file_non(s, t)Called when s is a file and t does not exist.
non_dir(s, t)Called when s does not exist and t is a directory.
non_dir_out(s, t)Called when s does not exist, t is a directory and the walker leaves there.
non_file(s, t)Called when s does not exist and t is a file.
non_non(s, t)Called when s and t do not exist.
run(mod, sqf, tqf)
This method is called directly by DirCompare#run.
And this calls the above 17 methods.
sqf and tqf is the QuasiFile objects.
For mod, see DirCompare#parse.
This is the similar class to DirPairWalker.
The difference is that the parameters
(s and t) of the 17 methods
are not String but QuasiFile - object.
This is the class to compare plural directories.
DirCompare.new(visitor)Creates the instance with visitor as the visitor object.
run([path1, [path2, [path3,..]]])
compare([path1, [path2, [path3,..]]])
Invokes the visitor's method run(mod, qf1, qf2, qf3,..).
qf1, qf2, qf3,.. are the QuasiFile objects
created by path1, path2, path3,...
For mod, see parse.
This method is defined as follows:
def run(*paths)
qfs = paths.map{|path| QuasiFile.new(path)}
parse(*qfs) do |mod, *qfs0|
@visitor.run(mod, *qfs0)
end
end
Here the instance variable @visitor indicates the parameter accompanied when self is created.
parse([qf1, [qf2, [qf3,..]]])Called with parameters qf1, qf2, qf3, ... those are QuasiFile objects, and iterates with some block parameters. These block parameters are the mod and QuasiFile objects which run over all subdirectories and files of the contents of qf1, qf2, qf3, ... The order of iteration is "depth-first".
mod is determined by the next way:
:in ... at least one of the block parameters is a directory and the walker goes into the directory.
:out ... it leaves there.
:empty ... none of the block parameters is a directory (i.e. they are files or non-existence).
Example: Synchronize files in plural directories
#!/usr/local/bin/ruby
require "dir-compare"
require "ftools"
qfa = ARGV.map{|dir| QuasiFile.new(dir)}
dc = DirCompare.new
dc.parse(*qfa) do |mod, *qfb|
if mod == :empty
m = (0...qfb.size).max{|i, j| qfb[i] <=> qfb[j]}
qfb.each_with_index do |qf, i|
File.cp(qfb[m].path, qf.path) if i != m
end
end
end
We use the property of <=> that the non-existing file is the oldest here.
We can stop the further searching of subdirectories by
invoking throw :prune.
This is the fake file class which has the information of files. In concrete terms, this is the wrapper class of String and File::Stat and has the methods exist? and content.
QuasiFile.new(path)Creates instance having the information of the file or the directory represented by the String path.
This class has the methods in String listed below:
%, *, +, <<, [], []=, capitalize, capitalize!, center, chomp, chomp!, chop, chop!, collect, concat, count, crypt, delete, delete!, detect, downcase, downcase!, dump, each, each_byte, each_line, each_with_index, empty?, entries, find, find_all, grep, gsub, gsub!, hex, include?, index, intern, length, ljust, map, max, member?, min, next, next!, oct, reject, replace, reverse, reverse!, rindex, rjust, scan, select, slice, slice!, sort, split, squeeze, squeeze!, strip, strip!, sub, sub!, succ, succ!, sum, swapcase, swapcase!, to_f, to_i, to_str, tr, tr!, tr_s, tr_s!, unpack, upcase, upcase!, upto, ~
and the methods in File::Stat listed below:
atime, blksize, blockdev?, blocks, chardev?, ctime, dev, directory?, executable?, executable_real?, file?, ftype, gid, grpowned?, ino, mode, mtime, nlink, owned?, pipe?, rdev, readable?, readable_real?, setgid?, setuid?, size, size?, socket?, sticky?, symlink?, uid, writable?, writable_real?, zero?
The return value of the methods in File::Stat for
the path which doesn't exist is nil.
Other methods are following:
pathReturns own name.
stat
Returns own File::Stat object.
<=>(o)When o is QuasiFile object, returns the comparing of modifying time. The non-existing file is assumed to be the oldest.
contentReturns the content as an array when self is a directory. When self is a file or does not exist, returns the empty array.
exist?Returns true when self is a file or a directory which exists.
2.00.00
1.00.00