Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 16 additions & 3 deletions lib/net/imap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -233,8 +233,9 @@ module Net
# Use paginated or limited versions of commands whenever possible.
#
# Use #add_response_handler to handle responses after each one is received.
# Use #extract_responses, #clear_responses, or #responses (with a block) to
# prune responses.
# Use the +response_handlers+ argument to ::new to assign response handlers
# before the receiver thread is started. Use #extract_responses,
# #clear_responses, or #responses (with a block) to prune responses.
#
# == Errors
#
Expand Down Expand Up @@ -881,6 +882,12 @@ def idle_response_timeout; config.idle_response_timeout end
#
# See DeprecatedClientOptions.new for deprecated SSL arguments.
#
# [response_handlers]
# A list of response handlers to be added before the receiver thread is
# started. This ensures every server response is handled, including the
# #greeting. Note that the greeting is handled in the current thread, but
# all other responses are handled in the receiver thread.
#
# [config]
# A Net::IMAP::Config object to use as the basis for #config. By default,
# the global Net::IMAP.config is used.
Expand Down Expand Up @@ -952,7 +959,7 @@ def idle_response_timeout; config.idle_response_timeout end
# [Net::IMAP::ByeResponseError]
# Connected to the host successfully, but it immediately said goodbye.
#
def initialize(host, port: nil, ssl: nil,
def initialize(host, port: nil, ssl: nil, response_handlers: nil,
config: Config.global, **config_options)
super()
# Config options
Expand All @@ -975,6 +982,7 @@ def initialize(host, port: nil, ssl: nil,
@receiver_thread = nil
@receiver_thread_exception = nil
@receiver_thread_terminating = false
response_handlers&.each do add_response_handler(_1) end

# Client Protocol Sender (including state for currently running commands)
@tag_prefix = "RUBY"
Expand Down Expand Up @@ -2756,6 +2764,10 @@ def response_handlers
# end
# }
#
# Response handlers can also be added when the client is created before the
# receiver thread is started, by the +response_handlers+ argument to ::new.
# This ensures every server response is handled, including the #greeting.
#
# Related: #remove_response_handler, #response_handlers
def add_response_handler(handler = nil, &block)
raise ArgumentError, "two Procs are passed" if handler && block
Expand All @@ -2782,6 +2794,7 @@ def remove_response_handler(handler)
def start_imap_connection
@greeting = get_server_greeting
@capabilities = capabilities_from_resp_code @greeting
@response_handlers.each do |handler| handler.call(@greeting) end
@receiver_thread = start_receiver_thread
rescue Exception
@sock.close
Expand Down
92 changes: 92 additions & 0 deletions test/net/imap/test_imap_response_handlers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

require "net/imap"
require "test/unit"
require_relative "fake_server"

class IMAPResponseHandlersTest < Test::Unit::TestCase
include Net::IMAP::FakeServer::TestHelper

def setup
Net::IMAP.config.reset
@do_not_reverse_lookup = Socket.do_not_reverse_lookup
Socket.do_not_reverse_lookup = true
@threads = []
end

def teardown
if !@threads.empty?
assert_join_threads(@threads)
end
ensure
Socket.do_not_reverse_lookup = @do_not_reverse_lookup
end

test "#add_response_handlers" do
responses = []
with_fake_server do |server, imap|
server.on("NOOP") do |resp|
3.times do resp.untagged("#{_1 + 1} EXPUNGE") end
resp.done_ok
end

assert_equal 0, imap.response_handlers.length
imap.add_response_handler do responses << [:block, _1] end
assert_equal 1, imap.response_handlers.length
imap.add_response_handler(->{ responses << [:proc, _1] })
assert_equal 2, imap.response_handlers.length

imap.noop
responses = responses[0, 6].map {|which, resp|
[which, resp.class, resp.name, resp.data]
}
assert_equal [
[:block, Net::IMAP::UntaggedResponse, "EXPUNGE", 1],
[:proc, Net::IMAP::UntaggedResponse, "EXPUNGE", 1],
[:block, Net::IMAP::UntaggedResponse, "EXPUNGE", 2],
[:proc, Net::IMAP::UntaggedResponse, "EXPUNGE", 2],
[:block, Net::IMAP::UntaggedResponse, "EXPUNGE", 3],
[:proc, Net::IMAP::UntaggedResponse, "EXPUNGE", 3],
], responses
end
end

test "::new with response_handlers kwarg" do
greeting = nil
expunges = []
alerts = []
untagged = 0
handler0 = ->{ greeting ||= _1 }
handler1 = ->(r) { alerts << r.data.text if r.data.code.name == "ALERT" rescue nil }
handler2 = ->(r) { expunges << r.data if r.name == "EXPUNGE" }
handler3 = ->(r) { untagged += 1 if r.is_a?(Net::IMAP::UntaggedResponse) }
response_handlers = [handler0, handler1, handler2, handler3]

run_fake_server_in_thread do |server|
port = server.port
imap = Net::IMAP.new("localhost", port: port,
response_handlers: response_handlers)
assert_equal response_handlers, imap.response_handlers
refute_same response_handlers, imap.response_handlers

# handler0 recieved the greeting and handler3 counted it
assert_equal imap.greeting, greeting
assert_equal 1, untagged

server.on("NOOP") do |resp|
resp.untagged "1 EXPUNGE"
resp.untagged "1 EXPUNGE"
resp.untagged "OK [ALERT] The first alert."
resp.done_ok "[ALERT] Did you see the alert?"
end

imap.noop
assert_equal 4, untagged
assert_equal [1, 1], expunges # from handler2
assert_equal ["The first alert.", "Did you see the alert?"], alerts
ensure
imap&.logout! unless imap&.disconnected?
end
end

end
Loading