Skip to content

Commit 2813b4c

Browse files
authored
🔀 Merge pull request #330 from nevans/extract_responses
✨ Add `#extract_responses` method
2 parents b78b5da + 2a7be9e commit 2813b4c

File tree

4 files changed

+89
-4
lines changed

4 files changed

+89
-4
lines changed

‎lib/net/imap.rb

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ module Net
288288
# pre-authenticated connection.
289289
# - #responses: Yields unhandled UntaggedResponse#data and <em>non-+nil+</em>
290290
# ResponseCode#data.
291+
# - #extract_responses: Removes and returns the responses for which the block
292+
# returns a true value.
291293
# - #clear_responses: Deletes unhandled data from #responses and returns it.
292294
# - #add_response_handler: Add a block to be called inside the receiver thread
293295
# with every server response.
@@ -2534,7 +2536,7 @@ def idle_done
25342536
# return the TaggedResponse directly, #add_response_handler must be used to
25352537
# handle all response codes.
25362538
#
2537-
# Related: #clear_responses, #response_handlers, #greeting
2539+
# Related: #extract_responses, #clear_responses, #response_handlers, #greeting
25382540
def responses(type = nil)
25392541
if block_given?
25402542
synchronize { yield(type ? @responses[type.to_s.upcase] : @responses) }
@@ -2562,7 +2564,7 @@ def responses(type = nil)
25622564
# Clearing responses is synchronized with other threads. The lock is
25632565
# released before returning.
25642566
#
2565-
# Related: #responses, #response_handlers
2567+
# Related: #extract_responses, #responses, #response_handlers
25662568
def clear_responses(type = nil)
25672569
synchronize {
25682570
if type
@@ -2576,6 +2578,30 @@ def clear_responses(type = nil)
25762578
.freeze
25772579
end
25782580

2581+
# :call-seq:
2582+
# extract_responses(type) {|response| ... } -> array
2583+
#
2584+
# Yields all of the unhandled #responses for a single response +type+.
2585+
# Removes and returns the responses for which the block returns a true
2586+
# value.
2587+
#
2588+
# Extracting responses is synchronized with other threads. The lock is
2589+
# released before returning.
2590+
#
2591+
# Related: #responses, #clear_responses
2592+
def extract_responses(type)
2593+
type = String.try_convert(type) or
2594+
raise ArgumentError, "type must be a string"
2595+
raise ArgumentError, "must provide a block" unless block_given?
2596+
extracted = []
2597+
responses(type) do |all|
2598+
all.reject! do |response|
2599+
extracted << response if yield response
2600+
end
2601+
end
2602+
extracted
2603+
end
2604+
25792605
# Returns all response handlers, including those that are added internally
25802606
# by commands. Each response handler will be called with every new
25812607
# UntaggedResponse, TaggedResponse, and ContinuationRequest.

‎test/net/imap/fake_server.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ def state; connection.state end
103103
# See CommandRouter#on
104104
def on(...) connection&.on(...) end
105105

106+
# See Connection#unsolicited
107+
def unsolicited(...) @mutex.synchronize { connection&.unsolicited(...) } end
108+
106109
private
107110

108111
attr_reader :tcp_server, :connection

‎test/net/imap/fake_server/connection.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def initialize(server, tcp_socket:)
1919

2020
def commands; state.commands end
2121
def on(...) router.on(...) end
22+
def unsolicited(...) writer.untagged(...) end
2223

2324
def run
2425
writer.greeting

‎test/net/imap/test_imap.rb

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1110,7 +1110,7 @@ def test_enable
11101110
end
11111111
end
11121112

1113-
def test_responses
1113+
test "#responses" do
11141114
with_fake_server do |server, imap|
11151115
# responses available before SELECT/EXAMINE
11161116
assert_equal(%w[IMAP4REV1 NAMESPACE MOVE IDLE UTF8=ACCEPT],
@@ -1144,7 +1144,7 @@ def test_responses
11441144
end
11451145
end
11461146

1147-
def test_clear_responses
1147+
test "#clear_responses" do
11481148
with_fake_server do |server, imap|
11491149
resp = imap.select "INBOX"
11501150
assert_equal([Net::IMAP::TaggedResponse, "RUBY0001", "OK"],
@@ -1168,6 +1168,49 @@ def test_clear_responses
11681168
end
11691169
end
11701170

1171+
test "#extract_responses" do
1172+
with_fake_server do |server, imap|
1173+
resp = imap.select "INBOX"
1174+
assert_equal([Net::IMAP::TaggedResponse, "RUBY0001", "OK"],
1175+
[resp.class, resp.tag, resp.name])
1176+
# Need to send a string type and a block
1177+
assert_raise(ArgumentError) do imap.extract_responses { true } end
1178+
assert_raise(ArgumentError) do imap.extract_responses(nil) { true } end
1179+
assert_raise(ArgumentError) do imap.extract_responses("OK") end
1180+
# matching nothing
1181+
assert_equal([172], imap.responses("EXISTS", &:dup))
1182+
assert_equal([], imap.extract_responses("EXISTS") { String === _1 })
1183+
assert_equal([172], imap.responses("EXISTS", &:dup))
1184+
# matching everything
1185+
assert_equal([172], imap.responses("EXISTS", &:dup))
1186+
assert_equal([172], imap.extract_responses("EXISTS", &:even?))
1187+
assert_equal([], imap.responses("EXISTS", &:dup))
1188+
# matching some
1189+
server.unsolicited("101 FETCH (UID 1111 FLAGS (\\Seen))")
1190+
server.unsolicited("102 FETCH (UID 2222 FLAGS (\\Seen \\Flagged))")
1191+
server.unsolicited("103 FETCH (UID 3333 FLAGS (\\Deleted))")
1192+
wait_for_response_count(imap, type: "FETCH", count: 3)
1193+
1194+
result = imap.extract_responses("FETCH") { _1.flags.include?(:Flagged) }
1195+
assert_equal(
1196+
[
1197+
Net::IMAP::FetchData.new(
1198+
102, {"UID" => 2222, "FLAGS" => [:Seen, :Flagged]}
1199+
),
1200+
],
1201+
result,
1202+
)
1203+
assert_equal 2, imap.responses("FETCH", &:count)
1204+
1205+
result = imap.extract_responses("FETCH") { _1.flags.include?(:Deleted) }
1206+
assert_equal(
1207+
[Net::IMAP::FetchData.new(103, {"UID" => 3333, "FLAGS" => [:Deleted]})],
1208+
result
1209+
)
1210+
assert_equal 1, imap.responses("FETCH", &:count)
1211+
end
1212+
end
1213+
11711214
test "#select with condstore" do
11721215
with_fake_server do |server, imap|
11731216
imap.select "inbox", condstore: true
@@ -1423,4 +1466,16 @@ def create_tcp_server
14231466
def server_addr
14241467
Addrinfo.tcp("localhost", 0).ip_address
14251468
end
1469+
1470+
def wait_for_response_count(imap, type:, count:,
1471+
timeout: 0.5, interval: 0.001)
1472+
deadline = Time.now + timeout
1473+
loop do
1474+
current_count = imap.responses(type, &:size)
1475+
break :count if count <= current_count
1476+
break :deadline if deadline < Time.now
1477+
sleep interval
1478+
end
1479+
end
1480+
14261481
end

0 commit comments

Comments
 (0)