use std::collections::HashMap;
use chrono::Utc;
use todoist_api_rs::sync::{CollaboratorState, SyncResponse};
use crate::Cache;
pub(crate) fn apply_sync_response(cache: &mut Cache, response: &SyncResponse) {
let now = Utc::now();
cache.sync_token = response.sync_token.clone();
cache.last_sync = Some(now);
if response.full_sync {
cache.full_sync_date_utc = response
.full_sync_date_utc
.as_ref()
.and_then(|s| chrono::DateTime::parse_from_rfc3339(s).ok())
.map(|dt| dt.with_timezone(&Utc))
.or(Some(now));
}
if response.full_sync {
cache.items = response
.items
.iter()
.filter(|i| !i.is_deleted)
.cloned()
.collect();
cache.projects = response
.projects
.iter()
.filter(|p| !p.is_deleted)
.cloned()
.collect();
cache.labels = response
.labels
.iter()
.filter(|l| !l.is_deleted)
.cloned()
.collect();
cache.sections = response
.sections
.iter()
.filter(|s| !s.is_deleted)
.cloned()
.collect();
cache.notes = response
.notes
.iter()
.filter(|n| !n.is_deleted)
.cloned()
.collect();
cache.project_notes = response
.project_notes
.iter()
.filter(|n| !n.is_deleted)
.cloned()
.collect();
cache.reminders = response
.reminders
.iter()
.filter(|r| !r.is_deleted)
.cloned()
.collect();
cache.filters = response
.filters
.iter()
.filter(|f| !f.is_deleted)
.cloned()
.collect();
cache.collaborators = response.collaborators.clone();
cache.collaborator_states = response
.collaborator_states
.iter()
.filter(|state| state.state != "deleted")
.cloned()
.collect();
} else {
merge_resources(
&mut cache.items,
&response.items,
|i| &i.id,
|i| i.is_deleted,
);
merge_resources(
&mut cache.projects,
&response.projects,
|p| &p.id,
|p| p.is_deleted,
);
merge_resources(
&mut cache.labels,
&response.labels,
|l| &l.id,
|l| l.is_deleted,
);
merge_resources(
&mut cache.sections,
&response.sections,
|s| &s.id,
|s| s.is_deleted,
);
merge_resources(
&mut cache.notes,
&response.notes,
|n| &n.id,
|n| n.is_deleted,
);
merge_resources(
&mut cache.project_notes,
&response.project_notes,
|n| &n.id,
|n| n.is_deleted,
);
merge_resources(
&mut cache.reminders,
&response.reminders,
|r| &r.id,
|r| r.is_deleted,
);
merge_resources(
&mut cache.filters,
&response.filters,
|f| &f.id,
|f| f.is_deleted,
);
merge_resources(
&mut cache.collaborators,
&response.collaborators,
|c| &c.id,
|_| false,
);
merge_collaborator_states(
&mut cache.collaborator_states,
&response.collaborator_states,
);
}
if response.user.is_some() {
cache.user = response.user.clone();
}
cache.rebuild_indexes();
}
pub(crate) fn apply_mutation_response(cache: &mut Cache, response: &SyncResponse) {
let now = Utc::now();
cache.sync_token = response.sync_token.clone();
cache.last_sync = Some(now);
merge_resources(
&mut cache.items,
&response.items,
|i| &i.id,
|i| i.is_deleted,
);
merge_resources(
&mut cache.projects,
&response.projects,
|p| &p.id,
|p| p.is_deleted,
);
merge_resources(
&mut cache.labels,
&response.labels,
|l| &l.id,
|l| l.is_deleted,
);
merge_resources(
&mut cache.sections,
&response.sections,
|s| &s.id,
|s| s.is_deleted,
);
merge_resources(
&mut cache.notes,
&response.notes,
|n| &n.id,
|n| n.is_deleted,
);
merge_resources(
&mut cache.project_notes,
&response.project_notes,
|n| &n.id,
|n| n.is_deleted,
);
merge_resources(
&mut cache.reminders,
&response.reminders,
|r| &r.id,
|r| r.is_deleted,
);
merge_resources(
&mut cache.filters,
&response.filters,
|f| &f.id,
|f| f.is_deleted,
);
merge_resources(
&mut cache.collaborators,
&response.collaborators,
|c| &c.id,
|_| false,
);
merge_collaborator_states(
&mut cache.collaborator_states,
&response.collaborator_states,
);
if response.user.is_some() {
cache.user = response.user.clone();
}
cache.rebuild_indexes();
}
fn merge_collaborator_states(
existing: &mut Vec<CollaboratorState>,
incoming: &[CollaboratorState],
) {
let mut index: HashMap<(&str, &str), usize> = HashMap::with_capacity(existing.len());
for (i, state) in existing.iter().enumerate() {
index.insert((state.project_id.as_str(), state.user_id.as_str()), i);
}
let mut updates: Vec<(usize, &CollaboratorState)> = Vec::with_capacity(incoming.len());
let mut inserts: Vec<&CollaboratorState> = Vec::with_capacity(incoming.len() / 4);
let mut to_remove: Vec<usize> = Vec::with_capacity(incoming.len() / 10);
for state in incoming {
let key = (state.project_id.as_str(), state.user_id.as_str());
let pos = index.get(&key).copied();
if state.state == "deleted" {
if let Some(idx) = pos {
to_remove.push(idx);
}
} else if let Some(idx) = pos {
updates.push((idx, state));
} else {
inserts.push(state);
}
}
for (idx, state) in updates {
existing[idx] = state.clone();
}
existing.reserve(inserts.len());
existing.extend(inserts.into_iter().cloned());
to_remove.sort_unstable();
for idx in to_remove.into_iter().rev() {
existing.remove(idx);
}
}
pub(crate) fn merge_resources<T, F, D>(
existing: &mut Vec<T>,
incoming: &[T],
get_id: F,
is_deleted: D,
) where
T: Clone,
F: Fn(&T) -> &str,
D: Fn(&T) -> bool,
{
let mut index: HashMap<&str, usize> = HashMap::with_capacity(existing.len());
for (i, item) in existing.iter().enumerate() {
index.insert(get_id(item), i);
}
let mut updates: Vec<(usize, &T)> = Vec::with_capacity(incoming.len());
let mut inserts: Vec<&T> = Vec::with_capacity(incoming.len() / 4);
let mut to_remove: Vec<usize> = Vec::with_capacity(incoming.len() / 10);
for item in incoming {
let id = get_id(item);
let pos = index.get(id).copied();
if is_deleted(item) {
if let Some(idx) = pos {
to_remove.push(idx);
}
} else if let Some(idx) = pos {
updates.push((idx, item));
} else {
inserts.push(item);
}
}
for (idx, item) in updates {
existing[idx] = item.clone();
}
existing.reserve(inserts.len());
existing.extend(inserts.into_iter().cloned());
to_remove.sort_unstable();
for idx in to_remove.into_iter().rev() {
existing.remove(idx);
}
}