Skip to content

Commit e94c14b

Browse files
committed
✨ QRESYNC: Add vanished kwarg to #uid_fetch
1 parent dced9c5 commit e94c14b

File tree

2 files changed

+113
-16
lines changed

2 files changed

+113
-16
lines changed

lib/net/imap.rb

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2636,6 +2636,7 @@ def fetch(...)
26362636

26372637
# :call-seq:
26382638
# uid_fetch(set, attr, changedsince: nil, partial: nil) -> array of FetchData (or UIDFetchData)
2639+
# uid_fetch(set, attr, changedsince:, vanished: true, partial: nil) -> array of VanishedData and FetchData (or UIDFetchData)
26392640
#
26402641
# Sends a {UID FETCH command [IMAP4rev1 §6.4.8]}[https://www.rfc-editor.org/rfc/rfc3501#section-6.4.8]
26412642
# to retrieve data associated with a message in the mailbox.
@@ -2652,6 +2653,23 @@ def fetch(...)
26522653
#
26532654
# +changedsince+ (optional) behaves the same as with #fetch.
26542655
#
2656+
# +vanished+ can be used to request a list all of the message UIDs in +set+
2657+
# that have been expunged since +changedsince+. Setting +vanished+ to true
2658+
# prepends a VanishedData object to the returned array. If the server does
2659+
# not return a +VANISHED+ response, an empty VanishedData object will still
2660+
# be added.
2661+
# <em>The +QRESYNC+ capabability must be enabled.</em>
2662+
# {[RFC7162]}[https://rfc-editor.org/rfc/rfc7162]
2663+
#
2664+
# For example:
2665+
#
2666+
# # must enable "QRESYNC" before selecting the mailbox
2667+
# imap.enable("QRESYNC")
2668+
# imap.select("INBOX")
2669+
# # first value in the array is VanishedData
2670+
# vanished, *fetched = imap.uid_fetch(301..500, %w[flags],
2671+
# changedsince: 12345, vanished: true)
2672+
#
26552673
# +partial+ is an optional range to limit the number of results returned.
26562674
# It's useful when +set+ contains an unknown number of messages.
26572675
# <tt>1..500</tt> returns the first 500 messages in +set+ (in mailbox
@@ -2683,6 +2701,9 @@ def fetch(...)
26832701
#
26842702
# ==== Capabilities
26852703
#
2704+
# QRESYNC[https://www.rfc-editor.org/rfc/rfc7162] must be enabled in order
2705+
# to use the +vanished+ fetch modifier.
2706+
#
26862707
# The server's capabilities must include +PARTIAL+
26872708
# {[RFC9394]}[https://rfc-editor.org/rfc/rfc9394] in order to use the
26882709
# +partial+ argument.
@@ -2962,9 +2983,8 @@ def uid_thread(algorithm, search_keys, charset)
29622983
# See {[RFC7162 §3.1]}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.1].
29632984
#
29642985
# [+QRESYNC+ {[RFC7162]}[https://www.rfc-editor.org/rfc/rfc7162.html]]
2965-
# *NOTE:* Enabling QRESYNC will replace +EXPUNGE+ with +VANISHED+, but
2966-
# the extension arguments to #select, #examine, and #uid_fetch are not
2967-
# supported yet.
2986+
# *NOTE:* The +QRESYNC+ argument to #select and #examine is not supported
2987+
# yet.
29682988
#
29692989
# Adds quick resynchronization options to #select, #examine, and
29702990
# #uid_fetch. +QRESYNC+ _must_ be explicitly enabled before using any of
@@ -3683,16 +3703,21 @@ def search_internal(cmd, ...)
36833703
end
36843704
end
36853705

3686-
def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
3687-
set = SequenceSet[set]
3688-
if partial
3689-
mod ||= []
3690-
mod << "PARTIAL" << PartialRange[partial]
3691-
end
3692-
if changedsince
3693-
mod ||= []
3694-
mod << "CHANGEDSINCE" << Integer(changedsince)
3706+
def fetch_internal(cmd, set, attr, mod = nil,
3707+
partial: nil,
3708+
changedsince: nil,
3709+
vanished: false)
3710+
if vanished
3711+
if !cmd.start_with?("UID ")
3712+
raise ArgumentError, "vanished can only be used with uid_fetch"
3713+
elsif !changedsince
3714+
raise ArgumentError, "vanished must be used with changedsince"
3715+
end
36953716
end
3717+
set = SequenceSet[set]
3718+
(mod ||= []) << "PARTIAL" << PartialRange[partial] if partial
3719+
(mod ||= []) << "CHANGEDSINCE" << Integer(changedsince) if changedsince
3720+
(mod ||= []) << "VANISHED" if vanished
36963721
case attr
36973722
when String then
36983723
attr = RawData.new(attr)
@@ -3704,7 +3729,7 @@ def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
37043729

37053730
args = [cmd, set, attr]
37063731
args << mod if mod
3707-
send_command_returning_fetch_results(*args)
3732+
send_command_returning_fetch_results(*args, vanished:)
37083733
end
37093734

37103735
def store_internal(cmd, set, attr, flags, unchangedsince: nil)
@@ -3715,14 +3740,20 @@ def store_internal(cmd, set, attr, flags, unchangedsince: nil)
37153740
send_command_returning_fetch_results(cmd, *args)
37163741
end
37173742

3718-
def send_command_returning_fetch_results(...)
3743+
def send_command_returning_fetch_results(*args, vanished: false)
37193744
synchronize do
37203745
clear_responses("FETCH")
37213746
clear_responses("UIDFETCH")
3722-
send_command(...)
3747+
send_command(*args)
37233748
fetches = clear_responses("FETCH")
37243749
uidfetches = clear_responses("UIDFETCH")
3725-
uidfetches.any? ? uidfetches : fetches
3750+
fetches = uidfetches if uidfetches.any?
3751+
if vanished
3752+
vanished = extract_responses("VANISHED", &:earlier?).last ||
3753+
VanishedData[uids: SequenceSet.empty, earlier: true]
3754+
fetches = [vanished, *fetches].freeze
3755+
end
3756+
fetches
37263757
end
37273758
end
37283759

test/net/imap/test_imap_fetch.rb

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,4 +99,70 @@ class IMAPFetchTest < Net::IMAP::TestCase
9999
end
100100
end
101101

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

0 commit comments

Comments
 (0)