OOCP

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.

Installation

Put dir-compare.rb in the directory where Ruby can load. The copy command is oocp.rb.

Usage

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:

"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

dir-compare.rb

oocp.rb is a sample application script using this library dir-compare.rb. Here we show other sample scripts for using dir-compare.rb:

  1. Copy
  2. Dir-Sync
  3. Dir-Common
  4. Rm-r
  5. Mv-o

The followings are the classes defined in this library.

  1. DirCompare
  2. DirWalker
  3. DirRunner
  4. DirPairWalker
  5. DirPairRunner
  6. QuasiFile

Samples

Copy

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.

Dir-Sync

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.

Dir-Common

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.

Rm-r

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.

Mv-o

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.

DirWalker

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.

Methods

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.

DirRunner

This is the similar class to DirWalker. The difference is that the parameters s of the methods are not String but QuasiFile - object.

DirPairWalker

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.

Methods

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.

DirPairRunner

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.

DirCompare

This is the class to compare plural directories.

Class Methods

DirCompare.new(visitor)

Creates the instance with visitor as the visitor object.

Methods

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:

mod

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

QuasiFile

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.

Included Modules

Class Methods

QuasiFile.new(path)

Creates instance having the information of the file or the directory represented by the String path.

Methods

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:

path

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

content

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

Change Log