Class: Redwood::Source
Direct Known Subclasses
Instance Attribute Summary collapse
-
#id ⇒ Object
Returns the value of attribute id.
-
#uri ⇒ Object
readonly
Returns the value of attribute uri.
-
#usual ⇒ Object
readonly
Implementing a new source should be easy, because Sup only needs to be able to: 1.
Class Method Summary collapse
- .encode_path_for_uri(path) ⇒ Object
- .expand_filesystem_uri(uri) ⇒ Object
-
.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.
Instance Method Summary collapse
- #==(o) ⇒ Object
- #fallback_date_for_message(info) ⇒ Object
-
#file_path ⇒ Object
overwrite me if you have a disk incarnation.
-
#go_idle ⇒ Object
release resources that are easy to reacquire.
-
#initialize(uri, usual = true, archived = false, id = nil) ⇒ Source
constructor
A new instance of Source.
- #is_source_for?(uri) ⇒ Boolean
-
#labels?(info) ⇒ Boolean
Returns an array containing all the labels that are currently in the location filename.
-
#poll ⇒ Object
Yields values of the form [Symbol, Hash] add: info, labels, progress delete: info, progress.
- #read? ⇒ Boolean
-
#supported_labels? ⇒ Boolean
Returns an array containing all the labels that are natively supported by this source.
- #synchronize(&block) ⇒ Object
- #to_s ⇒ Object
- #try_lock ⇒ Object
- #unlock ⇒ Object
- #valid?(info) ⇒ Boolean
Constructor Details
#initialize(uri, usual = true, archived = false, id = nil) ⇒ Source
Returns a new instance of Source.
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
#id ⇒ Object
Returns the value of attribute id.
58 59 60 |
# File 'lib/sup/source.rb', line 58 def id @id end |
#uri ⇒ Object (readonly)
Returns the value of attribute uri.
57 58 59 |
# File 'lib/sup/source.rb', line 57 def uri @uri end |
#usual ⇒ Object (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
.encode_path_for_uri(path) ⇒ Object
175 176 177 178 179 |
# File 'lib/sup/source.rb', line 175 def Source.encode_path_for_uri path path.gsub(Regexp.new("[#{Regexp.quote(URI_ENCODE_CHARS)}]")) { |c| c.each_byte.map { |x| sprintf("%%%02X", x) }.join } end |
.expand_filesystem_uri(uri) ⇒ Object
171 172 173 |
# File 'lib/sup/source.rb', line 171 def Source. uri uri.gsub "~", File.("~") end |
.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!
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 163 164 |
# File 'lib/sup/source.rb', line 132 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 |
#fallback_date_for_message(info) ⇒ Object
93 |
# File 'lib/sup/source.rb', line 93 def info; end |
#file_path ⇒ Object
overwrite me if you have a disk incarnation
72 |
# File 'lib/sup/source.rb', line 72 def file_path; nil end |
#go_idle ⇒ Object
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
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
91 |
# File 'lib/sup/source.rb', line 91 def labels? info; [] end |
#poll ⇒ Object
Yields values of the form [Symbol, Hash] add: info, labels, progress delete: info, progress
98 99 100 |
# File 'lib/sup/source.rb', line 98 def poll unimplemented end |
#read? ⇒ 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
87 |
# File 'lib/sup/source.rb', line 87 def supported_labels?; [] end |
#synchronize(&block) ⇒ Object
106 107 108 |
# File 'lib/sup/source.rb', line 106 def synchronize &block @poll_lock.synchronize(&block) end |
#to_s ⇒ Object
74 |
# File 'lib/sup/source.rb', line 74 def to_s; @uri.to_s; end |
#try_lock ⇒ Object
110 111 112 113 114 115 116 117 118 |
# File 'lib/sup/source.rb', line 110 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 |
#unlock ⇒ Object
120 121 122 123 |
# File 'lib/sup/source.rb', line 120 def unlock @poll_lock.exit debug "lock released for: #{self}" end |
#valid?(info) ⇒ Boolean
102 103 104 |
# File 'lib/sup/source.rb', line 102 def valid? info true end |