]> git.cworth.org Git - sup/blob - lib/sup/mbox/loader.rb
preparations for imap
[sup] / lib / sup / mbox / loader.rb
1 require 'thread'
2 require 'rmail'
3
4 module Redwood
5 module MBox
6
7 class Error < StandardError; end
8
9 class Loader
10   attr_reader :filename
11   bool_reader :usual, :archived, :read, :dirty
12   attr_accessor :id, :labels
13
14   ## end_offset is the last offsets within the file which we've read.
15   ## everything after that is considered new messages that haven't
16   ## been indexed.
17   def initialize filename, end_offset=nil, usual=true, archived=false, id=nil
18     @filename = filename.gsub(%r(^mbox://), "")
19     @end_offset = end_offset || 0
20     @dirty = false
21     @usual = usual
22     @archived = archived
23     @id = id
24     @mutex = Mutex.new
25     @f = File.open @filename
26     @labels = ([
27       :unread,
28       archived ? nil : :inbox,
29     ] +
30       if File.dirname(filename) =~ /\b(var|usr|spool)\b/
31         []
32       else
33         [File.basename(filename).intern] 
34       end).compact
35   end
36
37   def seek_to! offset
38     @end_offset = [offset, File.size(@f) - 1].min;
39     @dirty = true
40   end
41   def reset!; seek_to! 0; end
42   def == o; o.is_a?(Loader) && o.filename == filename; end
43   def to_s; "mbox://#{@filename}"; end
44
45   def is_source_for? s
46     @filename == s || self.to_s == s
47   end
48
49   def load_header offset
50     raise ArgumentError, "nil offset" unless offset
51     header = nil
52     @mutex.synchronize do
53       @f.seek offset if offset
54       l = @f.gets
55       raise Error, "offset mismatch in mbox file offset #{offset.inspect}: #{l.inspect}. Run 'sup-import --rebuild #{to_s}' to correct this." unless l =~ BREAK_RE
56       header = MBox::read_header @f
57     end
58     header
59   end
60
61   def load_message offset
62     ret = nil
63     @mutex.synchronize do
64       @f.seek offset
65       RMail::Mailbox::MBoxReader.new(@f).each_message do |input|
66         return RMail::Parser.read(input)
67       end
68     end
69   end
70
71   def raw_header offset
72     ret = ""
73     @mutex.synchronize do
74       @f.seek offset
75       until @f.eof? || (l = @f.gets) =~ /^$/
76         ret += l
77       end
78     end
79     ret
80   end
81
82   def raw_full_message offset
83     ret = ""
84     @mutex.synchronize do
85       @f.seek offset
86       @f.gets # skip mbox header
87       until @f.eof? || (l = @f.gets) =~ BREAK_RE
88         ret += l
89       end
90     end
91     ret
92   end
93
94   def next
95     return nil if done?
96     @dirty = true
97     start_offset = nil
98     next_end_offset = @end_offset
99
100     ## @end_offset could be at one of two places here: before a \n and
101     ## a mbox separator, if it was previously at EOF and a new message
102     ## was added; or, at the beginning of an mbox separator (in all
103     ## other cases).
104     @mutex.synchronize do
105       @f.seek @end_offset
106       l = @f.gets or return nil
107       if l =~ /^\s*$/
108         start_offset = @f.tell
109         @f.gets
110       else
111         start_offset = @end_offset
112       end
113
114       while(line = @f.gets)
115         break if line =~ BREAK_RE
116         next_end_offset = @f.tell
117       end
118     end
119
120     @end_offset = next_end_offset
121     start_offset
122   end
123
124   def each
125     until @end_offset >= File.size(@f)
126       n = self.next
127       yield(n, labels) if n
128     end
129   end
130
131   def done?; @end_offset >= File.size(@f); end 
132   def total; File.size @f; end
133 end
134
135 Redwood::register_yaml(Loader, %w(filename end_offset usual archived id))
136
137 end
138 end