Class: Redwood::Source

Inherits:
Object show all
Defined in:
lib/sup/source.rb

Direct Known Subclasses

DraftLoader, MBox, Maildir

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(uri, usual = true, archived = false, id = nil) ⇒ Source

Returns a new instance of Source.

Raises:

  • (ArgumentError)


67
68
69
70
71
72
73
74
# File 'lib/sup/source.rb', line 67

def initialize uri, usual=true, archived=false, id=nil
  raise ArgumentError, "id must be an integer: #{id.inspect}" unless id.is_a? Fixnum if id

  @uri = uri
  @usual = usual
  @archived = archived
  @id = id
end

Instance Attribute Details

#idObject

Returns the value of attribute id.



65
66
67
# File 'lib/sup/source.rb', line 65

def id
  @id
end

#uriObject (readonly)

Returns the value of attribute uri.



64
65
66
# File 'lib/sup/source.rb', line 64

def uri
  @uri
end

Class Method Details

.parse_raw_email_header(f) ⇒ Object

utility method to read a raw email header from an IO stream and turn it into a hash of key-value pairs. minor special semantics for certain headers.

THIS IS A SPEED-CRITICAL SECTION. Everything you do here will have a significant effect on Sup’s processing speed of email from ALL sources. Little things like string interpolation, regexp interpolation, += vs <<, all have DRAMATIC effects. BE CAREFUL WHAT YOU DO!



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/sup/source.rb', line 108

def self.parse_raw_email_header f
  header = {}
  last = nil

  while(line = f.gets)
    case line
    ## these three can occur multiple times, and we want the first one
    when /^(Delivered-To|X-Original-To|Envelope-To):\s*(.*?)\s*$/i; header[last = $1.downcase] ||= $2
    ## regular header: overwrite (not that we should see more than one)
    ## TODO: figure out whether just using the first occurrence changes
    ## anything (which would simplify the logic slightly)
    when /^([^:\s]+):\s*(.*?)\s*$/i; header[last = $1.downcase] = $2
    when /^\r*$/; break # blank line signifies end of header
    else
      if last
        header[last] << " " unless header[last].empty?
        header[last] << line.strip
      end
    end
  end

  %w(subject from to cc bcc).each do |k|
    v = header[k] or next
    next unless Rfc2047.is_encoded? v
    header[k] = begin
      Rfc2047.decode_to $encoding, v
    rescue Errno::EINVAL, Iconv::InvalidEncoding, Iconv::IllegalSequence => e
      #debug "warning: error decoding RFC 2047 header (#{e.class.name}): #{e.message}"
      v
    end
  end
  header
end

Instance Method Details

#==(o) ⇒ Object



80
# File 'lib/sup/source.rb', line 80

def == o; o.uri == uri; end

#file_pathObject

overwrite me if you have a disk incarnation (currently used only for sup-sync-back)



77
# File 'lib/sup/source.rb', line 77

def file_path; nil end

#go_idleObject

release resources that are easy to reacquire. it is called after processing a source (e.g. polling) to prevent resource leaks (esp. file descriptors).



88
# File 'lib/sup/source.rb', line 88

def go_idle; end

#is_source_for?(uri) ⇒ Boolean

Returns:

  • (Boolean)


81
# File 'lib/sup/source.rb', line 81

def is_source_for? uri; uri == @uri; end

#pollObject

Yields values of the form [Symbol, Hash] add: info, labels, progress delete: info, progress



93
94
95
# File 'lib/sup/source.rb', line 93

def poll
  unimplemented
end

#read?Boolean

Returns:

  • (Boolean)


83
# File 'lib/sup/source.rb', line 83

def read?; false; end

#to_sObject



79
# File 'lib/sup/source.rb', line 79

def to_s; @uri.to_s; end

#usualObject

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

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!) (or, #done?)

  • load_header offset

  • load_message offset

  • raw_header offset

  • raw_message offset

  • check (optional)

  • go_idle (optional)

  • 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.



63
# File 'lib/sup/source.rb', line 63

bool_accessor :usual, :archived

#valid?(info) ⇒ Boolean

Returns:

  • (Boolean)


97
98
99
# File 'lib/sup/source.rb', line 97

def valid? info
  true
end