root/lang/ruby/mdmaildir/mdmerge.rb @ 9074

Revision 9074, 6.3 kB (checked in by ihag, 7 years ago)

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.

  • Property svn:executable set to *
  • Property svn:keywords set to Id
Line 
1#!/usr/local/bin/ruby
2# mdmerge.rb -- Merge two maildir into one.
3# $Id$
4
5$LOAD_PATH << File.dirname($0)
6require 'mdd'  # MDDigest class
7require 'optparse'
8require 'pp'
9
10# store command-line configuration params.
11$config = Hash.new
12
13
14
15class CMD
16  require 'tempfile'
17
18  attr_reader :basedir, :fromdir
19  def initialize(basedir, fromdir, config = Hash.new)
20    @config = config.dup
21    @basedir = basedir.dup
22    @fromdir = fromdir.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)
53  end
54
55  def add(file)
56    fd.puts(file)
57    return self
58  end
59
60  def commit
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)
83      frfile = File.expand_path(rpath, @fromdir)
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
119  end
120
121  def abspath(file)
122    File.expand_path(file, @basedir)
123  end
124
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
216
217def usage
218  puts <<-_EOD_
219#{File.basename($0)} [options]... [base-maildir] [merge-from-maildir]
220Note: 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.
233  _EOD_
234end
235
236def getargs
237  $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
250  if ARGV.size != 2 then
251    usage()
252    exit
253  end
254  $src, $dest = ARGV[0..1]
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
268
269
270getargs()
271{ :on_the_fly => OnTheFly,
272  :make_cmdfile => MakeCMDFile,
273  :commit_from_file => CommitFromFile }[$config[:mode]].
274  new($src, $dest, $config).process.close
275__END__
Note: See TracBrowser for help on using the browser.