Skip to content

Commit 9a89959

Browse files
authored
Merge pull request #4716 from cyrusimap/imap-partial
IMAP PARTIAL and INPROGRESS support
2 parents 2516b97 + 6fec4c2 commit 9a89959

21 files changed

+1203
-328
lines changed

Diff for: cassandane/Cassandane/Cyrus/Fetch.pm

+68
Original file line numberDiff line numberDiff line change
@@ -1066,4 +1066,72 @@ sub test_unknown_cte
10661066
$self->assert_matches(qr{UNKNOWN-CTE}, $imaptalk->get_last_error());
10671067
}
10681068

1069+
sub test_partial
1070+
{
1071+
my ($self) = @_;
1072+
1073+
my $imaptalk = $self->{store}->get_client();
1074+
1075+
xlog $self, "append some messages";
1076+
my %exp;
1077+
my $N = 10;
1078+
for (1..$N)
1079+
{
1080+
my $msg = $self->make_message("Message $_");
1081+
$exp{$_} = $msg;
1082+
}
1083+
xlog $self, "check the messages got there";
1084+
$self->check_messages(\%exp);
1085+
1086+
# expunge the 1st and 6th
1087+
$imaptalk->store('1,6', '+FLAGS', '(\\Deleted)');
1088+
$self->assert_str_equals('ok', $imaptalk->get_last_completion_response());
1089+
$imaptalk->expunge();
1090+
1091+
# fetch all
1092+
my $res = $imaptalk->fetch('1:*', '(UID)');
1093+
$self->assert_str_equals('ok', $imaptalk->get_last_completion_response());
1094+
$self->assert_str_equals($res->{'1'}->{uid}, "2");
1095+
$self->assert_str_equals($res->{'2'}->{uid}, "3");
1096+
$self->assert_str_equals($res->{'3'}->{uid}, "4");
1097+
$self->assert_str_equals($res->{'4'}->{uid}, "5");
1098+
$self->assert_str_equals($res->{'5'}->{uid}, "7");
1099+
$self->assert_str_equals($res->{'6'}->{uid}, "8");
1100+
$self->assert_str_equals($res->{'7'}->{uid}, "9");
1101+
$self->assert_str_equals($res->{'8'}->{uid}, "10");
1102+
1103+
# fetch first 2
1104+
$res = $imaptalk->fetch('1:*', '(UID) (PARTIAL 1:2)');
1105+
$self->assert_str_equals('ok', $imaptalk->get_last_completion_response());
1106+
$self->assert_str_equals($res->{'1'}->{uid}, "2");
1107+
$self->assert_str_equals($res->{'2'}->{uid}, "3");
1108+
1109+
# fetch next 2
1110+
$res = $imaptalk->fetch('1:*', '(UID) (PARTIAL 3:4)');
1111+
$self->assert_str_equals('ok', $imaptalk->get_last_completion_response());
1112+
$self->assert_str_equals($res->{'3'}->{uid}, "4");
1113+
$self->assert_str_equals($res->{'4'}->{uid}, "5");
1114+
1115+
# fetch last 2
1116+
$res = $imaptalk->fetch('1:*', '(UID) (PARTIAL -1:-2)');
1117+
$self->assert_str_equals('ok', $imaptalk->get_last_completion_response());
1118+
$self->assert_str_equals($res->{'8'}->{uid}, "10");
1119+
$self->assert_str_equals($res->{'7'}->{uid}, "9");
1120+
1121+
# fetch the previous 2
1122+
$res = $imaptalk->fetch('1:*', '(UID) (PARTIAL -3:-4)');
1123+
$self->assert_str_equals('ok', $imaptalk->get_last_completion_response());
1124+
$self->assert_str_equals($res->{'6'}->{uid}, "8");
1125+
$self->assert_str_equals($res->{'5'}->{uid}, "7");
1126+
1127+
# enable UID mode...
1128+
$imaptalk->uid(1);
1129+
1130+
# fetch the middle 2 by UID
1131+
$res = $imaptalk->fetch('4:8', '(UID) (PARTIAL 2:3)');
1132+
$self->assert_str_equals('ok', $imaptalk->get_last_completion_response());
1133+
$self->assert_str_equals($res->{'5'}->{uid}, "5");
1134+
$self->assert_str_equals($res->{'7'}->{uid}, "7");
1135+
}
1136+
10691137
1;

Diff for: cassandane/Cassandane/Cyrus/InProgress.pm

+220
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
#!/usr/bin/perl
2+
#
3+
# Copyright (c) 2011-2023 FastMail Pty Ltd. All rights reserved.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions
7+
# are met:
8+
#
9+
# 1. Redistributions of source code must retain the above copyright
10+
# notice, this list of conditions and the following disclaimer.
11+
#
12+
# 2. Redistributions in binary form must reproduce the above copyright
13+
# notice, this list of conditions and the following disclaimer in
14+
# the documentation and/or other materials provided with the
15+
# distribution.
16+
#
17+
# 3. The name "Fastmail Pty Ltd" must not be used to
18+
# endorse or promote products derived from this software without
19+
# prior written permission. For permission or any legal
20+
# details, please contact
21+
# FastMail Pty Ltd
22+
# PO Box 234
23+
# Collins St West 8007
24+
# Victoria
25+
# Australia
26+
#
27+
# 4. Redistributions of any form whatsoever must retain the following
28+
# acknowledgment:
29+
# "This product includes software developed by Fastmail Pty. Ltd."
30+
#
31+
# FASTMAIL PTY LTD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
32+
# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
33+
# EVENT SHALL OPERA SOFTWARE AUSTRALIA BE LIABLE FOR ANY SPECIAL, INDIRECT
34+
# OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF
35+
# USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
36+
# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
37+
# OF THIS SOFTWARE.
38+
#
39+
40+
package Cassandane::Cyrus::InProgress;
41+
use strict;
42+
use warnings;
43+
use DateTime;
44+
use JSON;
45+
use JSON::XS;
46+
use Mail::JMAPTalk 0.13;
47+
use Data::Dumper;
48+
use Storable 'dclone';
49+
use File::Basename;
50+
use IO::File;
51+
use Cwd qw(abs_path getcwd);
52+
53+
use lib '.';
54+
use base qw(Cassandane::Cyrus::TestCase);
55+
use Cassandane::Util::Log;
56+
57+
use charnames ':full';
58+
59+
sub new
60+
{
61+
my $class = shift;
62+
63+
my $config = Cassandane::Config->default()->clone();
64+
$config->set(mailbox_legacy_dirs => 'yes');
65+
$config->set(singleinstancestore => 'no');
66+
$config->set(imap_inprogress_interval => '1s');
67+
68+
return $class->SUPER::new({
69+
adminstore => 1,
70+
config => $config,
71+
services => ['imap'],
72+
}, @_);
73+
}
74+
75+
sub set_up
76+
{
77+
my ($self) = @_;
78+
$self->SUPER::set_up();
79+
}
80+
81+
sub tear_down
82+
{
83+
my ($self) = @_;
84+
$self->SUPER::tear_down();
85+
}
86+
87+
sub test_xrename
88+
:NoAltNameSpace :min_version_3_9
89+
{
90+
my ($self) = @_;
91+
92+
my @resp;
93+
my %handlers =
94+
(
95+
ok => sub
96+
{
97+
my (undef, $ok) = @_;
98+
push(@resp, $ok);
99+
},
100+
);
101+
102+
xlog $self, "Create some personal folders";
103+
my $talk = $self->{store}->get_client();
104+
$self->setup_mailbox_structure($talk, [
105+
[ 'create' => [qw( INBOX.src INBOX.src.child INBOX.src.child.grand)] ],
106+
]);
107+
108+
xlog $self, "rename mailbox tree";
109+
@resp = ();
110+
$talk->_imap_cmd('XRENAME', 0, \%handlers, "INBOX.src", "INBOX.dst");
111+
$self->assert_str_equals('ok', $talk->get_last_completion_response());
112+
$self->assert_str_equals('[INPROGRESS', $resp[0][0]);
113+
# we shouldn't have a count or total
114+
$self->assert_null($resp[0][1][1]);
115+
$self->assert_null($resp[0][1][2]);
116+
$self->assert_str_equals('rename', $resp[0][3]);
117+
$self->assert_str_equals('INBOX.src', $resp[0][4]);
118+
$self->assert_str_equals('INBOX.dst', $resp[0][5]);
119+
$self->assert_str_equals('INBOX.src.child', $resp[1][4]);
120+
$self->assert_str_equals('INBOX.dst.child', $resp[1][5]);
121+
$self->assert_str_equals('INBOX.src.child.grand', $resp[2][4]);
122+
$self->assert_str_equals('INBOX.dst.child.grand', $resp[2][5]);
123+
}
124+
125+
sub test_copy_search_slow
126+
:NoAltNameSpace :min_version_3_9
127+
{
128+
my ($self) = @_;
129+
130+
my @resp;
131+
my %handlers =
132+
(
133+
ok => sub
134+
{
135+
my (undef, $ok) = @_;
136+
push(@resp, $ok);
137+
},
138+
);
139+
140+
xlog "extract messages into INBOX";
141+
$self->{instance}->unpackfile(abs_path('data/cyrus/15k_messages.tar.gz'),
142+
'data/user/cassandane');
143+
$self->{instance}->run_command({ cyrus => 1 },
144+
'reconstruct', 'q', 'user.cassandane');
145+
146+
xlog $self, "Create another folder";
147+
my $talk = $self->{store}->get_client();
148+
$talk->create("INBOX.dst");
149+
$self->assert_str_equals('ok', $talk->get_last_completion_response());
150+
151+
xlog $self, "copy messages";
152+
@resp = ();
153+
$talk->select("INBOX");
154+
$talk->_imap_cmd('COPY', 0, \%handlers, '1:15000', 'INBOX.dst');
155+
$self->assert_str_equals('ok', $talk->get_last_completion_response());
156+
$self->assert_str_equals('[INPROGRESS', $resp[0][0]);
157+
# we don't know what the exact count will be, be we know the total
158+
$self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]);
159+
$self->assert_str_equals('15000', $resp[0][1][2]);
160+
161+
xlog $self, "search messages";
162+
@resp = ();
163+
$talk->_imap_cmd('SEARCH', 0, \%handlers, 'RETURN', '(PARTIAL -1:-500)', '9500:9999', 'BODY', 'needle');
164+
$self->assert_str_equals('ok', $talk->get_last_completion_response());
165+
$self->assert_str_equals('[INPROGRESS', $resp[0][0]);
166+
# we don't know what the exact count will be, be we know the total
167+
$self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]);
168+
$self->assert_str_equals('500', $resp[0][1][2]);
169+
170+
xlog $self, "esearch selected mailbox";
171+
@resp = ();
172+
$talk->_imap_cmd('ESEARCH', 0, \%handlers,
173+
'IN', '(SELECTED)', '9500:9999', 'BODY', 'needle');
174+
$self->assert_str_equals('ok', $talk->get_last_completion_response());
175+
$self->assert_str_equals('[INPROGRESS', $resp[0][0]);
176+
# we don't know what the exact count will be, be we know the total
177+
$self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]);
178+
$self->assert_str_equals('500', $resp[0][1][2]);
179+
180+
xlog $self, "esearch multiple mailboxes";
181+
@resp = ();
182+
$talk->_imap_cmd('ESEARCH', 0, \%handlers,
183+
'IN', '(PERSONAL)', '9500:9999', 'BODY', 'needle');
184+
$self->assert_str_equals('ok', $talk->get_last_completion_response());
185+
$self->assert_str_equals('[INPROGRESS', $resp[0][0]);
186+
# we shouldn't have a count or total
187+
$self->assert_null($resp[0][1][1]);
188+
$self->assert_null($resp[0][1][2]);
189+
190+
xlog $self, "sort messages";
191+
@resp = ();
192+
$talk->_imap_cmd('SORT', 0, \%handlers,
193+
'(ARRIVAL)', 'US-ASCII', '9500:9999', 'BODY', 'needle');
194+
$self->assert_str_equals('ok', $talk->get_last_completion_response());
195+
$self->assert_str_equals('[INPROGRESS', $resp[0][0]);
196+
# we don't know what the exact count will be, be we know the total
197+
$self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]);
198+
$self->assert_str_equals('500', $resp[0][1][2]);
199+
200+
xlog $self, "thread messages";
201+
@resp = ();
202+
$talk->_imap_cmd('THREAD', 0, \%handlers,
203+
'REFERENCES', 'US-ASCII', '9500:9999', 'BODY', 'needle');
204+
$self->assert_str_equals('ok', $talk->get_last_completion_response());
205+
$self->assert_str_equals('[INPROGRESS', $resp[0][0]);
206+
# we don't know what the exact count will be, be we know the total
207+
$self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]);
208+
$self->assert_str_equals('500', $resp[0][1][2]);
209+
210+
xlog $self, "rename INBOX";
211+
@resp = ();
212+
$talk->_imap_cmd('RENAME', 0, \%handlers, 'INBOX', 'INBOX.Archive');
213+
$self->assert_str_equals('ok', $talk->get_last_completion_response());
214+
$self->assert_str_equals('[INPROGRESS', $resp[0][0]);
215+
# we don't know what the exact count will be, be we know the total
216+
$self->assert_matches(qr/^[0-9]+$/, $resp[0][1][1]);
217+
$self->assert_str_equals('15000', $resp[0][1][2]);
218+
}
219+
220+
1;

0 commit comments

Comments
 (0)