Changeset 9074

Show
Ignore:
Timestamp:
04/07/08 16:46:43 (6 years ago)
Author:
ihag
Message:

lang/ruby/mdmaildir/mdmerge.rb:

  • add -f COMMAND_FILE option for make cmdfile mode.
  • add -c -f COMMAND_FILE option for commit from cmdfile mode.
  • Introduced CMDProcessor class for abstracting the behavior of command.

lang/ruby/mdmaildir/mdd.rb: add comments.
lang/ruby/mdmaildir/README: fix typo.

Location:
lang/ruby/mdmaildir
Files:
3 modified

Legend:

Unmodified
Added
Removed
  • lang/ruby/mdmaildir/README

    r5682 r9074  
    1 Maildir manupiration scripts for migration and maintenance. 
     1Maildir manipuration scripts for migration and maintenance. 
    22 
    33- mdmerge.rb: Merge different two Maildir into one Maildir. 
  • lang/ruby/mdmaildir/mdd.rb

    r5682 r9074  
    11#!/usr/local/bin/ruby 
    22# $Id$ 
    3 require 'gdbm' 
    4 require 'digest/sha1' 
    5 require 'pp' 
    63 
     4# 
     5# = Manipulates message-digest file for whole of messages in the Maildir. 
     6# 
     7# Authors::  $Author: genta $ 
     8# Revision:: $Revision: 77 $ 
     9# 
     10class MDDigest 
     11  require 'gdbm' 
     12  require 'digest/sha1' 
     13  require 'pp' 
    714 
    8 # Maildir digest file class 
    9 class MDDigest 
    1015  attr_reader :path, :basedir, :db 
     16 
     17  # Create MDDigest instance.  MDDigest opens a message-digest file in 
     18  # the specified Maildir.  The message-digest file is stored into the 
     19  # top-level directory as filename like "_maildir_/mddigest". 
     20  # 
     21  # _maildir_:: Path of Maildir. 
     22  # returns::   An instance of MDDigest class 
    1123  def initialize(maildir) 
    1224    @basedir = maildir.dup 
     
    1628 
    1729  def open;  @db = GDBM.open(@path); end 
    18   def close; @db.reorganize.close;   end 
    19   def clear; @db.clear;              end 
     30  private :open 
    2031 
     32  # Close the backend DBM. Call this method at the end of your program. 
     33  def close; 
     34    @db.reorganize.close 
     35  end 
     36 
     37  # Truncate message-digest file. 
     38  def clear 
     39    @db.clear 
     40  end 
     41 
     42  # Caliculates a message-digest for specified message, and records it 
     43  # into message-digest file.  
     44  # 
     45  # _path_::  Relative path of a message from Maildir directory. 
     46  # returns:: void 
    2147  def add(path) 
    2248    apath = abspath(path) 
     
    3359  end 
    3460 
     61  # Check specified key and file are already records in message-digest 
     62  # file, or not. 
     63  # 
     64  # _key_::   Key of file-hash. It typically message-digest + size of 
     65  #           message. 
     66  # _file_::  Relative path of a message from Maildir directory. 
     67  # returns:: *true* if it already exists. 
     68  #           *false* for not exists. 
    3569  def has_file?(key, file) 
    3670    return false if @db[key].nil? 
     
    4175  end 
    4276 
     77  # Wrapper of GDBM#each. It is for traversal all of messages in Maildir 
     78  # by using a message-digest file. 
    4379  def each(&block) 
    4480    @db.each(&block) 
     
    4682 
    4783 
     84  private 
     85 
     86  # Caliculates a Message digest for specified message. 
     87  # 
     88  # _file_::  Path for a message. 
     89  # returns:: Instance of Digest::SHA1. 
    4890  def hash(file) 
    4991    md = Digest::SHA1.new 
     
    5799  end 
    58100 
    59   def abspath(file) 
    60     File.expand_path(file, @basedir) 
    61   end 
    62  
     101  # _key_:: 
     102  # _dest_:: 
    63103  def merge(key, dest) 
    64104    dfolder = folder(abspath(dest)) 
     
    77117  end 
    78118 
     119  # Determines a folder name for a specified message. 
     120  # 
     121  # _path_::  Relative path of a message from Maildir directory 
     122  # returns:: A folder name in Maildir for a message that directed by path. 
    79123  def folder(path) 
    80124    f, = path.split('/') 
     
    82126    return f 
    83127  end 
     128 
     129  # Convert relative path to the absolute path. The root of absolute path is 
     130  # Maildir directory. 
     131  # 
     132  # _file_::  Relative path from Maildir directory. 
     133  # returns:: Absolute path that converted from _file_. 
     134  def abspath(file) 
     135    File.expand_path(file, @basedir) 
     136  end 
    84137end 
  • lang/ruby/mdmaildir/mdmerge.rb

    r5682 r9074  
    55$LOAD_PATH << File.dirname($0) 
    66require 'mdd'  # MDDigest class 
     7require 'optparse' 
    78require 'pp' 
    89 
     10# store command-line configuration params. 
     11$config = Hash.new 
     12 
    913 
    1014 
    1115class CMD 
    12   attr_reader :fileq, :dirq, :skipdir, :basedir, :fromdir, :config 
    13   def initialize(basedir, fromdir, config = {}) 
    14     @fileq, @dirq = [], [] 
    15     @skipdir = [] 
     16  require 'tempfile' 
     17 
     18  attr_reader :basedir, :fromdir 
     19  def initialize(basedir, fromdir, config = Hash.new) 
     20    @config = config.dup 
    1621    @basedir = basedir.dup 
    1722    @fromdir = fromdir.dup 
    18     @config = config.dup 
     23    @logger = nil 
     24    @tempfile = Tempfile.new(File.basename($0)) 
     25  end 
     26 
     27  def openlog(file) 
     28    @logger = open(file, 'w') 
     29  end 
     30 
     31  def closelog 
     32    @logger.close 
     33    @logger = nil 
     34  end 
     35 
     36  def logging? 
     37    @logger.nil? ? false : true 
     38  end 
     39 
     40  def fd 
     41    logging? ? @logger : @tempfile 
     42  end 
     43  protected :fd 
     44 
     45  def rewind 
     46    return self if logging? 
     47    @tempfile.rewind 
     48  end 
     49  protected :rewind 
     50 
     51  def load(file) 
     52    @tempfile = open(file) 
    1953  end 
    2054 
    2155  def add(file) 
    22     mkdir(file) 
    23     cpfile(file) 
    24   end 
    25  
    26   def cpfile(rpath) 
    27     return if @fileq.include?(rpath) 
    28     @fileq << rpath 
    29   end 
    30  
    31   def mkdir(rpath) 
    32     rdir = File.dirname(rpath) 
    33     path = rdir.split(File::SEPARATOR) 
    34     path.pop if path[-1] =~ /^(cur|new|tmp)$/ 
    35     rdir = File.join(path) 
    36     adir = abspath(rdir) 
    37  
    38     return if @dirq.include?(rdir) or @skipdir.include?(rdir) 
    39     if test(?d, adir) then 
    40       puts "mkdir: already exists: #{adir}" 
    41       @skipdir << rdir 
    42       return 
    43     end 
    44     @dirq << rdir 
    45   end 
    46  
    47   def commit_check 
    48     @dirq.each do |dir| 
    49       puts "mkdir: #{dir}" 
    50     end 
    51  
    52     @fileq.each do |file| 
    53       puts "cp: #{file}" 
    54     end 
     56    fd.puts(file) 
     57    return self 
    5558  end 
    5659 
    5760  def commit 
    58     return if immediate? 
    59     do_mkdir 
    60     do_cpfile 
    61   end 
    62  
    63  
    64   def do_mkdir 
    65     @dirq.each do |rpath| 
    66       folder = rpath.split(File::SEPARATOR)[0].sub(/^\./, '') 
    67       #puts "maildirmake: #{folder} (from: #{rpath}, basedir: #{@basedir})" 
    68       system('maildirmake', '-f',  folder, @basedir) or 
    69         raise "maildirmake: #{$?}" 
    70     end 
    71   end 
    72  
    73   def do_cpfile 
    74     @fileq.each do |rpath| 
    75       tofile = File.expand_path(rpath, @basedir) 
     61    rewind 
     62    dircache = Hash.new(false) 
     63    while rpath = fd.gets 
     64      rpath.chomp! 
     65      folder = rpath_to_folder(rpath) 
     66      if dircache[folder] == false then 
     67        do_maildirmake(folder) 
     68        dircache[folder] = true 
     69      end 
     70 
     71      to_rpath = rpath.dup 
     72      unless imap_prefix.empty? then 
     73        if to_rpath =~ /^\./ then 
     74          to_rpath = '.' + imap_prefix + to_rpath 
     75        else 
     76          unless to_rpath =~ /^(cur|tmp|new)/ then 
     77            raise "Internal Error: rpath contains illegal path: #{to_rpath}" 
     78          end 
     79          to_rpath = '.' + imap_prefix + '/' + to_rpath 
     80        end 
     81      end 
     82      tofile = File.expand_path(to_rpath, @basedir) 
    7683      frfile = File.expand_path(rpath, @fromdir) 
    77  
    78       system('cp', '-p', frfile, tofile) or raise "cp: #{$?}" 
    79     end 
     84      do_cpfile(frfile, tofile) 
     85    end 
     86  end 
     87 
     88 
     89  private 
     90 
     91  def do_maildirmake(folder) 
     92    path = folder.split('.') 
     93    path.unshift(imap_prefix) unless imap_prefix.empty? 
     94    path.size.times do |i| 
     95      folder = path[0..i].join('.') 
     96      next if test(?e, abspath('.' + folder)) 
     97       
     98      args = ['maildirmake', '-f', folder, @basedir] 
     99      args.unshift('echo') if dry_run? 
     100      system(*args) or 
     101        raise("maildirmake: returned in status #{$?} for " + 
     102              "'maildirmake -f #{folder} #{@basedir}'") 
     103    end 
     104    return self 
     105  end 
     106 
     107  def do_cpfile(src, dest) 
     108    args = link? ? ['ln', '-f', src, dest] : ['cp', '-p', src, dest] 
     109    args.unshift('echo') if dry_run? 
     110    system(*args) or 
     111      raise("#{args[0]}: returned in status #{$?} for " + args.join(' ')) 
     112    return self 
     113  end 
     114 
     115  def rpath_to_folder(msg_rpath) 
     116    folder, = File.dirname(msg_rpath).split(File::SEPARATOR) 
     117    return '' unless folder.sub!(/^\./, '') 
     118    return folder 
    80119  end 
    81120 
     
    84123  end 
    85124 
    86   def immediate? 
    87     @config.key?(:immediate) 
    88   end 
    89 end 
     125  def dry_run?;    @config[:dry_run]; end 
     126  def link?;       @config[:link];    end 
     127  def imap_prefix; @config.key?(:prefix) ? @config[:prefix] : ''; end 
     128end 
     129 
     130 
     131class CMDProcessor 
     132  require 'forwardable' 
     133  extend Forwardable 
     134  def_delegators(:@cmd, :openlog, :closelog, :load, :commit) 
     135  protected :openlog, :closelog, :load, :commit 
     136 
     137  def initialize(updating_maildir, mergefrom_maildir, config) 
     138    @config = config 
     139    @mode = @config[:mode] 
     140    @cmdfile = @config[:cmdfile] 
     141 
     142    @updating = MDDigest.new(updating_maildir) 
     143    @mergefrom = MDDigest.new(mergefrom_maildir) 
     144 
     145    @cmd = CMD.new(@updating.basedir, @mergefrom.basedir, @config) 
     146  end 
     147 
     148  def process 
     149    raise "Please inhelit this class" 
     150  end 
     151 
     152  def close 
     153    $stderr.print 'Closing DBMs...' 
     154    @updating.close 
     155    @mergefrom.close 
     156    $stderr.puts ' done.' 
     157    return nil 
     158  end 
     159 
     160  protected 
     161  def check_maildir 
     162    @mergefrom.each do |key, file| 
     163      # 'files' are mail. that have a same hash digest. 
     164      files = file.split('\0') 
     165      files.each do |file| 
     166        @cmd.add(file) unless @updating.has_file?(key, file) 
     167      end 
     168    end 
     169    return self 
     170  end 
     171end 
     172 
     173class OnTheFly < CMDProcessor 
     174  def process 
     175    $stderr.puts 'Processing in on-the-fly mode.' 
     176 
     177    $stderr.print "Checking maildir... " 
     178    check_maildir 
     179    $stderr.puts "done." 
     180 
     181    $stderr.print "Commit... " 
     182    commit 
     183    $stderr.puts "done." 
     184 
     185    return self 
     186  end 
     187end 
     188 
     189class MakeCMDFile < CMDProcessor 
     190  def process 
     191    $stderr.puts 'Processing in make-cmdfile mode.' 
     192 
     193    openlog(@cmdfile) 
     194    $stderr.print "Checking maildir... " 
     195    check_maildir 
     196    $stderr.puts "done." 
     197    closelog 
     198 
     199    return self 
     200  end 
     201end 
     202 
     203class CommitFromFile < CMDProcessor 
     204  def process 
     205    $stderr.puts 'Processing in commit-from-file mode.' 
     206    self.load(@cmdfile) 
     207 
     208    $stderr.print "Commit... " 
     209    commit 
     210    $stderr.puts "done." 
     211 
     212    return self 
     213  end 
     214end 
     215 
    90216 
    91217def usage 
    92218  puts <<-_EOD_ 
    93 #{File.basename($0)} [base-maildir] [merge-from-maildir] 
     219#{File.basename($0)} [options]... [base-maildir] [merge-from-maildir] 
    94220Note: base-maildir will be overwritten.  Please backup first. 
     221 
     222Options: 
     223  -f COMMAND_FILE             実際に処理を行わず,COMMAND_FILEに処理の 
     224                              内容を書き出します. 
     225  -c -f COMMAND_FILE          COMMAND_FILEから処理の内容を読み込み,実 
     226                              行します. 
     227  -p IMAP_FOLDER_PREFIX       base-maildirにコピーする際,指定されたフ 
     228                              ォルダ以下にコピーを行います. 
     229  --link                      base-maildirにコピーする際,cp(1) の代わ 
     230                              りにln(1)を用います. 
     231  -n                          For debug. 
     232  -h                          Show usage. 
    95233  _EOD_ 
    96234end 
     
    98236def getargs 
    99237  $home = ENV['HOME'] or raise "HOME environment undefined" 
     238   
     239  commit_from_file = false 
     240  OptionParser.new do |opt| 
     241    opt.on('-f MANDATORY') {|val| $config[:cmdfile] = val } 
     242    opt.on('-n') {|v| $config[:dry_run] = true } 
     243    opt.on('-c') {|v| commit_from_file = true } 
     244    opt.on('-p MANDATORY') {|val| $config[:prefix] = val } 
     245    opt.on('--link') {|v| $config[:link] = true } 
     246    opt.on('-h') {|v| usage(); exit } 
     247    opt.parse!(ARGV) 
     248  end 
     249 
    100250  if ARGV.size != 2 then 
    101251    usage() 
     
    103253  end 
    104254  $src, $dest = ARGV[0..1] 
    105 end 
    106  
     255 
     256  # sanity check for options 
     257  $config[:mode] = :on_the_fly 
     258  if $config.key?(:cmdfile) then 
     259    $config[:mode] = :make_cmdfile 
     260  end 
     261  if (commit_from_file == true) then 
     262    $config[:mode] = :commit_from_file 
     263    unless test(?e, $config[:cmdfile]) then 
     264      raise "#{$config[:cmdfile]}: Not exists" 
     265    end 
     266  end 
     267end 
    107268 
    108269 
    109270getargs() 
    110 smd = MDDigest.new($src) 
    111 dmd = MDDigest.new($dest) 
    112 cmd = CMD.new(smd.basedir, dmd.basedir, {:immediate => true}) 
    113  
    114 dmd.each do |key, file| 
    115   files = file.split('\0')  # 'files' are mail. that have a same hash digest. 
    116   files.each do |file| 
    117     cmd.add(file) unless smd.has_file?(key, file) 
    118   end 
    119 end 
    120 cmd.commit 
    121  
    122 smd.close 
    123 dmd.close 
     271{ :on_the_fly => OnTheFly,  
     272  :make_cmdfile => MakeCMDFile, 
     273  :commit_from_file => CommitFromFile }[$config[:mode]]. 
     274  new($src, $dest, $config).process.close 
    124275__END__