Class: Larch::IMAP
- Inherits:
-
Object
- Object
- Larch::IMAP
- Defined in:
- lib/larch/imap.rb,
lib/larch/errors.rb,
lib/larch/imap/mailbox.rb
Overview
Manages a connection to an IMAP server and all the glorious fun that entails.
This class borrows heavily from Sup, the source code of which should be required reading if you’re doing anything with IMAP in Ruby: sup.rubyforge.org
Defined Under Namespace
Classes: Error, FatalError, Mailbox, MailboxNotFoundError, Message, MessageNotFoundError
Constant Summary collapse
- REGEX_URI =
URI format validation regex.
URI.regexp(['imap', 'imaps'])
Instance Attribute Summary collapse
-
#conn ⇒ Object
readonly
Returns the value of attribute conn.
-
#db_account ⇒ Object
readonly
Returns the value of attribute db_account.
-
#mailboxes ⇒ Object
readonly
Returns the value of attribute mailboxes.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
-
#quirks ⇒ Object
readonly
Returns the value of attribute quirks.
Instance Method Summary collapse
-
#connect ⇒ Object
Connects to the IMAP server and logs in if a connection hasn’t already been established.
-
#delim ⇒ Object
Gets the server’s mailbox hierarchy delimiter.
-
#disconnect ⇒ Object
Closes the IMAP connection if one is currently open.
-
#each_mailbox ⇒ Object
Iterates through all mailboxes in the account, yielding each one as a Larch::IMAP::Mailbox instance to the given block.
-
#host ⇒ Object
Gets the IMAP hostname.
-
#initialize(uri, options = {}) ⇒ IMAP
constructor
Initializes a new Larch::IMAP instance that will connect to the specified IMAP URI.
-
#mailbox(name, delim = '/') ⇒ Object
Gets a Larch::IMAP::Mailbox instance representing the specified mailbox.
-
#noop ⇒ Object
Sends an IMAP NOOP command.
-
#password ⇒ Object
Gets the IMAP password.
-
#port ⇒ Object
Gets the IMAP port number.
-
#safely ⇒ Object
Connect if necessary, execute the given block, retry if a recoverable error occurs, die if an unrecoverable error occurs.
-
#ssl? ⇒ Boolean
Gets the SSL status.
-
#uri ⇒ Object
Gets the IMAP URI.
-
#uri_mailbox ⇒ Object
Gets the IMAP mailbox specified in the URI, or
nilif none. -
#username ⇒ Object
Gets the IMAP username.
Constructor Details
#initialize(uri, options = {}) ⇒ IMAP
Initializes a new Larch::IMAP instance that will connect to the specified IMAP URI.
In addition to the URI, the following options may be specified:
- :create_mailbox
-
If
true, mailboxes that don’t already exist will be created if necessary. - :dry_run
-
If
true, read-only operations will be performed as usual and all change operations will be simulated, but no changes will actually be made. Note that it’s not actually possible to simulate mailbox creation, so:dry_runmode always behaves as if:create_mailboxisfalse. - :log_label
-
Label to use for this connection in log output. If not specified, the default label is “[username@host]”.
- :max_retries
-
After a recoverable error occurs, retry the operation up to this many times. Default is 3.
- :ssl_certs
-
Path to a trusted certificate bundle to use to verify server SSL certificates. You can download a bundle of certificate authority root certs at curl.haxx.se/ca/cacert.pem (it’s up to you to verify that this bundle hasn’t been tampered with, however; don’t trust it blindly).
- :ssl_verify
-
If
true, server SSL certificates will be verified against the trusted certificate bundle specified inssl_certs. By default, server SSL certificates are not verified.
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/larch/imap.rb', line 52 def initialize(uri, = {}) raise ArgumentError, "not an IMAP URI: #{uri}" unless uri.is_a?(URI) || uri =~ REGEX_URI raise ArgumentError, "options must be a Hash" unless .is_a?(Hash) @uri = uri.is_a?(URI) ? uri : URI(uri) @options = { :log_label => "[#{username}@#{host}]", :max_retries => 3, :ssl_verify => false }.merge() raise ArgumentError, "must provide a username and password" unless @uri.user && @uri.password @conn = nil @mailboxes = {} @quirks = { :gmail => false, :yahoo => false } @db_account = Database::Account.find_or_create( :hostname => host, :username => username ) @db_account.touch # Create private convenience methods (debug, info, warn, etc.) to make # logging easier. Logger::LEVELS.each_key do |level| next if IMAP.private_method_defined?(level) IMAP.class_eval do define_method(level) do |msg| Larch.log.log(level, "#{@options[:log_label]} #{msg}") end private level end end end |
Instance Attribute Details
#conn ⇒ Object (readonly)
Returns the value of attribute conn.
9 10 11 |
# File 'lib/larch/imap.rb', line 9 def conn @conn end |
#db_account ⇒ Object (readonly)
Returns the value of attribute db_account.
9 10 11 |
# File 'lib/larch/imap.rb', line 9 def db_account @db_account end |
#mailboxes ⇒ Object (readonly)
Returns the value of attribute mailboxes.
9 10 11 |
# File 'lib/larch/imap.rb', line 9 def mailboxes @mailboxes end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
9 10 11 |
# File 'lib/larch/imap.rb', line 9 def @options end |
#quirks ⇒ Object (readonly)
Returns the value of attribute quirks.
9 10 11 |
# File 'lib/larch/imap.rb', line 9 def quirks @quirks end |
Instance Method Details
#connect ⇒ Object
Connects to the IMAP server and logs in if a connection hasn’t already been established.
97 98 99 100 |
# File 'lib/larch/imap.rb', line 97 def connect return if @conn safely {} # connect, but do nothing else end |
#delim ⇒ Object
Gets the server’s mailbox hierarchy delimiter.
103 104 105 |
# File 'lib/larch/imap.rb', line 103 def delim @delim ||= safely { @conn.list('', '')[0].delim || '.'} end |
#disconnect ⇒ Object
Closes the IMAP connection if one is currently open.
108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/larch/imap.rb', line 108 def disconnect return unless @conn begin @conn.disconnect rescue Errno::ENOTCONN => e debug "#{e.class.name}: #{e.}" end reset info "disconnected" end |
#each_mailbox ⇒ Object
Iterates through all mailboxes in the account, yielding each one as a Larch::IMAP::Mailbox instance to the given block.
124 125 126 127 |
# File 'lib/larch/imap.rb', line 124 def each_mailbox update_mailboxes @mailboxes.each_value {|mailbox| yield mailbox } end |
#host ⇒ Object
Gets the IMAP hostname.
130 131 132 |
# File 'lib/larch/imap.rb', line 130 def host @uri.host end |
#mailbox(name, delim = '/') ⇒ Object
Gets a Larch::IMAP::Mailbox instance representing the specified mailbox. If the mailbox doesn’t exist and the :create_mailbox option is false, or if :create_mailbox is true and mailbox creation fails, a Larch::IMAP::MailboxNotFoundError will be raised.
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 165 166 |
# File 'lib/larch/imap.rb', line 138 def mailbox(name, delim = '/') retries = 0 name.gsub!(/^(inbox\/?)/i){ $1.upcase } name.gsub!(delim, self.delim) # Gmail doesn't allow folders with leading or trailing whitespace. name.strip! if @quirks[:gmail] #Rackspace namespaces everything under INDEX. name.sub!(/^|inbox\./i, "INBOX.") if @quirks[:rackspace] && name != 'INBOX' begin @mailboxes.fetch(name) do update_mailboxes return @mailboxes[name] if @mailboxes.has_key?(name) raise MailboxNotFoundError, "mailbox not found: #{name}" end rescue MailboxNotFoundError => e raise unless @options[:create_mailbox] && retries == 0 info "creating mailbox: #{name}" safely { @conn.create(Net::IMAP.encode_utf7(name)) } unless @options[:dry_run] retries += 1 retry end end |
#noop ⇒ Object
Sends an IMAP NOOP command.
169 170 171 |
# File 'lib/larch/imap.rb', line 169 def noop safely { @conn.noop } end |
#password ⇒ Object
Gets the IMAP password.
174 175 176 |
# File 'lib/larch/imap.rb', line 174 def password CGI.unescape(@uri.password) end |
#port ⇒ Object
Gets the IMAP port number.
179 180 181 |
# File 'lib/larch/imap.rb', line 179 def port @uri.port || (ssl? ? 993 : 143) end |
#safely ⇒ Object
Connect if necessary, execute the given block, retry if a recoverable error occurs, die if an unrecoverable error occurs.
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/larch/imap.rb', line 185 def safely safe_connect retries = 0 begin yield rescue Errno::ECONNABORTED, Errno::ECONNRESET, Errno::ENOTCONN, Errno::EPIPE, Errno::ETIMEDOUT, IOError, Net::IMAP::ByeResponseError, OpenSSL::SSL::SSLError => e raise unless (retries += 1) <= @options[:max_retries] warning "#{e.class.name}: #{e.} (reconnecting)" reset sleep 1 * retries safe_connect retry rescue Net::IMAP::BadResponseError, Net::IMAP::NoResponseError, Net::IMAP::ResponseParseError => e raise unless (retries += 1) <= @options[:max_retries] warning "#{e.class.name}: #{e.} (will retry)" sleep 1 * retries retry end rescue Larch::Error => e raise rescue Net::IMAP::Error => e raise Error, "#{e.class.name}: #{e.} (giving up)" rescue => e raise FatalError, "#{e.class.name}: #{e.} (cannot recover)" end |
#ssl? ⇒ Boolean
Gets the SSL status.
234 235 236 |
# File 'lib/larch/imap.rb', line 234 def ssl? @uri.scheme == 'imaps' end |
#uri ⇒ Object
Gets the IMAP URI.
239 240 241 |
# File 'lib/larch/imap.rb', line 239 def uri @uri.to_s end |
#uri_mailbox ⇒ Object
Gets the IMAP mailbox specified in the URI, or nil if none.
244 245 246 247 |
# File 'lib/larch/imap.rb', line 244 def uri_mailbox mb = @uri.path[1..-1] mb.nil? || mb.empty? ? nil : CGI.unescape(mb) end |
#username ⇒ Object
Gets the IMAP username.
250 251 252 |
# File 'lib/larch/imap.rb', line 250 def username CGI.unescape(@uri.user) end |