Skip to content

Commit 909d447

Browse files
committed
✨ QRESYNC: Add vanished kwarg to #uid_fetch
1 parent 87739c8 commit 909d447

File tree

2 files changed

+93
-16
lines changed

2 files changed

+93
-16
lines changed

lib/net/imap.rb

Lines changed: 39 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,14 @@ def search_internal(cmd, ...)
36833703
end
36843704
end
36853705

3686-
def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
3706+
def fetch_internal(cmd, set, attr, mod = nil,
3707+
partial: nil,
3708+
changedsince: nil,
3709+
vanished: false)
36873710
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)
3695-
end
3711+
(mod ||= []) << "PARTIAL" << PartialRange[partial] if partial
3712+
(mod ||= []) << "CHANGEDSINCE" << Integer(changedsince) if changedsince
3713+
(mod ||= []) << "VANISHED" if vanished
36963714
case attr
36973715
when String then
36983716
attr = RawData.new(attr)
@@ -3704,7 +3722,7 @@ def fetch_internal(cmd, set, attr, mod = nil, partial: nil, changedsince: nil)
37043722

37053723
args = [cmd, set, attr]
37063724
args << mod if mod
3707-
send_command_returning_fetch_results(*args)
3725+
send_command_returning_fetch_results(*args, vanished:)
37083726
end
37093727

37103728
def store_internal(cmd, set, attr, flags, unchangedsince: nil)
@@ -3715,14 +3733,19 @@ def store_internal(cmd, set, attr, flags, unchangedsince: nil)
37153733
send_command_returning_fetch_results(cmd, *args)
37163734
end
37173735

3718-
def send_command_returning_fetch_results(...)
3736+
def send_command_returning_fetch_results(*args, vanished: false)
37193737
synchronize do
37203738
clear_responses("FETCH")
37213739
clear_responses("UIDFETCH")
3722-
send_command(...)
3740+
send_command(*args)
37233741
fetches = clear_responses("FETCH")
37243742
uidfetches = clear_responses("UIDFETCH")
3725-
uidfetches.any? ? uidfetches : fetches
3743+
fetches = uidfetches if uidfetches.any?
3744+
if vanished
3745+
vanished = responses("VANISHED", &:pop) || VanishedData[nil, true]
3746+
fetches = [vanished, *fetches].freeze
3747+
end
3748+
fetches
37263749
end
37273750
end
37283751

test/net/imap/test_imap_fetch.rb

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

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

0 commit comments

Comments
 (0)