tmail/tmail/loader.rb

#
# loader.rb
#
#   Copyright (c) 1999 Minero Aoki <aamine@dp.u-netsurf.ne.jp>
#
#   This program is free software.
#   You can distribute/modify this program under the terms of
#   the GNU Lesser General Public License version 2 or later.
#

require 'amstd/futils'


module TMail


class Loader

  include FileUtils

  def each_filename
    filenames.each {|i| yield i }
  end

end



class MhLoader < Loader

  def initialize( dname )
    @dirname = mustdir( dname )
    @oldnext = nil
    @lasttime = nil
  end


  def filenames
    arr = []
    foreach_fullpath( @dirname ) do |path|
      next unless /\A\d+\z/o === basename(path)
      next unless file? path
      arr.push path
    end
    arr
  end


  def nextfile
    n = @oldnext
    unless n then
      n = 0
      each_filename do |fname, base|
        i = base.to_i
        n = i if i > n
      end
      n += 1
    end

    while exist? sepjoin( @dirname, n ) do
      n += 1
    end
    @oldnext = n

    return sepjoin( @dirname, n )
  end


  def each_mail
    arr = []
    @lasttime = Time.now
    each_filename do |fname|
      yield FilePort.new( fname )
    end
  end

  alias each each_mail


  def new_mail
    return FilePort.new( nextfile )
  end

  alias new_port new_mail


  def each_newmail( mtime = nil )
    mtime ||= @lasttime
    raise ArgumentError, "last update time is not given"

    return if mtime > File.mtime( @dirname )
    @lasttime = Time.now
    each_filename do |fname|
      if File.mtime( fname ) > mtime then
        yield FilePort.new( fname )
      end
    end
  end

end



class MboxLoader < Loader

  def initialize( fname, tempdir = nil )
    @filename = expand( fname )
    unless file? @filename then
      raise ArgumentError, "'#{fname}' is not normal file"
    end

    if tempdir then
      @tempdir = mustdir( tempdir )
    else
      @tempdir = isdir( "/tmp/tmail_mboxloader_#{$$}_#{id}" )
      @finalizer = MboxLoader.mkproc( @tempdir )
      ObjectSpace.add_finalizer @finalizer
    end

    @real = nil
    @closed = false
  end

  def close
    return if @closed

    @closed = true
    @real = nil
    @tempdir = nil
    @finalizer.call
    @finalizer = nil
  end


  def each_mail
    close_check
    sync
    @real.each_mail {|p| yield p }
  end
  alias each each_mail


  def each_newmail( mtime = nil )
    close_check
    sync
    @real.each_newmail( mtime ) {|p| yield p }
  end


  def new_mail
    raise
  end

  alias new_port new_mail


  private


  def sync
    wf = nil
    n = 1

    lock do |f|
      begin
        f.each do |line|
          if /\AFrom /o then
            if wf then
              wf.close
            end
            wf = File.open( sepjoin(@tempdir, n) )
            n += 1
          else
            wf << line if wf
          end
        end
        if port then
          ws.close
          yield port
        end
      ensure
        if wf and not wf.closed? then
          wf.close
        end
      end
    end

    @real = MhLoader.new( @tempdir )
  end


  def lock
    begin
      f = File.open( @filename )
      begin
        f.flock File::LOCK_EX
        yield f
      ensure
        f.flock File::LOCK_UN
      end
    ensure
      f.close if f
    end
  end

end



class MaildirLoader < Loader

  def initialize( dname )
    @dirname = mustdir( dname )
    @new = mustdir( sepjoin( dname, 'new' ) )
    @tmp = mustdir( sepjoin( dname, 'tmp' ) )
    @cur = mustdir( sepjoin( dname, 'cur' ) )
  end

  def close
    @closed = true
  end


  def each_filename( dn )
    Dir.foreach( dn ) do |fn|
      full = sepjoin(dn, fn)
      if fn[0] != ?. and File.file? full then
        yield full, fn
      end
    end
  end


  def each_mail
    each_filename( @tmp ) do |full, fname|
      yield FilePort.new( full )
    end
  end
  alias each each_mail


  def new_mail
    fn = nil
    tmp = nil
    i = 0

    while true do
      fn = "#{Time.now.to_i}.#{$$}.#{ENV['HOSTNAME']}"
      tmp = sepjoin(@tmp, fn )
      break unless File.exist? tmp
      i += 1
      if i == 3 then
        raise IOError, "can't create new file in maildir"
      end
      sleep 1 #2
    end
    File.open( tmp, 'w' ){|f| f.write "\n\n" }
    cur = sepjoin(@cur, fn)
    File.rename tmp, cur

    FilePort.new( cur )
  end
  alias new_port new_mail


  def each_newmail
    arr = []
    each_filename( @new ) do |old, fname|
      new = sepjoin(@new, fname + ':2,')
      File.rename old, new
      arr.push FilePort.new( new )
    end
    arr.each{|i| yield i }

    check_tmp
  end


  TOO_OLD = 60 * 60 * 36   # 36 hour

  def check_tmp
    old = Time.now.to_i - TOO_OLD
    
    each_filename( @tmp ) do |full, fname|
      if File.file? full then
        if File.stat(full).mtime.to_i < nt then
          File.rm_f full
        end
      end
    end
  end
  
end


end   # module TMail