Skip to content

Commit a05a047

Browse files
committed
JMAP/changes: Return the correct container/item change id when there are no changes
1 parent 3c0312d commit a05a047

File tree

3 files changed

+60
-27
lines changed

3 files changed

+60
-27
lines changed

crates/jmap/src/changes/get.rs

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
55
*/
66

7-
use crate::api::auth::JmapAuthorization;
7+
use crate::{api::auth::JmapAuthorization, changes::state::JmapCacheState};
88
use common::{Server, auth::AccessToken};
9+
use email::cache::MessageCacheFetch;
10+
use groupware::cache::GroupwareCache;
911
use jmap_proto::{
1012
method::changes::{ChangesRequest, ChangesResponse},
1113
object::{JmapObject, NullObject, mailbox::MailboxProperty},
@@ -15,6 +17,7 @@ use jmap_proto::{
1517
};
1618
use std::future::Future;
1719
use store::query::log::{Change, Query};
20+
use trc::AddContext;
1821
use types::collection::{Collection, SyncCollection};
1922

2023
pub trait ChangesLookup: Sync + Send {
@@ -139,12 +142,41 @@ impl ChangesLookup for Server {
139142

140143
(0, changelog)
141144
}
142-
State::Exact(change_id) => (
143-
0,
144-
self.store()
145-
.changes(account_id, collection.into(), Query::Since(*change_id))
146-
.await?,
147-
),
145+
State::Exact(change_id) => {
146+
let last_state = match collection {
147+
SyncCollection::Calendar | SyncCollection::AddressBook => self
148+
.fetch_dav_resources(access_token, account_id, collection)
149+
.await
150+
.caused_by(trc::location!())?
151+
.get_state(is_container)
152+
.into(),
153+
SyncCollection::Email => self
154+
.get_cached_messages(account_id)
155+
.await?
156+
.get_state(is_container)
157+
.into(),
158+
_ => None,
159+
};
160+
161+
if let Some(last_state) = last_state {
162+
response.new_state = last_state;
163+
164+
if response.new_state == State::Exact(*change_id) {
165+
return Ok(IntermediateChangesResponse {
166+
response,
167+
object,
168+
only_container_changes: false,
169+
});
170+
}
171+
}
172+
173+
(
174+
0,
175+
self.store()
176+
.changes(account_id, collection.into(), Query::Since(*change_id))
177+
.await?,
178+
)
179+
}
148180
State::Intermediate(intermediate_state) => {
149181
let changelog = self
150182
.store()
@@ -175,10 +207,16 @@ impl ChangesLookup for Server {
175207
}
176208
};
177209

178-
if changelog.is_truncated && request.since_state != State::Initial {
179-
return Err(trc::JmapEvent::CannotCalculateChanges
180-
.into_err()
181-
.details("Changelog has been truncated"));
210+
if (changelog.is_truncated || changelog.from_change_id == 0)
211+
&& request.since_state != State::Initial
212+
{
213+
return Err(trc::JmapEvent::CannotCalculateChanges.into_err().details(
214+
if changelog.is_truncated {
215+
"Change log is truncated"
216+
} else {
217+
"Since state is invalid"
218+
},
219+
));
182220
}
183221

184222
let mut changes = changelog
@@ -218,15 +256,15 @@ impl ChangesLookup for Server {
218256
.unwrap_or(changelog.to_change_id);
219257

220258
response.has_more_changes = changes.peek().is_some();
221-
response.new_state = if response.has_more_changes {
222-
State::new_intermediate(
259+
if response.has_more_changes {
260+
response.new_state = State::new_intermediate(
223261
changelog.from_change_id,
224262
change_id,
225263
items_sent + max_changes,
226-
)
227-
} else {
228-
State::new_exact(change_id)
229-
};
264+
);
265+
} else if response.new_state == State::Initial {
266+
response.new_state = State::new_exact(change_id)
267+
}
230268

231269
Ok(IntermediateChangesResponse {
232270
only_container_changes: is_container && !response.updated.is_empty() && !items_changed,

crates/store/src/query/log.rs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -120,22 +120,16 @@ impl Store {
120120
} else {
121121
changelog.changes.push(Change::InsertItem(change_id));
122122
}
123+
} else {
124+
changelog.from_change_id = change_id;
125+
changelog.to_change_id = change_id;
123126
}
124127
Ok(true)
125128
},
126129
)
127130
.await
128131
.caused_by(trc::location!())?;
129132

130-
// A non-existing change id was requested, return the last change id
131-
if changelog.changes.is_empty() && from_change_id != 0 && changelog.from_change_id == 0 {
132-
changelog.from_change_id = self
133-
.get_last_change_id(account_id, collection_)
134-
.await?
135-
.unwrap_or_default();
136-
changelog.to_change_id = changelog.from_change_id;
137-
}
138-
139133
Ok(changelog)
140134
}
141135

tests/src/jmap/mail/changes.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ pub async fn test(params: &mut JMAPTest) {
198198
state
199199
);
200200

201-
if let State::Initial = state {
201+
if &State::Initial == state {
202202
new_state = State::parse_str(changes.new_state()).unwrap();
203203
}
204204

@@ -312,6 +312,7 @@ pub async fn test(params: &mut JMAPTest) {
312312
assert_eq!(created, vec![2, 3, 11, 12]);
313313
assert_eq!(changes.updated(), Vec::<String>::new());
314314
assert_eq!(changes.destroyed(), Vec::<String>::new());
315+
params.destroy_all_mailboxes(account).await;
315316
params.assert_is_empty().await;
316317
}
317318

0 commit comments

Comments
 (0)