| 1 | #!/usr/local/bin/ruby |
|---|
| 2 | # $Id$ |
|---|
| 3 | |
|---|
| 4 | # |
|---|
| 5 | # = Manipulates message-digest file for whole of messages in the Maildir. |
|---|
| 6 | # |
|---|
| 7 | # Authors:: $Author: genta $ |
|---|
| 8 | # Revision:: $Revision: 77 $ |
|---|
| 9 | # |
|---|
| 10 | class MDDigest |
|---|
| 11 | require 'gdbm' |
|---|
| 12 | require 'digest/sha1' |
|---|
| 13 | require 'pp' |
|---|
| 14 | |
|---|
| 15 | 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 |
|---|
| 23 | def initialize(maildir) |
|---|
| 24 | @basedir = maildir.dup |
|---|
| 25 | @path = File.join(@basedir, 'mddigest') |
|---|
| 26 | open() |
|---|
| 27 | end |
|---|
| 28 | |
|---|
| 29 | def open; @db = GDBM.open(@path); end |
|---|
| 30 | private :open |
|---|
| 31 | |
|---|
| 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 |
|---|
| 47 | def add(path) |
|---|
| 48 | apath = abspath(path) |
|---|
| 49 | |
|---|
| 50 | md = hash(apath).hexdigest |
|---|
| 51 | size = File.size(apath) |
|---|
| 52 | key = md + '|' + size.to_s |
|---|
| 53 | |
|---|
| 54 | if @db.key?(key) then |
|---|
| 55 | merge(key, path) |
|---|
| 56 | return |
|---|
| 57 | end |
|---|
| 58 | @db[key] = path |
|---|
| 59 | end |
|---|
| 60 | |
|---|
| 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. |
|---|
| 69 | def has_file?(key, file) |
|---|
| 70 | return false if @db[key].nil? |
|---|
| 71 | @db[key].split('\0').each do |src| |
|---|
| 72 | return true if src == file |
|---|
| 73 | end |
|---|
| 74 | return false |
|---|
| 75 | end |
|---|
| 76 | |
|---|
| 77 | # Wrapper of GDBM#each. It is for traversal all of messages in Maildir |
|---|
| 78 | # by using a message-digest file. |
|---|
| 79 | def each(&block) |
|---|
| 80 | @db.each(&block) |
|---|
| 81 | end |
|---|
| 82 | |
|---|
| 83 | |
|---|
| 84 | private |
|---|
| 85 | |
|---|
| 86 | # Caliculates a Message digest for specified message. |
|---|
| 87 | # |
|---|
| 88 | # _file_:: Path for a message. |
|---|
| 89 | # returns:: Instance of Digest::SHA1. |
|---|
| 90 | def hash(file) |
|---|
| 91 | md = Digest::SHA1.new |
|---|
| 92 | File.open(file, 'r') do |fd| |
|---|
| 93 | buf = '' |
|---|
| 94 | while fd.read(256, buf) |
|---|
| 95 | md << buf |
|---|
| 96 | end |
|---|
| 97 | end |
|---|
| 98 | return md |
|---|
| 99 | end |
|---|
| 100 | |
|---|
| 101 | # _key_:: |
|---|
| 102 | # _dest_:: |
|---|
| 103 | def merge(key, dest) |
|---|
| 104 | dfolder = folder(abspath(dest)) |
|---|
| 105 | list = @db[key].split('\0') |
|---|
| 106 | list.map! do |src| |
|---|
| 107 | return if src == dest |
|---|
| 108 | if folder(src) == dfolder then |
|---|
| 109 | return if File.mtime(abspath(dest)) < File.mtime(abspath(src)) |
|---|
| 110 | next |
|---|
| 111 | end |
|---|
| 112 | src |
|---|
| 113 | end.compact! |
|---|
| 114 | list += [dest] |
|---|
| 115 | raise "Idential" if list.join('\0') == @db[key] |
|---|
| 116 | @db[key] = list.join('\0') |
|---|
| 117 | end |
|---|
| 118 | |
|---|
| 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. |
|---|
| 123 | def folder(path) |
|---|
| 124 | f, = path.split('/') |
|---|
| 125 | return '' unless f =~ /^\./ |
|---|
| 126 | return f |
|---|
| 127 | 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 |
|---|
| 137 | end |
|---|