Parent

Larch::IMAP

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

Constants

Message

Larch::IMAP::Message represents a transferable IMAP message which can be passed between Larch::IMAP instances.

REGEX_URI

URI format validation regex.

Attributes

conn[R]
db_account[R]
mailboxes[R]
options[R]
quirks[R]

Public Class Methods

new(uri, options = {}) click to toggle source

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_run mode always behaves as if :create_mailbox is false.

: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 in ssl_certs. By default, server SSL certificates are not verified.

# File lib/larch/imap.rb, line 52
def initialize(uri, options = {})
  raise ArgumentError, "not an IMAP URI: #{uri}" unless uri.is_a?(URI) || uri =~ REGEX_URI
  raise ArgumentError, "options must be a Hash" unless options.is_a?(Hash)

  @uri     = uri.is_a?(URI) ? uri : URI(uri)
  @options = {
    :log_label   => "[#{username}@#{host}]",
    :max_retries => 3,
    :ssl_verify  => false
  }.merge(options)

  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

Public Instance Methods

connect() click to toggle source

Connects to the IMAP server and logs in if a connection hasn't already been established.

# File lib/larch/imap.rb, line 97
def connect
  return if @conn
  safely {} # connect, but do nothing else
end
delim() click to toggle source

Gets the server's mailbox hierarchy delimiter.

# File lib/larch/imap.rb, line 103
def delim
  @delim ||= safely { @conn.list('', '')[0].delim || '.'}
end
disconnect() click to toggle source

Closes the IMAP connection if one is currently open.

# File lib/larch/imap.rb, line 108
def disconnect
  return unless @conn

  begin
    @conn.disconnect
  rescue Errno::ENOTCONN => e
    debug "#{e.class.name}: #{e.message}"
  end

  reset

  info "disconnected"
end
each_mailbox() click to toggle source

Iterates through all mailboxes in the account, yielding each one as a Larch::IMAP::Mailbox instance to the given block.

# File lib/larch/imap.rb, line 124
def each_mailbox
  update_mailboxes
  @mailboxes.each_value {|mailbox| yield mailbox }
end
host() click to toggle source

Gets the IMAP hostname.

# File lib/larch/imap.rb, line 130
def host
  @uri.host
end
mailbox(name, delim = '/') click to toggle source

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.

# File lib/larch/imap.rb, line 138
def mailbox(name, delim = '/')
  retries = 0

  name.gsub!(/^(inbox\/?)/){ $1.upcase }
  name.gsub!(delim, self.delim)

  # Gmail doesn't allow folders with leading or trailing whitespace.
  name.strip! if @quirks[:gmail]

  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() click to toggle source

Sends an IMAP NOOP command.

# File lib/larch/imap.rb, line 166
def noop
  safely { @conn.noop }
end
password() click to toggle source

Gets the IMAP password.

# File lib/larch/imap.rb, line 171
def password
  CGI.unescape(@uri.password)
end
port() click to toggle source

Gets the IMAP port number.

# File lib/larch/imap.rb, line 176
def port
  @uri.port || (ssl? ? 993 : 143)
end
safely() click to toggle source

Connect if necessary, execute the given block, retry if a recoverable error occurs, die if an unrecoverable error occurs.

# File lib/larch/imap.rb, line 182
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.message} (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.message} (will retry)"

    sleep 1 * retries
    retry
  end

rescue Larch::Error => e
  raise

rescue Net::IMAP::Error => e
  raise Error, "#{e.class.name}: #{e.message} (giving up)"

rescue => e
  raise FatalError, "#{e.class.name}: #{e.message} (cannot recover)"
end
ssl?() click to toggle source

Gets the SSL status.

# File lib/larch/imap.rb, line 231
def ssl?
  @uri.scheme == 'imaps'
end
uri() click to toggle source

Gets the IMAP URI.

# File lib/larch/imap.rb, line 236
def uri
  @uri.to_s
end
uri_mailbox() click to toggle source

Gets the IMAP mailbox specified in the URI, or nil if none.

# File lib/larch/imap.rb, line 241
def uri_mailbox
  mb = @uri.path[1..-1]
  mb.nil? || mb.empty? ? nil : CGI.unescape(mb)
end
username() click to toggle source

Gets the IMAP username.

# File lib/larch/imap.rb, line 247
def username
  CGI.unescape(@uri.user)
end

[Validate]

Generated with the Darkfish Rdoc Generator 2.