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)


60
61
62
63
64
65
66
67
68
69
# File 'lib/sup/source.rb', line 60

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

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

  @poll_lock = Monitor.new
end

Instance Attribute Details

#idObject

Returns the value of attribute id.



58
59
60
# File 'lib/sup/source.rb', line 58

def id
  @id
end

#uriObject (readonly)

Returns the value of attribute uri.



57
58
59
# File 'lib/sup/source.rb', line 57

def uri
  @uri
end

#usualObject (readonly)

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

Messages are identified internally based on the message id, and stored with an unique document id. Along with the message, source information that can contain arbitrary fields (set up by the source) is stored. This information will be passed back to the source when a message in the index (Sup database) needs to be identified to its source, e.g. when re-reading or modifying a unique message.

To write a new source, subclass this class, and implement:

  • initialize

  • load_header offset

  • load_message offset

  • raw_header offset

  • raw_message offset

  • store_message (optional)

  • poll (loads new messages)

  • go_idle (optional)

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.rb and maildir.rb.



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

bool_accessor :usual, :archived

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!



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/sup/source.rb', line 130

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
      #debug "warning: error decoding RFC 2047 header (#{e.class.name}): #{e.message}"
      v
    end
  end
  header
end

Instance Method Details

#==(o) ⇒ Object



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

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

#file_pathObject

overwrite me if you have a disk incarnation



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

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



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

def go_idle; end

#is_source_for?(uri) ⇒ Boolean

Returns:

  • (Boolean)


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

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

#labels?(info) ⇒ Boolean

Returns an array containing all the labels that are currently in the location filename

Returns:

  • (Boolean)


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

def labels? info; [] end

#pollObject

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



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

def poll
  unimplemented
end

#read?Boolean

Returns:

  • (Boolean)


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

def read?; false; end

#supported_labels?Boolean

Returns an array containing all the labels that are natively supported by this source

Returns:

  • (Boolean)


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

def supported_labels?; [] end

#synchronize(&block) ⇒ Object



104
105
106
# File 'lib/sup/source.rb', line 104

def synchronize &block
  @poll_lock.synchronize(&block)
end

#to_sObject



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

def to_s; @uri.to_s; end

#try_lockObject



108
109
110
111
112
113
114
115
116
# File 'lib/sup/source.rb', line 108

def try_lock
  acquired = @poll_lock.try_enter
  if acquired
    debug "lock acquired for: #{self}"
  else
    debug "could not acquire lock for: #{self}"
  end
  acquired
end

#unlockObject



118
119
120
121
# File 'lib/sup/source.rb', line 118

def unlock
  @poll_lock.exit
  debug "lock released for: #{self}"
end

#valid?(info) ⇒ Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/sup/source.rb', line 100

def valid? info
  true
end