8 yaml_properties :uri, :cur_offset, :usual, :archived, :id, :labels
9 def initialize uri_or_fp, start_offset=nil, usual=true, archived=false, id=nil, labels=[]
10 super uri_or_fp, start_offset, usual, archived, id
13 @labels = (labels || []).freeze
18 raise ArgumentError, "not an mbox uri" unless uri.scheme == "mbox"
19 raise ArgumentError, "mbox uri ('#{uri}') cannot have a host: #{uri.host}" if uri.host
20 @f = File.open uri.path
26 def file_path; URI(uri).path end
28 def self.suggest_labels_for path
29 ## heuristic: use the filename as a label, unless the file
30 ## has a path that probably represents an inbox.
31 if File.dirname(path) =~ /\b(var|usr|spool)\b/
34 [File.basename(path).intern]
39 if (cur_offset ||= start_offset) > end_offset
40 raise OutOfSyncSourceError, "mbox file is smaller than last recorded message offset. Messages have probably been deleted by another client."
44 def start_offset; 0; end
45 def end_offset; File.size @f; end
47 def load_header offset
53 raise OutOfSyncSourceError, "mismatch in mbox file offset #{offset.inspect}: #{l.inspect}."
55 header = MBox::read_header @f
60 def load_message offset
64 RMail::Mailbox::MBoxReader.new(@f).each_message do |input|
65 return RMail::Parser.read(input)
67 rescue RMail::Parser::Error => e
68 raise FatalSourceError, "error parsing mbox file: #{e.message}"
77 until @f.eof? || (l = @f.gets) =~ /^$/
84 def raw_full_message offset
86 each_raw_full_message_line(offset) { |l| ret += l }
90 ## apparently it's a million times faster to call this directly if
91 ## we're just moving messages around on disk, than reading things
92 ## into memory with raw_full_message.
94 ## i hoped never to have to move shit around on disk but
95 ## sup-sync-back has to do it.
96 def each_raw_full_message_line offset
100 until @f.eof? || (l = @f.gets) =~ BREAK_RE
107 returned_offset = nil
108 next_offset = cur_offset
111 @mutex.synchronize do
114 ## cur_offset could be at one of two places here:
116 ## 1. before a \n and a mbox separator, if it was previously at
117 ## EOF and a new message was added; or,
118 ## 2. at the beginning of an mbox separator (in all other
121 l = @f.gets or raise "next while at EOF"
122 if l =~ /^\s*$/ # case 1
123 returned_offset = @f.tell
124 @f.gets # now we're at a BREAK_RE, so skip past it
126 returned_offset = cur_offset
127 ## we've already skipped past the BREAK_RE, so just go
130 while(line = @f.gets)
131 break if line =~ BREAK_RE
132 next_offset = @f.tell
135 rescue SystemCallError, IOError => e
136 raise FatalSourceError, "Error reading #{@f.path}: #{e.message}"
139 self.cur_offset = next_offset
140 [returned_offset, @labels]