class Redwood::Maildir

Maildir doesn't provide an ordered unique id, which is what Sup requires to be really useful. So we must maintain, in memory, a mapping between Sup "ids" (timestamps, essentially) and the pathnames on disk.

Constants

MYHOSTNAME
SCAN_INTERVAL

Public Class Methods

new(uri, last_date=nil, usual=true, archived=false, id=nil, labels=[], mtimes={}) click to toggle source
# File lib/sup/maildir.rb, line 18
def initialize uri, last_date=nil, usual=true, archived=false, id=nil, labels=[], mtimes={}
  super uri, last_date, usual, archived, id
  uri = URI(Source.expand_filesystem_uri(uri))

  raise ArgumentError, "not a maildir URI" unless uri.scheme == "maildir"
  raise ArgumentError, "maildir URI cannot have a host: #{uri.host}" if uri.host
  raise ArgumentError, "maildir URI must have a path component" unless uri.path

  @dir = uri.path
  @labels = Set.new(labels || [])
  @ids = []
  @ids_to_fns = {}
  @last_scan = nil
  @mutex = Mutex.new
  #the mtime from the subdirs in the maildir with the unix epoch as default.
  #these are used to determine whether scanning the directory for new mail
  #is a worthwhile effort
  @mtimes = { 'cur' => Time.at(0), 'new' => Time.at(0) }.merge(mtimes || {})
  @dir_ids = { 'cur' => [], 'new' => [] }
end
suggest_labels_for(path;) click to toggle source
# File lib/sup/maildir.rb, line 40
def self.suggest_labels_for path; [] end

Public Instance Methods

check() click to toggle source
# File lib/sup/maildir.rb, line 43
def check
  scan_mailbox
  return unless start_offset

  start = @ids.index(cur_offset || start_offset) or raise OutOfSyncSourceError, "Unknown message id #{cur_offset || start_offset}." # couldn't find the most recent email
end
draft?(msg;) click to toggle source
# File lib/sup/maildir.rb, line 176
def draft? msg; maildir_data(msg)[2].include? "D"; end
each() { |id, labels + (seen?(id) ? [] : [:unread]) + (trashed?(id) ? [:deleted] : []) + (flagged?(id) ? [:starred] : [])| ... } click to toggle source
# File lib/sup/maildir.rb, line 151
def each
  scan_mailbox
  return unless start_offset

  start = @ids.index(cur_offset || start_offset) or raise OutOfSyncSourceError, "Unknown message id #{cur_offset || start_offset}." # couldn't find the most recent email

  start.upto(@ids.length - 1) do |i|         
    id = @ids[i]
    self.cur_offset = id
    yield id, @labels + (seen?(id) ? [] : [:unread]) + (trashed?(id) ? [:deleted] : []) + (flagged?(id) ? [:starred] : [])
  end
end
each_raw_message_line(id) { |gets| ... } click to toggle source
# File lib/sup/maildir.rb, line 78
def each_raw_message_line id
  scan_mailbox
  with_file_for(id) do |f|
    until f.eof?
      yield f.gets
    end
  end
end
end_offset() click to toggle source
# File lib/sup/maildir.rb, line 169
def end_offset
  scan_mailbox :rescan => true
  @ids.last + 1
end
file_path() click to toggle source
# File lib/sup/maildir.rb, line 39
def file_path; @dir end
filename_for_id(id;) click to toggle source
# File lib/sup/maildir.rb, line 190
def filename_for_id id; @ids_to_fns[id] end
flagged?(msg;) click to toggle source
# File lib/sup/maildir.rb, line 177
def flagged? msg; maildir_data(msg)[2].include? "F"; end
is_source_for?(uri;) click to toggle source
# File lib/sup/maildir.rb, line 41
def is_source_for? uri; super || (URI(Source.expand_filesystem_uri(uri)) == URI(self.uri)); end
load_header(id) click to toggle source
# File lib/sup/maildir.rb, line 87
def load_header id
  scan_mailbox
  with_file_for(id) { |f| parse_raw_email_header f }
end
load_message(id) click to toggle source
# File lib/sup/maildir.rb, line 92
def load_message id
  scan_mailbox
  with_file_for(id) { |f| RMail::Parser.read f }
end
mark_draft(msg;) click to toggle source
# File lib/sup/maildir.rb, line 183
def mark_draft msg; maildir_mark_file msg, "D" unless draft? msg; end
mark_flagged(msg;) click to toggle source
# File lib/sup/maildir.rb, line 184
def mark_flagged msg; maildir_mark_file msg, "F" unless flagged? msg; end
mark_passed(msg;) click to toggle source
# File lib/sup/maildir.rb, line 185
def mark_passed msg; maildir_mark_file msg, "P" unless passed? msg; end
mark_replied(msg;) click to toggle source
# File lib/sup/maildir.rb, line 186
def mark_replied msg; maildir_mark_file msg, "R" unless replied? msg; end
mark_seen(msg;) click to toggle source
# File lib/sup/maildir.rb, line 187
def mark_seen msg; maildir_mark_file msg, "S" unless seen? msg; end
mark_trashed(msg;) click to toggle source
# File lib/sup/maildir.rb, line 188
def mark_trashed msg; maildir_mark_file msg, "T" unless trashed? msg; end
passed?(msg;) click to toggle source
# File lib/sup/maildir.rb, line 178
def passed? msg; maildir_data(msg)[2].include? "P"; end
pct_done() click to toggle source
# File lib/sup/maildir.rb, line 174
def pct_done; 100.0 * (@ids.index(cur_offset) || 0).to_f / (@ids.length - 1).to_f; end
raw_header(id) click to toggle source
# File lib/sup/maildir.rb, line 97
def raw_header id
  scan_mailbox
  ret = ""
  with_file_for(id) do |f|
    until f.eof? || (l = f.gets) =~ %r^$/
      ret += l
    end
  end
  ret
end
raw_message(id) click to toggle source
# File lib/sup/maildir.rb, line 108
def raw_message id
  scan_mailbox
  with_file_for(id) { |f| f.read }
end
replied?(msg;) click to toggle source
# File lib/sup/maildir.rb, line 179
def replied? msg; maildir_data(msg)[2].include? "R"; end
scan_mailbox(opts={}) click to toggle source
# File lib/sup/maildir.rb, line 113
def scan_mailbox opts={}
  return unless @ids.empty? || opts[:rescan]
  return if @last_scan && (Time.now - @last_scan) < SCAN_INTERVAL

  initial_poll = @ids.empty?

  debug "scanning maildir #@dir..."
  begin
    @mtimes.each_key do |d|
      subdir = File.join(@dir, d)
      raise FatalSourceError, "#{subdir} not a directory" unless File.directory? subdir

      mtime = File.mtime subdir

      #only scan the dir if the mtime is more recent (or we haven't polled
      #since startup)
      if @mtimes[d] < mtime || initial_poll
        @mtimes[d] = mtime
        @dir_ids[d] = []
        Dir[File.join(subdir, '*')].map do |fn|
          id = make_id fn
          @dir_ids[d] << id
          @ids_to_fns[id] = fn
        end
      else
        debug "no poll on #{d}.  mtime on indicates no new messages."
      end
    end
    @ids = @dir_ids.values.flatten.uniq.sort!
  rescue SystemCallError, IOError => e
    raise FatalSourceError, "Problem scanning Maildir directories: #{e.message}."
  end
  
  debug "done scanning maildir"
  @last_scan = Time.now
end
seen?(msg;) click to toggle source
# File lib/sup/maildir.rb, line 180
def seen? msg; maildir_data(msg)[2].include? "S"; end
start_offset() click to toggle source
# File lib/sup/maildir.rb, line 164
def start_offset
  scan_mailbox
  @ids.first
end
store_message(date, from_email) { |f| ... } click to toggle source
# File lib/sup/maildir.rb, line 50
def store_message date, from_email, &block
  stored = false
  new_fn = new_maildir_basefn + ':2,S'
  Dir.chdir(@dir) do |d|
    tmp_path = File.join(@dir, 'tmp', new_fn)
    new_path = File.join(@dir, 'new', new_fn)
    begin
      sleep 2 if File.stat(tmp_path)

      File.stat(tmp_path)
    rescue Errno::ENOENT #this is what we want.
      begin
        File.open(tmp_path, 'wb') do |f|
          yield f #provide a writable interface for the caller
          f.fsync
        end

        File.link tmp_path, new_path
        stored = true
      ensure
        File.unlink tmp_path if File.exists? tmp_path
      end
    end #rescue Errno...
  end #Dir.chdir

  stored
end
trashed?(msg;) click to toggle source
# File lib/sup/maildir.rb, line 181
def trashed? msg; maildir_data(msg)[2].include? "T"; end