Skip to content

Commit 9dce78a

Browse files
authored
🔀 Merge pull request #427 from ruby/backport/v0.4-GH419-response_handlers-kwarg
✨ Add `response_handlers` kwarg to `Net::IMAP.new` (backports #419)
2 parents 9afa7e7 + 3095b84 commit 9dce78a

File tree

2 files changed

+108
-3
lines changed

2 files changed

+108
-3
lines changed

‎lib/net/imap.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -233,8 +233,9 @@ module Net
233233
# Use paginated or limited versions of commands whenever possible.
234234
#
235235
# Use #add_response_handler to handle responses after each one is received.
236-
# Use #extract_responses, #clear_responses, or #responses (with a block) to
237-
# prune responses.
236+
# Use the +response_handlers+ argument to ::new to assign response handlers
237+
# before the receiver thread is started. Use #extract_responses,
238+
# #clear_responses, or #responses (with a block) to prune responses.
238239
#
239240
# == Errors
240241
#
@@ -881,6 +882,12 @@ def idle_response_timeout; config.idle_response_timeout end
881882
#
882883
# See DeprecatedClientOptions.new for deprecated SSL arguments.
883884
#
885+
# [response_handlers]
886+
# A list of response handlers to be added before the receiver thread is
887+
# started. This ensures every server response is handled, including the
888+
# #greeting. Note that the greeting is handled in the current thread, but
889+
# all other responses are handled in the receiver thread.
890+
#
884891
# [config]
885892
# A Net::IMAP::Config object to use as the basis for #config. By default,
886893
# the global Net::IMAP.config is used.
@@ -952,7 +959,7 @@ def idle_response_timeout; config.idle_response_timeout end
952959
# [Net::IMAP::ByeResponseError]
953960
# Connected to the host successfully, but it immediately said goodbye.
954961
#
955-
def initialize(host, port: nil, ssl: nil,
962+
def initialize(host, port: nil, ssl: nil, response_handlers: nil,
956963
config: Config.global, **config_options)
957964
super()
958965
# Config options
@@ -975,6 +982,7 @@ def initialize(host, port: nil, ssl: nil,
975982
@receiver_thread = nil
976983
@receiver_thread_exception = nil
977984
@receiver_thread_terminating = false
985+
response_handlers&.each do add_response_handler(_1) end
978986

979987
# Client Protocol Sender (including state for currently running commands)
980988
@tag_prefix = "RUBY"
@@ -2756,6 +2764,10 @@ def response_handlers
27562764
# end
27572765
# }
27582766
#
2767+
# Response handlers can also be added when the client is created before the
2768+
# receiver thread is started, by the +response_handlers+ argument to ::new.
2769+
# This ensures every server response is handled, including the #greeting.
2770+
#
27592771
# Related: #remove_response_handler, #response_handlers
27602772
def add_response_handler(handler = nil, &block)
27612773
raise ArgumentError, "two Procs are passed" if handler && block
@@ -2782,6 +2794,7 @@ def remove_response_handler(handler)
27822794
def start_imap_connection
27832795
@greeting = get_server_greeting
27842796
@capabilities = capabilities_from_resp_code @greeting
2797+
@response_handlers.each do |handler| handler.call(@greeting) end
27852798
@receiver_thread = start_receiver_thread
27862799
rescue Exception
27872800
@sock.close
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# frozen_string_literal: true
2+
3+
require "net/imap"
4+
require "test/unit"
5+
require_relative "fake_server"
6+
7+
class IMAPResponseHandlersTest < Test::Unit::TestCase
8+
include Net::IMAP::FakeServer::TestHelper
9+
10+
def setup
11+
Net::IMAP.config.reset
12+
@do_not_reverse_lookup = Socket.do_not_reverse_lookup
13+
Socket.do_not_reverse_lookup = true
14+
@threads = []
15+
end
16+
17+
def teardown
18+
if !@threads.empty?
19+
assert_join_threads(@threads)
20+
end
21+
ensure
22+
Socket.do_not_reverse_lookup = @do_not_reverse_lookup
23+
end
24+
25+
test "#add_response_handlers" do
26+
responses = []
27+
with_fake_server do |server, imap|
28+
server.on("NOOP") do |resp|
29+
3.times do resp.untagged("#{_1 + 1} EXPUNGE") end
30+
resp.done_ok
31+
end
32+
33+
assert_equal 0, imap.response_handlers.length
34+
imap.add_response_handler do responses << [:block, _1] end
35+
assert_equal 1, imap.response_handlers.length
36+
imap.add_response_handler(->{ responses << [:proc, _1] })
37+
assert_equal 2, imap.response_handlers.length
38+
39+
imap.noop
40+
responses = responses[0, 6].map {|which, resp|
41+
[which, resp.class, resp.name, resp.data]
42+
}
43+
assert_equal [
44+
[:block, Net::IMAP::UntaggedResponse, "EXPUNGE", 1],
45+
[:proc, Net::IMAP::UntaggedResponse, "EXPUNGE", 1],
46+
[:block, Net::IMAP::UntaggedResponse, "EXPUNGE", 2],
47+
[:proc, Net::IMAP::UntaggedResponse, "EXPUNGE", 2],
48+
[:block, Net::IMAP::UntaggedResponse, "EXPUNGE", 3],
49+
[:proc, Net::IMAP::UntaggedResponse, "EXPUNGE", 3],
50+
], responses
51+
end
52+
end
53+
54+
test "::new with response_handlers kwarg" do
55+
greeting = nil
56+
expunges = []
57+
alerts = []
58+
untagged = 0
59+
handler0 = ->{ greeting ||= _1 }
60+
handler1 = ->(r) { alerts << r.data.text if r.data.code.name == "ALERT" rescue nil }
61+
handler2 = ->(r) { expunges << r.data if r.name == "EXPUNGE" }
62+
handler3 = ->(r) { untagged += 1 if r.is_a?(Net::IMAP::UntaggedResponse) }
63+
response_handlers = [handler0, handler1, handler2, handler3]
64+
65+
run_fake_server_in_thread do |server|
66+
port = server.port
67+
imap = Net::IMAP.new("localhost", port: port,
68+
response_handlers: response_handlers)
69+
assert_equal response_handlers, imap.response_handlers
70+
refute_same response_handlers, imap.response_handlers
71+
72+
# handler0 recieved the greeting and handler3 counted it
73+
assert_equal imap.greeting, greeting
74+
assert_equal 1, untagged
75+
76+
server.on("NOOP") do |resp|
77+
resp.untagged "1 EXPUNGE"
78+
resp.untagged "1 EXPUNGE"
79+
resp.untagged "OK [ALERT] The first alert."
80+
resp.done_ok "[ALERT] Did you see the alert?"
81+
end
82+
83+
imap.noop
84+
assert_equal 4, untagged
85+
assert_equal [1, 1], expunges # from handler2
86+
assert_equal ["The first alert.", "Did you see the alert?"], alerts
87+
ensure
88+
imap&.logout! unless imap&.disconnected?
89+
end
90+
end
91+
92+
end

0 commit comments

Comments
 (0)