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