api: rewrite conflict detection in iterators

This commit is contained in:
Peter Cai 2020-02-22 17:33:42 +08:00
parent 3f266269cc
commit 09b2f945d2
No known key found for this signature in database
GPG Key ID: 71F5FB4E4F3FD54F
3 changed files with 51 additions and 34 deletions

16
Cargo.lock generated
View File

@ -240,6 +240,12 @@ dependencies = [
"regex 0.2.11",
]
[[package]]
name = "either"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
[[package]]
name = "fake-simd"
version = "0.1.2"
@ -412,6 +418,15 @@ dependencies = [
"libc",
]
[[package]]
name = "itertools"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.5"
@ -1079,6 +1094,7 @@ dependencies = [
"diesel",
"diesel_migrations",
"dotenv",
"itertools",
"lazy_static",
"regex 1.3.4",
"rocket",

View File

@ -17,4 +17,5 @@ scrypt = "0.2.0"
uuid = { version = "0.8", features = ["v4"] }
chrono = "0.4"
serde_json = "1.0"
regex = "1"
regex = "1"
itertools = "0.8"

View File

@ -2,6 +2,7 @@ use crate::DbConn;
use crate::user;
use crate::item;
use crate::lock::UserLock;
use itertools::{Itertools, Either};
use rocket::State;
use rocket::http::Status;
use rocket::response::status::Custom;
@ -258,42 +259,41 @@ fn items_sync(
}
}
// Then, update all items sent by client
let mut last_id: i64 = -1;
for mut it in inner_params.items.into_iter() {
// Handle conflicts
// Anything that we just retrieved but need to save immediately
// is potentially a conflict
// TODO: how do we handle this when the sync needs multiple requests
// to finish?
let mut conflicted = false;
for y in resp.retrieved_items.iter() {
if it.uuid == y.uuid {
conflicted = true;
// We assume enc_item_key identifies an "item"
if it.enc_item_key == y.enc_item_key {
// A sync conflict
resp.conflicts.push(SyncConflict {
conf_type: "sync_conflict".to_string(),
server_item: Some(y.clone()),
unsaved_item: None
});
} else {
// A UUID conflict (unlikely)
resp.conflicts.push(SyncConflict {
conf_type: "uuid_conflict".to_string(),
server_item: None,
unsaved_item: Some(it.clone())
})
}
// Detect conflicts between client items and server items
let (items_conflicted, items_to_save): (Vec<_>, Vec<_>) =
inner_params.items.into_iter().partition_map(|client_item| {
let conflict: Vec<_> = resp.retrieved_items.iter()
.filter(|server_item| client_item.uuid == server_item.uuid)
.collect();
if !conflict.is_empty() {
Either::Left((client_item, conflict[0].clone()))
} else {
Either::Right(client_item)
}
});
// Convert conflicts into the format our client wants
resp.conflicts = items_conflicted.into_iter().map(|(client_item, server_item)| {
// We assume enc_item_key "identifies" an "item"
if client_item.enc_item_key == server_item.enc_item_key {
SyncConflict {
conf_type: "sync_conflict".to_string(),
server_item: Some(server_item),
unsaved_item: None
}
} else {
// A UUID conflict (unlikely)
SyncConflict {
conf_type: "uuid_conflict".to_string(),
server_item: None,
unsaved_item: Some(client_item)
}
}
}).collect();
// do not save conflicted items
if conflicted {
continue;
}
// Then, update all items sent by client
let mut last_id: i64 = -1;
for mut it in items_to_save.into_iter() {
// Always update updated_at for all items on server
it.updated_at =
Some(chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true));