diff --git a/lib/imap.rb b/lib/imap.rb index a94ef5c..61e8b7c 100644 --- a/lib/imap.rb +++ b/lib/imap.rb @@ -26,7 +26,7 @@ class Command < Struct.new(:tag, :cmd, :args) DG.enhance! self end - class Continuation < Struct.new(:block) + class ContinuationWaiter < Struct.new(:block) include EventMachine::Deferrable DG.enhance! self end diff --git a/lib/imap/authenticators.rb b/lib/imap/authenticators.rb index 7e7981e..a589b59 100644 --- a/lib/imap/authenticators.rb +++ b/lib/imap/authenticators.rb @@ -1,15 +1,16 @@ module EventMachine + # Makes Net::IMAP.add_authenticator accessible through EM::Imap and instances thereof. + # Also provides the authenticator method to EM::Imap::Client to get authenticators + # for use in the authentication exchange. + # module Imap - # Support Net::IMAP compatible authenticators. - module Authenticators - def self.included(klass) - def klass.add_authenticator(*args) - Net::IMAP.add_authenticator(*args) - end - end + def self.add_authenticator(klass) + Net::IMAP.add_authenticator(*args) + end + module Authenticators def add_authenticator(*args) - self.class.add_authenticator(*args) + EventMachine::Imap.add_authenticator(*args) end private diff --git a/lib/imap/client.rb b/lib/imap/client.rb index 43a8429..6b6505c 100644 --- a/lib/imap/client.rb +++ b/lib/imap/client.rb @@ -99,6 +99,32 @@ def status(mailbox, attr) end end + def append(mailbox, message, flags=nil, date_time=nil) + args = [mailbox] + args << flags if flags + args << date_time if date_time + args << Net::IMAP::Literal.new(message) + tagged_response("APPEND" *args) + end + + # 6.4 Client Commands - Selected State + + def check + tagged_response("CHECK") + end + + def close + tagged_response("CLOSE") + end + + def expunge + tagged_response("EXPUNGE") + end + + def search(keys, charset) + + end + private # The callback of a Command returns both a tagged response, diff --git a/lib/imap/command_sender.rb b/lib/imap/command_sender.rb index 25e1b65..44e7480 100644 --- a/lib/imap/command_sender.rb +++ b/lib/imap/command_sender.rb @@ -22,10 +22,6 @@ def send_literal(str) public :send_data end - def post_init - @send_buffer = "" - end - # This is a method that synchronously converts the command into fragments # of string. # @@ -61,8 +57,8 @@ def send_authentication_data(auth_handler, command) def send_string(str, command) when_not_awaiting_continuation do begin - send_fragment str - rescue + send_line_buffered str + rescue => e command.fail e end end @@ -71,7 +67,7 @@ def send_string(str, command) def send_literal(literal, command) when_not_awaiting_continuation do begin - send_fragment "{" + literal.size.to_s + "}" + CRLF + send_line_buffered "{" + literal.size.to_s + "}" + CRLF rescue => e command.fail e end @@ -87,37 +83,22 @@ def send_literal(literal, command) end end - # Each fragment is re-assembled into a full line before we send it to - # the remote server. - # - # This works as all commands end with \r\n, and the encoding of a literal - # sends a \r\n before the body of the literal. - def send_fragment(str) - @send_buffer += str - while eol = @send_buffer.index(CRLF) - to_send = @send_buffer.slice! 0, eol + CRLF.size - send_data to_send + module LineBuffer + def post_init + super + @line_buffer = "" end - end - - # When we're waiting for a continuation response from the server, we must not - # send any more data lest we confuse it mightily. - # - # To this end, this method will hold any pending writes in a queue until the - # continuation response has been received and responded to. - # - # We're using the facts that deferrable callbacks fire in the order that they - # were added to the deferrable and that any continuation response can be dealt - # with synchronously. - def when_not_awaiting_continuation(&block) - if awaiting_continuation? - @awaiting_continuation.bothback{ when_not_awaiting_continuation(&block) } - else - yield + def send_line_buffered(str) + @line_buffer += str + while eol = @line_buffer.index(CRLF) + to_send = @line_buffer.slice! 0, eol + CRLF.size + send_data to_send + end end end - + include Imap::CommandSender::LineBuffer + include Imap::CommandSender::Formatter end end end diff --git a/lib/imap/connection.rb b/lib/imap/connection.rb index bb8bfc1..8ffd4a4 100644 --- a/lib/imap/connection.rb +++ b/lib/imap/connection.rb @@ -17,10 +17,8 @@ def post_init super @tagged_commands = {} @named_responses = {} - @awaiting_continuation = nil end - # TODO: This should block while we're awaiting continuation. def send_command(cmd, *args) Command.new(next_tag!, cmd, args).tap do |command| @@ -35,14 +33,6 @@ def send_command(cmd, *args) fail_all e end - def await_continuations(&block) - if @awaiting_continuation - fail_all RuntimeError.new("Two things tried awaiting...") - else - @awaiting_continuation = Continuation.new(block).bothback{ @awaiting_continuation = nil } - end - end - # See also Net::IMAP#receive_responses def receive_response(response) case response @@ -63,11 +53,8 @@ def receive_response(response) end when Net::IMAP::ContinuationRequest - if awaiting_continuation? - @awaiting_continuation.block.call response - else - fail_all Net::IMAP::ResponseParseError.new(response.raw_data) - end + receive_continuation response + end rescue => e fail_all e @@ -112,10 +99,6 @@ def unbind end end - def awaiting_continuation? - !!@awaiting_continuation - end - # Provides a next_tag! method to generate unique tags # for an Imap session. module TagSequence diff --git a/lib/imap/continuation_synchronisation.rb b/lib/imap/continuation_synchronisation.rb new file mode 100644 index 0000000..f3f8175 --- /dev/null +++ b/lib/imap/continuation_synchronisation.rb @@ -0,0 +1,75 @@ +module EventMachine + module Imap + # The basic IMAP protocol is an unsynchronised exchange of lines, + # however under some circumstances it is necessary to synchronise + # so that the server acknowledges each item sent by the client. + # + # For example, this happens during authentication: + # + # C: A0001 AUTHENTICATE LOGIN + # S: + + # C: USERNAME + # S: + + # C: PASSWORD + # S: A0001 OK authenticated as USERNAME. + # + # And during the sending of literals: + # + # C: A0002 SELECT {8} + # S: + continue + # C: All Mail + # S: A0002 OK + # + # In order to make this work this module allows part of the client + # to block the outbound link while waiting for the continuation + # responses that it is expecting. + # + module ContinuationSynchronisation + + def post_init + super + @awaiting_continuation = nil + end + + # Pass all continuation responses to the block until further notice. + # + # Returns a deferrable which you should succeed or fail when you have + # received all the continuations you need. + def await_continuations(&block) + ContinuationWaiter.new(block).tap do |waiter| + when_not_awaiting_continuation do + @awaiting_continuation = waiter.bothback{ @awaiting_continuation = nil } + end + end + end + + # Pass any continuation response to the block that is expecting it. + def receive_continuation(response) + if awaiting_continuation? + @awaiting_continuation.block.call response + else + fail_all Net::IMAP::ResponseParseError.new(response.raw_data) + end + end + + # Wait until the connection is not waiting for a continuation before + # performing this block. + # + # If possible, the block will be executed immediately; if not it will + # be added to a queue and executed whenever the queue has been emptied. + # + # Any previous items in the queue that wait on the connection will + def when_not_awaiting_continuation(&block) + if awaiting_continuation? + @awaiting_continuation.bothback{ when_not_awaiting_continuation(&block) } + else + yield + end + end + + def awaiting_continuation? + !!@awaiting_continuation + end + end + end +end