X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=lib%2Fsup%2Fsource.rb;h=6510aae8a738dba0b3d81acb617312a1e1334bec;hb=95e1f7e3fb05db7f313c49e82040eb6b626fb10d;hp=4ce1f9b9782988dfcf29d885b2bfd97ad74a0db5;hpb=45e9a7d94bb5d4285c15e6e3453ebe0bd4ad0843;p=sup diff --git a/lib/sup/source.rb b/lib/sup/source.rb index 4ce1f9b..6510aae 100644 --- a/lib/sup/source.rb +++ b/lib/sup/source.rb @@ -1,64 +1,114 @@ module Redwood -class SourceError < StandardError; end +class SourceError < StandardError + def initialize *a + raise "don't instantiate me!" if SourceError.is_a?(self.class) + super + end +end +class OutOfSyncSourceError < SourceError; end +class FatalSourceError < SourceError; end class Source - ## dirty? described whether cur_offset has changed, which means the - ## source needs to be re-saved to disk. + ## Implementing a new source should be easy, because Sup only needs + ## to be able to: + ## 1. See how many messages it contains + ## 2. Get an arbitrary message + ## 3. (optional) see whether the source has marked it read or not ## - ## broken? means no message can be loaded (e.g. IMAP server is - ## down), so don't even bother. - bool_reader :usual, :archived, :dirty - attr_reader :cur_offset, :broken_msg - attr_accessor :id + ## In particular, Sup doesn't need to move messages, mark them as + ## read, delete them, or anything else. (Well, it's nice to be able + ## to delete them, but that is optional.) + ## + ## On the other hand, Sup assumes that you can assign each message a + ## unique integer id, such that newer messages have higher ids than + ## earlier ones, and that those ids stay constant across sessions + ## (in the absence of some other client going in and fucking + ## everything up). For example, for mboxes I use the file offset of + ## the start of the message. If a source does NOT have that + ## capability, e.g. IMAP, then you have to do a little more work to + ## simulate it. + ## + ## To write a new source, subclass this class, and implement: + ## + ## - start_offset + ## - end_offset (exclusive!) + ## - load_header offset + ## - load_message offset + ## - raw_header offset + ## - raw_message offset + ## - check + ## - next (or each, if you prefer): should return a message and an + ## array of labels. + ## + ## ... where "offset" really means unique id. (You can tell I + ## started with mbox.) + ## + ## All exceptions relating to accessing the source must be caught + ## and rethrown as FatalSourceErrors or OutOfSyncSourceErrors. + ## OutOfSyncSourceErrors should be used for problems that a call to + ## sup-sync will fix (namely someone's been playing with the source + ## from another client); FatalSourceErrors can be used for anything + ## else (e.g. the imap server is down or the maildir is missing.) + ## + ## Finally, be sure the source is thread-safe, since it WILL be + ## pummelled from multiple threads at once. + ## + ## Examples for you to look at: mbox/loader.rb, imap.rb, and + ## maildir.rb. - ## You should implement: + ## let's begin! ## - ## start_offset - ## end_offset - ## load_header(offset) - ## load_message(offset) - ## raw_header(offset) - ## raw_full_message(offset) - ## next + ## dirty? means cur_offset has changed, so the source info needs to + ## be re-saved to sources.yaml. + bool_reader :usual, :archived, :dirty + attr_reader :uri, :cur_offset + attr_accessor :id def initialize uri, initial_offset=nil, usual=true, archived=false, id=nil + raise ArgumentError, "id must be an integer: #{id.inspect}" unless id.is_a? Fixnum if id + @uri = uri - @cur_offset = initial_offset || start_offset + @cur_offset = initial_offset @usual = usual @archived = archived @id = id @dirty = false - @broken_msg = nil end - def broken?; !@broken_msg.nil?; end - def to_s; @uri; end + def file_path; nil end + + def to_s; @uri.to_s; end def seek_to! o; self.cur_offset = o; end def reset!; seek_to! start_offset; end - def == o; o.to_s == to_s; end - def done?; cur_offset >= end_offset; end - def is_source_for? s; to_s == s; end + def == o; o.uri == uri; end + def done?; start_offset.nil? || (self.cur_offset ||= start_offset) >= end_offset; end + def is_source_for? uri; uri == @uri; end + + ## check should throw a FatalSourceError or an OutOfSyncSourcError + ## if it can detect a problem. it is called when the sup starts up + ## to proactively notify the user of any source problems. + def check; end def each + self.cur_offset ||= start_offset until done? n, labels = self.next raise "no message" unless n - labels += [:inbox] unless archived? yield n, labels end end protected + + def Source.expand_filesystem_uri uri + uri.gsub "~", File.expand_path("~") + end def cur_offset= o @cur_offset = o @dirty = true end - - attr_writer :broken_msg end -Redwood::register_yaml(Source, %w(uri cur_offset usual archived id)) - end