]> git.cworth.org Git - sup/blob - lib/sup/maildir.rb
improved source error handling
[sup] / lib / sup / maildir.rb
1 require 'rmail'
2 require 'uri'
3
4 module Redwood
5
6 ## Maildir doesn't provide an ordered unique id, which is what Sup
7 ## requires to be really useful. So we must maintain, in memory, a
8 ## mapping between Sup "ids" (timestamps, essentially) and the
9 ## pathnames on disk.
10
11 class Maildir < Source
12   SCAN_INTERVAL = 30 # seconds
13
14   def initialize uri, last_date=nil, usual=true, archived=false, id=nil
15     super
16
17     @dir = URI(uri).path
18     @ids = []
19     @ids_to_fns = {}
20     @last_scan = nil
21     @mutex = Mutex.new
22   end
23
24   def load_header id
25     scan_mailbox
26     with_file_for(id) { |f| MBox::read_header f }
27   end
28
29   def load_message id
30     scan_mailbox
31     with_file_for(id) { |f| RMail::Parser.read f }
32   end
33
34   def raw_header id
35     scan_mailbox
36     ret = ""
37     with_file_for(id) do |f|
38       until f.eof? || (l = f.gets) =~ /^$/
39         ret += l
40       end
41     end
42     ret
43   end
44
45   def raw_full_message id
46     scan_mailbox
47     with_file_for(id) { |f| f.readlines.join }
48   end
49
50   def scan_mailbox
51     return if @last_scan && (Time.now - @last_scan) < SCAN_INTERVAL
52
53     cdir = File.join(@dir, 'cur')
54     ndir = File.join(@dir, 'new')
55
56     begin
57       @ids, @ids_to_fns = @mutex.synchronize do
58         ids, ids_to_fns = [], {}
59         (Dir[File.join(cdir, "*")] + Dir[File.join(ndir, "*")]).map do |fn|
60           id = make_id fn
61           ids << id
62           ids_to_fns[id] = fn
63         end
64         [ids.sort, ids_to_fns]
65       end
66     rescue SystemCallError => e
67       raise FatalSourceError, "Problem scanning Maildir directories: #{e.message}."
68     end
69     
70     @last_scan = Time.now
71   end
72
73   def each
74     scan_mailbox
75     start = @ids.index(cur_offset || start_offset) or raise OutOfSyncSourceError, "Unknown message id #{cur_offset || start_offset}." # couldn't find the most recent email
76
77     start.upto(@ids.length - 1) do |i|         
78       id = @ids[i]
79       self.cur_offset = id
80       yield id, (@ids_to_fns[id] =~ /,.*R.*$/ ? [] : [:unread])
81     end
82   end
83
84   def start_offset
85     scan_mailbox
86     @ids.first
87   end
88
89   def end_offset
90     scan_mailbox
91     @ids.last
92   end
93
94   def pct_done; 100.0 * (@ids.index(cur_offset) || 0).to_f / (@ids.length - 1).to_f; end
95
96 private
97
98   def make_id fn
99     # use 7 digits for the size. why 7? seems nice.
100     sprintf("%d%07d", File.mtime(fn), File.size(fn)).to_i
101   end
102
103   def with_file_for id
104     fn = @ids_to_fns[id] or raise OutOfSyncSourceError, "No such id: #{id.inspect}."
105     begin
106       File.open(fn) { |f| yield f }
107     rescue SystemCallError => e
108       raise FatalSourceError, "Problem reading file for id #{id.inspect}: #{fn.inspect}: #{e.message}."
109     end
110   end
111 end
112
113 Redwood::register_yaml(Maildir, %w(uri cur_offset usual archived id))
114
115 end