Skip to content
This repository has been archived by the owner on Dec 5, 2023. It is now read-only.

Commit

Permalink
Split out continuation synchronization
Browse files Browse the repository at this point in the history
  • Loading branch information
ConradIrwin committed Apr 16, 2011
1 parent 2c2a8b5 commit 440c794
Show file tree
Hide file tree
Showing 6 changed files with 128 additions and 62 deletions.
2 changes: 1 addition & 1 deletion lib/imap.rb
Expand Up @@ -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
Expand Down
17 changes: 9 additions & 8 deletions 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
Expand Down
26 changes: 26 additions & 0 deletions lib/imap/client.rb
Expand Up @@ -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,
Expand Down
49 changes: 15 additions & 34 deletions lib/imap/command_sender.rb
Expand Up @@ -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.
#
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
21 changes: 2 additions & 19 deletions lib/imap/connection.rb
Expand Up @@ -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|

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down
75 changes: 75 additions & 0 deletions 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

0 comments on commit 440c794

Please sign in to comment.