]> git.cworth.org Git - sup/blob - lib/sup/mbox/loader.rb
notify user of source errors on startup and on poll
[sup] / lib / sup / mbox / loader.rb
1 require 'rmail'
2
3 module Redwood
4 module MBox
5
6 class Loader < Source
7   def initialize uri_or_fp, start_offset=nil, usual=true, archived=false, id=nil
8     super
9
10     @mutex = Mutex.new
11     @labels = [:unread]
12
13     case uri_or_fp
14     when String
15       raise ArgumentError, "not an mbox uri" unless uri_or_fp =~ %r!mbox://!
16
17       fn = uri_or_fp.sub(%r!^mbox://!, "")
18       ## heuristic: use the filename as a label, unless the file
19       ## has a path that probably represents an inbox.
20       @labels << File.basename(fn).intern unless File.dirname(fn) =~ /\b(var|usr|spool)\b/
21       @f = File.open fn
22     else
23       @f = uri_or_fp
24     end
25
26     if cur_offset > end_offset
27       self.broken_msg = "mbox file is smaller than last recorded message offset. Messages have probably been deleted via another client. Run 'sup-import --rebuild #{to_s}' to correct this."
28     end
29   end
30
31   def start_offset; 0; end
32   def end_offset; File.size @f; end
33
34   def load_header offset
35     header = nil
36     @mutex.synchronize do
37       @f.seek offset
38       l = @f.gets
39       unless l =~ BREAK_RE
40         Redwood::log "#{to_s}: offset mismatch in mbox file offset #{offset.inspect}: #{l.inspect}"
41         self.broken_msg = "offset mismatch in mbox file offset #{offset.inspect}: #{l.inspect}. Run 'sup-import --rebuild #{to_s}' to correct this." 
42         raise SourceError, self.broken_msg
43       end
44       header = MBox::read_header @f
45     end
46     header
47   end
48
49   def load_message offset
50     raise SourceError, self.broken_msg if broken?
51     @mutex.synchronize do
52       @f.seek offset
53       begin
54         RMail::Mailbox::MBoxReader.new(@f).each_message do |input|
55           return RMail::Parser.read(input)
56         end
57       rescue RMail::Parser::Error => e
58         raise SourceError, "error parsing message with rmail: #{e.message}"
59       end
60     end
61   end
62
63   def raw_header offset
64     raise SourceError, self.broken_msg if broken?
65     ret = ""
66     @mutex.synchronize do
67       @f.seek offset
68       until @f.eof? || (l = @f.gets) =~ /^$/
69         ret += l
70       end
71     end
72     ret
73   end
74
75   def raw_full_message offset
76     raise SourceError, self.broken_msg if broken?
77     ret = ""
78     @mutex.synchronize do
79       @f.seek offset
80       @f.gets # skip mbox header
81       until @f.eof? || (l = @f.gets) =~ BREAK_RE
82         ret += l
83       end
84     end
85     ret
86   end
87
88   def next
89     raise SourceError, self.broken_msg if broken?
90     returned_offset = nil
91     next_offset = cur_offset
92
93     @mutex.synchronize do
94       @f.seek cur_offset
95
96       ## cur_offset could be at one of two places here:
97
98       ## 1. before a \n and a mbox separator, if it was previously at
99       ##    EOF and a new message was added; or,
100       ## 2. at the beginning of an mbox separator (in all other
101       ##    cases).
102
103       l = @f.gets or raise "next while at EOF"
104       if l =~ /^\s*$/ # case 1
105         returned_offset = @f.tell
106         @f.gets # now we're at a BREAK_RE, so skip past it
107       else # case 2
108         returned_offset = cur_offset
109         ## we've already skipped past the BREAK_RE, so just go
110       end
111
112       while(line = @f.gets)
113         break if line =~ BREAK_RE
114         next_offset = @f.tell
115       end
116     end
117
118     self.cur_offset = next_offset
119     [returned_offset, @labels.clone]
120   end
121 end
122
123 Redwood::register_yaml(Loader, %w(uri cur_offset usual archived id))
124
125 end
126 end