]> git.cworth.org Git - sup/blob - lib/sup/mbox/loader.rb
added sup-sync-back
[sup] / lib / sup / mbox / loader.rb
1 require 'rmail'
2 require 'uri'
3
4 module Redwood
5 module MBox
6
7 class Loader < Source
8   def initialize uri_or_fp, start_offset=nil, usual=true, archived=false, id=nil
9     super
10
11     @mutex = Mutex.new
12     @labels = [:unread]
13
14     case uri_or_fp
15     when String
16       uri = URI(uri_or_fp)
17       raise ArgumentError, "not an mbox uri" unless uri.scheme == "mbox"
18       raise ArgumentError, "mbox uri ('#{uri}') cannot have a host: #{uri.host}" if uri.host
19       ## heuristic: use the filename as a label, unless the file
20       ## has a path that probably represents an inbox.
21       @labels << File.basename(uri.path).intern unless File.dirname(uri.path) =~ /\b(var|usr|spool)\b/
22       @f = File.open uri.path
23     else
24       @f = uri_or_fp
25     end
26   end
27
28   def check
29     if (cur_offset ||= start_offset) > end_offset
30       raise OutOfSyncSourceError, "mbox file is smaller than last recorded message offset. Messages have probably been deleted by another client."
31     end
32   end
33     
34   def start_offset; 0; end
35   def end_offset; File.size @f; end
36
37   def load_header offset
38     header = nil
39     @mutex.synchronize do
40       @f.seek offset
41       l = @f.gets
42       unless l =~ BREAK_RE
43         raise OutOfSyncSourceError, "mismatch in mbox file offset #{offset.inspect}: #{l.inspect}." 
44       end
45       header = MBox::read_header @f
46     end
47     header
48   end
49
50   def load_message offset
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 FatalSourceError, "error parsing mbox file: #{e.message}"
59       end
60     end
61   end
62
63   def raw_header offset
64     ret = ""
65     @mutex.synchronize do
66       @f.seek offset
67       until @f.eof? || (l = @f.gets) =~ /^$/
68         ret += l
69       end
70     end
71     ret
72   end
73
74   def raw_full_message offset
75     ret = ""
76     each_raw_full_message_line(offset) { |l| ret += l }
77     ret
78   end
79
80   ## apparently it's a million times faster to call this directly if
81   ## we're just moving messages around on disk, than reading things
82   ## into memory with raw_full_message.
83   ##
84   ## i hoped never to have to move shit around on disk but
85   ## sup-sync-back has to do it.
86   def each_raw_full_message_line offset
87     @mutex.synchronize do
88       @f.seek offset
89       yield @f.gets
90       until @f.eof? || (l = @f.gets) =~ BREAK_RE
91         yield l
92       end
93     end
94   end
95
96   def next
97     returned_offset = nil
98     next_offset = cur_offset
99
100     begin
101       @mutex.synchronize do
102         @f.seek cur_offset
103
104         ## cur_offset could be at one of two places here:
105
106         ## 1. before a \n and a mbox separator, if it was previously at
107         ##    EOF and a new message was added; or,
108         ## 2. at the beginning of an mbox separator (in all other
109         ##    cases).
110
111         l = @f.gets or raise "next while at EOF"
112         if l =~ /^\s*$/ # case 1
113           returned_offset = @f.tell
114           @f.gets # now we're at a BREAK_RE, so skip past it
115         else # case 2
116           returned_offset = cur_offset
117           ## we've already skipped past the BREAK_RE, so just go
118         end
119
120         while(line = @f.gets)
121           break if line =~ BREAK_RE
122           next_offset = @f.tell
123         end
124       end
125     rescue SystemCallError, IOError => e
126       raise FatalSourceError, "Error reading #{@f.path}: #{e.message}"
127     end
128
129     self.cur_offset = next_offset
130     [returned_offset, @labels.clone]
131   end
132 end
133
134 Redwood::register_yaml(Loader, %w(uri cur_offset usual archived id))
135
136 end
137 end