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

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

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

+ (Object) suggest_labels_for(path)



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

def self.suggest_labels_for path; [] end

Instance Method Details

- (Object) check



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

- (Boolean) draft?(msg)

Returns:

  • (Boolean)


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

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

- (Object) each



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

- (Object) each_raw_message_line(id)



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

- (Object) end_offset



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

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

- (Object) file_path



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

def file_path; @dir end

- (Boolean) flagged?(msg)

Returns:

  • (Boolean)


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

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

- (Boolean) is_source_for?(uri)

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

- (Object) load_header(id)



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

- (Object) load_message(id)



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

- (Object) mark_draft(msg)



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

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

- (Object) mark_flagged(msg)



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

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

- (Object) mark_passed(msg)



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

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

- (Object) mark_replied(msg)



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

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

- (Object) mark_seen(msg)



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

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

- (Object) mark_trashed(msg)



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

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

- (Boolean) passed?(msg)

Returns:

  • (Boolean)


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

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

- (Object) pct_done



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

- (Object) raw_header(id)



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

- (Object) raw_message(id)



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

- (Boolean) replied?(msg)

Returns:

  • (Boolean)


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

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

- (Object) scan_mailbox(opts = {})



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

- (Boolean) seen?(msg)

Returns:

  • (Boolean)


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

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

- (Object) start_offset



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

def start_offset
  scan_mailbox
  @ids.first
end

- (Boolean) trashed?(msg)

Returns:

  • (Boolean)


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

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