Module: EventMachine::IMAP::Connection

Includes:
EM::Deferrable, CommandSender, TagSequence, DeferrableSSL, ResponseParser
Defined in:
lib/em-imap/connection.rb

Defined Under Namespace

Modules: Debug, TagSequence

Class Method Summary collapse

Instance Method Summary collapse

Methods included from TagSequence

#next_tag!

Methods included from DeferrableSSL

#ssl_handshake_completed, #start_tls

Methods included from ResponseParser

#receive_data

Methods included from CommandSender

#prepare_idle_continuation, #send_authentication_data, #send_command_object, #send_literal, #send_string

Methods included from ContinuationSynchronisation

#await_continuations, #awaiting_continuation?, #listen_for_continuation, #when_not_awaiting_continuation

Methods included from EventMachine::IMAP::CommandSender::LineBuffer

#send_line_buffered

Class Method Details

.connect(host, port, ssl = false) ⇒ Object

Create a new connection to an IMAP server.

Parameters:

  • host,

    The host name (warning DNS lookups are synchronous)

  • port,

    The port to connect to.

  • ssl=false,

    Whether or not to use TLS.

Returns:

  • Connection, a deferrable that will succeed when the server has replied with OK or PREAUTH, or fail if the connection could not be established, or the first response was BYE.


23
24
25
26
27
# File 'lib/em-imap/connection.rb', line 23

def self.connect(host, port, ssl=false)
  EventMachine.connect(host, port, self).tap do |conn|
    conn.start_tls if ssl
  end
end

.debug!Object


194
195
196
# File 'lib/em-imap/connection.rb', line 194

def self.debug!
  include IMAP::Connection::Debug
end

Instance Method Details

#add_response_handler(&block) ⇒ Object

Create a new listener for responses from the IMAP server.

Parameters:

  • &block,

    a block to which all responses will be passed.

Returns:

  • Listener, an object with a .stop method that you can use to unregister this block.

    You may also want to listen on the Listener's errback for when problems arise. The Listener's callbacks will be called after you call its stop method.


98
99
100
101
102
103
# File 'lib/em-imap/connection.rb', line 98

def add_response_handler(&block)
  Listener.new(&block).tap do |listener|
    listener.stopback{ listener.succeed }
    add_to_listener_pool(listener)
  end
end

#add_to_listener_pool(listener) ⇒ Object


105
106
107
# File 'lib/em-imap/connection.rb', line 105

def add_to_listener_pool(listener)
  @listeners << listener.bothback{ @listeners.delete listener }
end

#hello_listenerObject

Returns a Listener that is active during connection setup, and which is succeeded or failed as soon as we've received a greeting from the server.


57
58
59
# File 'lib/em-imap/connection.rb', line 57

def hello_listener
  @hello_listener ||= Listener.new.errback{ |e| fail e }.bothback{ hello_listener.stop }
end

#listen_for_failureObject

Attach life-long listeners on various conditions that we want to treat as connection errors. When such an error occurs, we want to fail all the currently pending commands so that the user of the library doesn't have to subscribe to more than one stream of errors.


146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/em-imap/connection.rb', line 146

def listen_for_failure
  errback do |error|
    # NOTE: Take a shallow clone of the listeners here so that we get guaranteed
    # behaviour. We want to fail any listeners that may be added by the errbacks
    # of other listeners.
    @listeners.clone.each{ |listener| listener.fail error } while @listeners.size > 0
    close_connection unless @unbound
  end

  # If we receive a BYE response from the server, then we're not going
  # to hear any more, so we fail all our listeners.
  add_response_handler do |response|
    if response.is_a?(Net::IMAP::UntaggedResponse) && response.name == "BYE"
      fail Net::IMAP::ByeResponseError.new((RUBY_VERSION[0,3] == "1.8" ? response.raw_data : response))
    end
  end
end

#listen_for_greetingObject

This listens for the IMAP connection to have been set up. This should be shortly after the TCP connection is available, once we've received a greeting from the server.


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/em-imap/connection.rb', line 39

def listen_for_greeting
  add_to_listener_pool(hello_listener)
  hello_listener.listen do |response|
    # TODO: Is this the right condition? I think it can be one of several
    # possible answers depending on how trusted the connection is, but probably
    # not *anything* except BYE.
    if response.is_a?(Net::IMAP::UntaggedResponse) && response.name != "BYE"
      hello_listener.succeed response
    else
      hello_listener.fail Net::IMAP::ResponseParseError.new((RUBY_VERSION[0,3] == "1.8" ? response.raw_data : response))
    end
  end.errback do |e|
    hello_listener.fail e
  end
end

#listen_for_tagged_response(command) ⇒ Object

Await the response that marks the completion of this command, and succeed or fail the command as appropriate.


120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/em-imap/connection.rb', line 120

def listen_for_tagged_response(command)
  command.listen do |response|
    if response.is_a?(Net::IMAP::TaggedResponse) && response.tag == command.tag
      case response.name
      when "NO"
        command.fail Net::IMAP::NoResponseError.new((RUBY_VERSION[0,3] == "1.8" ? response.data.text : response))
      when "BAD"
        command.fail Net::IMAP::BadResponseError.new((RUBY_VERSION[0,3] == "1.8" ? response.data.text : response))
      else
        command.succeed response
      end
    end
  end
end

#post_initObject


29
30
31
32
33
34
# File 'lib/em-imap/connection.rb', line 29

def post_init
  @listeners = []
  super
  listen_for_failure
  listen_for_greeting
end

#receive_response(response) ⇒ Object

receive_response is a higher-level receive_data provided by EM::IMAP::ResponseParser. Each response is a Net::IMAP response object. (FIXME)


112
113
114
115
116
# File 'lib/em-imap/connection.rb', line 112

def receive_response(response)
  # NOTE: Take a shallow clone of the listeners so that if receiving an
  # event causes a new listener to be added, it won't receive this response!
  @listeners.clone.each{ |listener| listener.receive_event response }
end

#send_command(cmd, *args) ⇒ Object

Send the command, with the given arguments, to the IMAP server.

Exceptions thrown during serialization will be thrown to the user, exceptions thrown while communicating to the socket will cause the returned command to fail.

Parameters:

  • cmd,

    the name of the command to send (a string)

  • *args,

    the arguments for the command, serialized by Net::IMAP. (FIXME)

Returns:

  • Command, a listener and deferrable that will receive_event with the responses from the IMAP server, and which will succeed with a tagged response from the server, or fail with a tagged error response, or an exception.

    NOTE: The responses it overhears may be intended for other commands that are running in parallel.


80
81
82
83
84
85
86
# File 'lib/em-imap/connection.rb', line 80

def send_command(cmd, *args)
  Command.new(next_tag!, cmd, args).tap do |command|
    add_to_listener_pool(command)
    listen_for_tagged_response(command)
    send_command_object(command)
  end
end

#unbindObject

Called when the connection is closed. TODO: Figure out how to send a useful error…


137
138
139
140
# File 'lib/em-imap/connection.rb', line 137

def unbind
  @unbound = true
  fail EOFError.new("Connection to IMAP server was unbound")
end