Skip to content

Commit 7e833f3

Browse files
committed
🔀 Merge branch 'qresync-uid_fetch-vanished'
2 parents 38cfa66 + 129fbda commit 7e833f3

File tree

2 files changed

+105
-15
lines changed

2 files changed

+105
-15
lines changed

lib/net/imap.rb

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2647,6 +2647,7 @@ def fetch(...)
26472647

26482648
# :call-seq:
26492649
# uid_fetch(set, attr, changedsince: nil, partial: nil) -> array of FetchData (or UIDFetchData)
2650+
# uid_fetch(set, attr, changedsince:, vanished: true, partial: nil) -> array of VanishedData and FetchData (or UIDFetchData)
26502651
#
26512652
# Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
26522653
# to retrieve data associated with a message in the mailbox.
@@ -2663,6 +2664,22 @@ def fetch(...)
26632664
#
26642665
# +changedsince+ (optional) behaves the same as with #fetch.
26652666
#
2667+
# +vanished+ can be used to request a list all of the message UIDs in +set+
2668+
# that have been expunged since +changedsince+. Setting +vanished+ to true
2669+
# prepends a VanishedData object to the returned array. If the server does
2670+
# not return a +VANISHED+ response, an empty VanishedData object will still
2671+
# be added.
2672+
# <em>The +QRESYNC+ capabability must be enabled.</em>
2673+
# {[RFC7162]}[https://rfc-editor.org/rfc/rfc7162]
2674+
#
2675+
# For example:
2676+
#
2677+
# imap.enable("QRESYNC") # must enable before selecting the mailbox
2678+
# imap.select("INBOX")
2679+
# # first value in the array is VanishedData
2680+
# vanished, *fetched = imap.uid_fetch(301..500, %w[flags],
2681+
# changedsince: 12345, vanished: true)
2682+
#
26662683
# +partial+ is an optional range to limit the number of results returned.
26672684
# It's useful when +set+ contains an unknown number of messages.
26682685
# <tt>1..500</tt> returns the first 500 messages in +set+ (in mailbox
@@ -2695,6 +2712,9 @@ def fetch(...)
26952712
#
26962713
# ==== Capabilities
26972714
#
2715+
# QRESYNC[https://www.rfc-editor.org/rfc/rfc7162] must be enabled in order
2716+
# to use the +vanished+ fetch modifier.
2717+
#
26982718
# The server's capabilities must include +PARTIAL+
26992719
# {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394] in order to use the
27002720
# +partial+ argument.
@@ -2974,7 +2994,6 @@ def uid_thread(algorithm, search_keys, charset)
29742994
# See {[RFC7162 §3.1]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1].
29752995
#
29762996
# [+QRESYNC+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]]
2977-
# *NOTE:* The +QRESYNC+ argument to #uid_fetch is not supported yet.
29782997
#
29792998
# Adds quick resynchronization options to #select, #examine, and
29802999
# #uid_fetch. +QRESYNC+ _must_ be explicitly enabled before using any of
@@ -3707,19 +3726,23 @@ def search_internal(cmd, ...)
37073726
end
37083727
end
37093728

3710-
def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
3711-
if partial && !cmd.start_with?("UID ")
3729+
def fetch_internal(cmd, set, attr, mod = nil,
3730+
partial: nil,
3731+
changedsince: nil,
3732+
vanished: false)
3733+
if cmd.start_with?("UID ")
3734+
if vanished && !changedsince
3735+
raise ArgumentError, "vanished must be used with changedsince"
3736+
end
3737+
elsif vanished
3738+
raise ArgumentError, "vanished can only be used with uid_fetch"
3739+
elsif partial
37123740
raise ArgumentError, "partial can only be used with uid_fetch"
37133741
end
37143742
set = SequenceSet[set]
3715-
if partial
3716-
mod ||= []
3717-
mod << "PARTIAL" << PartialRange[partial]
3718-
end
3719-
if changedsince
3720-
mod ||= []
3721-
mod << "CHANGEDSINCE" << Integer(changedsince)
3722-
end
3743+
(mod ||= []) << "PARTIAL" << PartialRange[partial] if partial
3744+
(mod ||= []) << "CHANGEDSINCE" << Integer(changedsince) if changedsince
3745+
(mod ||= []) << "VANISHED" if vanished
37233746
case attr
37243747
when String then
37253748
attr = RawData.new(attr)
@@ -3731,7 +3754,7 @@ def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
37313754

37323755
args = [cmd, set, attr]
37333756
args << mod if mod
3734-
send_command_returning_fetch_results(*args)
3757+
send_command_returning_fetch_results(*args, vanished:)
37353758
end
37363759

37373760
def store_internal(cmd, set, attr, flags, unchangedsince: nil)
@@ -3742,14 +3765,20 @@ def store_internal(cmd, set, attr, flags, unchangedsince: nil)
37423765
send_command_returning_fetch_results(cmd, *args)
37433766
end
37443767

3745-
def send_command_returning_fetch_results(...)
3768+
def send_command_returning_fetch_results(*args, vanished: false)
37463769
synchronize do
37473770
clear_responses("FETCH")
37483771
clear_responses("UIDFETCH")
3749-
send_command(...)
3772+
send_command(*args)
37503773
fetches = clear_responses("FETCH")
37513774
uidfetches = clear_responses("UIDFETCH")
3752-
uidfetches.any? ? uidfetches : fetches
3775+
fetches = uidfetches if uidfetches.any?
3776+
if vanished
3777+
vanished = extract_responses("VANISHED", &:earlier?).last ||
3778+
VanishedData[uids: SequenceSet.empty, earlier: true]
3779+
fetches = [vanished, *fetches].freeze
3780+
end
3781+
fetches
37533782
end
37543783
end
37553784

test/net/imap/test_imap_fetch.rb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ class IMAPFetchTest < Net::IMAP::TestCase
1212
assert_raise_with_message(ArgumentError, /\Apartial.*uid_fetch/) do
1313
imap.fetch(1, "FAST", partial: 1..10)
1414
end
15+
assert_raise_with_message(ArgumentError, /\Avanished.*uid_fetch/) do
16+
imap.fetch(1, "FAST", changedsince: 1234, vanished: true)
17+
end
18+
assert_raise_with_message(ArgumentError, /\Avanished.*changedsince/) do
19+
imap.uid_fetch(1, "FAST", vanished: true)
20+
end
1521
end
1622
end
1723

@@ -107,4 +113,59 @@ class IMAPFetchTest < Net::IMAP::TestCase
107113
end
108114
end
109115

116+
test "#uid_fetch with changedsince and vanished" do
117+
with_fake_server select: "inbox" do |server, imap|
118+
server.on("UID FETCH") do |resp|
119+
resp.untagged "VANISHED (EARLIER) 300:310,405,411"
120+
resp.untagged "1 FETCH (UID 404 MODSEQ (65402) FLAGS (\\Seen))"
121+
resp.untagged "2 FETCH (UID 406 MODSEQ (75403) FLAGS (\\Deleted))"
122+
resp.untagged "4 FETCH (UID 408 MODSEQ (29738) " \
123+
"FLAGS ($NoJunk $AutoJunk $MDNSent))"
124+
resp.done_ok
125+
end
126+
# vanished: true changes the output to begin with VanishedData
127+
vanished, *fetched = imap.uid_fetch(300..500, %w[FLAGS],
128+
changedsince: 12345, vanished: true)
129+
assert_equal(
130+
"RUBY0002 UID FETCH 300:500 (FLAGS) (CHANGEDSINCE 12345 VANISHED)",
131+
server.commands.pop.raw.strip
132+
)
133+
assert_equal Net::IMAP::VanishedData["300:310,405,411", true], vanished
134+
expected = [
135+
[1, 404, 65402, %i[Seen]],
136+
[2, 406, 75403, %i[Deleted]],
137+
[4, 408, 29738, %w[$NoJunk $AutoJunk $MDNSent]],
138+
]
139+
assert_equal expected.size, fetched.size
140+
fetched.zip(expected).each do |fetch, (seqno, uid, modseq, flags)|
141+
assert_instance_of Net::IMAP::FetchData, fetch
142+
assert_equal seqno, fetch.seqno
143+
assert_equal uid, fetch.uid
144+
assert_equal modseq, fetch.modseq
145+
assert_equal flags, fetch.flags
146+
end
147+
148+
# without VANISHED
149+
server.on("UID FETCH") do |resp|
150+
resp.untagged "1 FETCH (UID 404 MODSEQ (65402) FLAGS (\\Seen))"
151+
resp.untagged "2 FETCH (UID 406 MODSEQ (75403) FLAGS (\\Deleted))"
152+
resp.untagged "4 FETCH (UID 408 MODSEQ (29738) " \
153+
"FLAGS ($NoJunk $AutoJunk $MDNSent))"
154+
resp.done_ok
155+
end
156+
vanished, *fetched = imap.uid_fetch(300..500, %w[FLAGS],
157+
changedsince: 12345, vanished: true)
158+
assert_equal(Net::IMAP::VanishedData[Net::IMAP::SequenceSet.empty, true],
159+
vanished)
160+
assert_equal expected.size, fetched.size
161+
fetched.zip(expected).each do |fetch, (seqno, uid, modseq, flags)|
162+
assert_instance_of Net::IMAP::FetchData, fetch
163+
assert_equal seqno, fetch.seqno
164+
assert_equal uid, fetch.uid
165+
assert_equal modseq, fetch.modseq
166+
assert_equal flags, fetch.flags
167+
end
168+
end
169+
end
170+
110171
end

0 commit comments

Comments
 (0)