Class: Redwood::Maildir

Inherits:
Source show all
Defined in:
lib/sup/maildir.rb

Overview

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.

Constant Summary

SCAN_INTERVAL =

seconds

30

Instance Attribute Summary

Attributes inherited from Source

#cur_offset, #id, #uri

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Source

#==, #done?, #reset!, #seek_to!, #to_s

Constructor Details

#initialize(uri, last_date = nil, usual = true, archived = false, id = nil, labels = [], mtimes = {}) ⇒ Maildir

Returns a new instance of Maildir

Raises:

  • (ArgumentError)


16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/sup/maildir.rb', line 16

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 = (labels || []).freeze
  @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

Class Method Details

.suggest_labels_for(path) ⇒ Object



38
# File 'lib/sup/maildir.rb', line 38

def self.suggest_labels_for path; [] end

Instance Method Details

#checkObject



41
42
43
44
45
46
# File 'lib/sup/maildir.rb', line 41

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) ⇒ Boolean

Returns:

  • (Boolean)


146
# File 'lib/sup/maildir.rb', line 146

def draft? msg; maildir_data(msg)[2].include? "D"; end

#eachObject



121
122
123
124
125
126
127
128
129
130
131
132
# File 'lib/sup/maildir.rb', line 121

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) ⇒ Object



48
49
50
51
52
53
54
55
# File 'lib/sup/maildir.rb', line 48

def each_raw_message_line id
  scan_mailbox
  with_file_for(id) do |f|
    until f.eof?
      yield f.gets
    end
  end
end

#end_offsetObject



139
140
141
142
# File 'lib/sup/maildir.rb', line 139

def end_offset
  scan_mailbox :rescan => true
  @ids.last + 1
end

#file_pathObject



37
# File 'lib/sup/maildir.rb', line 37

def file_path; @dir end

#flagged?(msg) ⇒ Boolean

Returns:

  • (Boolean)


147
# File 'lib/sup/maildir.rb', line 147

def flagged? msg; maildir_data(msg)[2].include? "F"; end

#is_source_for?(uri) ⇒ Boolean

Returns:

  • (Boolean)


39
# File 'lib/sup/maildir.rb', line 39

def is_source_for? uri; super || (URI(Source.expand_filesystem_uri(uri)) == URI(self.uri)); end

#load_header(id) ⇒ Object



57
58
59
60
# File 'lib/sup/maildir.rb', line 57

def load_header id
  scan_mailbox
  with_file_for(id) { |f| MBox::read_header f }
end

#load_message(id) ⇒ Object



62
63
64
65
# File 'lib/sup/maildir.rb', line 62

def load_message id
  scan_mailbox
  with_file_for(id) { |f| RMail::Parser.read f }
end

#mark_draft(msg) ⇒ Object



153
# File 'lib/sup/maildir.rb', line 153

def mark_draft msg; maildir_mark_file msg, "D" unless draft? msg; end

#mark_flagged(msg) ⇒ Object



154
# File 'lib/sup/maildir.rb', line 154

def mark_flagged msg; maildir_mark_file msg, "F" unless flagged? msg; end

#mark_passed(msg) ⇒ Object



155
# File 'lib/sup/maildir.rb', line 155

def mark_passed msg; maildir_mark_file msg, "P" unless passed? msg; end

#mark_replied(msg) ⇒ Object



156
# File 'lib/sup/maildir.rb', line 156

def mark_replied msg; maildir_mark_file msg, "R" unless replied? msg; end

#mark_seen(msg) ⇒ Object



157
# File 'lib/sup/maildir.rb', line 157

def mark_seen msg; maildir_mark_file msg, "S" unless seen? msg; end

#mark_trashed(msg) ⇒ Object



158
# File 'lib/sup/maildir.rb', line 158

def mark_trashed msg; maildir_mark_file msg, "T" unless trashed? msg; end

#passed?(msg) ⇒ Boolean

Returns:

  • (Boolean)


148
# File 'lib/sup/maildir.rb', line 148

def passed? msg; maildir_data(msg)[2].include? "P"; end

#pct_doneObject



144
# File 'lib/sup/maildir.rb', line 144

def pct_done; 100.0 * (@ids.index(cur_offset) || 0).to_f / (@ids.length - 1).to_f; end

#raw_header(id) ⇒ Object



67
68
69
70
71
72
73
74
75
76
# File 'lib/sup/maildir.rb', line 67

def raw_header id
  scan_mailbox
  ret = ""
  with_file_for(id) do |f|
    until f.eof? || (l = f.gets) =~ /^$/
      ret += l
    end
  end
  ret
end

#raw_message(id) ⇒ Object



78
79
80
81
# File 'lib/sup/maildir.rb', line 78

def raw_message id
  scan_mailbox
  with_file_for(id) { |f| f.read }
end

#replied?(msg) ⇒ Boolean

Returns:

  • (Boolean)


149
# File 'lib/sup/maildir.rb', line 149

def replied? msg; maildir_data(msg)[2].include? "R"; end

#scan_mailbox(opts = {}) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/sup/maildir.rb', line 83

def scan_mailbox opts={}
  return unless @ids.empty? || opts[:rescan]
  return if @last_scan && (Time.now - @last_scan) < SCAN_INTERVAL

  initial_poll = @ids.empty?

  Redwood::log "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
 Redwood::log "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
  
  Redwood::log "done scanning maildir"
  @last_scan = Time.now
end

#seen?(msg) ⇒ Boolean

Returns:

  • (Boolean)


150
# File 'lib/sup/maildir.rb', line 150

def seen? msg; maildir_data(msg)[2].include? "S"; end

#start_offsetObject



134
135
136
137
# File 'lib/sup/maildir.rb', line 134

def start_offset
  scan_mailbox
  @ids.first
end

#trashed?(msg) ⇒ Boolean

Returns:

  • (Boolean)


151
# File 'lib/sup/maildir.rb', line 151

def trashed? msg; maildir_data(msg)[2].include? "T"; end