use std::collections::{HashMap, HashSet};
use crate::calendar::Calendar;
use crate::date_range::DateRange;
use crate::diff::{DiffKind, EventDiff};
use crate::error::{CalDirError, CalDirResult};
use crate::event::Event;
pub struct CalendarDiff {
pub calendar: Calendar,
pub to_push: Vec<EventDiff>,
pub to_pull: Vec<EventDiff>,
}
impl CalendarDiff {
pub fn is_empty(&self) -> bool {
self.to_push.is_empty() && self.to_pull.is_empty()
}
pub async fn apply_push(&self) -> CalDirResult<()> {
let all_deletes =
!self.to_push.is_empty() && self.to_push.iter().all(|d| d.kind == DiffKind::Delete);
let local_empty = self.calendar.events()?.is_empty();
if all_deletes && local_empty {
return Err(CalDirError::Sync(format!(
"Refusing to delete all {} remote events for '{}' — local calendar is empty. \
If you deleted files by accident, run `caldir pull` to restore them.",
self.to_push.len(),
self.calendar.slug,
)));
}
let remote = self
.calendar
.remote()
.ok_or_else(|| CalDirError::RemoteNotFound(self.calendar.slug.to_string()))?;
let mut known_ids: HashSet<String> = self
.calendar
.state()
.read()
.known_event_ids
.into_iter()
.collect();
for diff in &self.to_push {
match diff.kind {
DiffKind::Create => {
let event = diff.new.as_ref().expect("Create must have new event");
let created = remote.create_event(event).await?;
self.calendar.update_event(&event.uid, &created)?;
known_ids.insert(created.unique_id());
}
DiffKind::Update => {
let event = diff.new.as_ref().expect("Update must have new event");
let updated = remote.update_event(event).await?;
self.calendar.update_event(&event.uid, &updated)?;
}
DiffKind::Delete => {
let event = diff.old.as_ref().expect("Delete must have old event");
remote.delete_event(event).await?;
known_ids.remove(&event.unique_id());
}
}
}
self.calendar.state().save(&known_ids)?;
Ok(())
}
pub fn apply_discard(&self) -> CalDirResult<()> {
for diff in &self.to_push {
match diff.kind {
DiffKind::Create => {
let event = diff.new.as_ref().expect("Create must have new event");
self.calendar
.delete_event(&event.uid, event.recurrence_id.as_ref())?;
}
DiffKind::Update => {
let old = diff.old.as_ref().expect("Update must have old event");
let new = diff.new.as_ref().expect("Update must have new event");
self.calendar.update_event(&new.uid, old)?;
}
DiffKind::Delete => {
let event = diff.old.as_ref().expect("Delete must have old event");
self.calendar.create_event(event)?;
}
}
}
Ok(())
}
pub fn apply_pull(&self) -> CalDirResult<()> {
let mut known_ids: HashSet<String> = self
.calendar
.state()
.read()
.known_event_ids
.into_iter()
.collect();
for diff in &self.to_pull {
match diff.kind {
DiffKind::Create => {
let event = diff.new.as_ref().expect("Create must have new event");
self.calendar.create_event(event)?;
known_ids.insert(event.unique_id());
}
DiffKind::Update => {
let event = diff.new.as_ref().expect("Update must have new event");
self.calendar.update_event(&event.uid, event)?;
}
DiffKind::Delete => {
let event = diff.old.as_ref().expect("Delete must have old event");
self.calendar
.delete_event(&event.uid, event.recurrence_id.as_ref())?;
known_ids.remove(&event.unique_id());
}
}
}
self.calendar.state().save(&known_ids)?;
Ok(())
}
pub async fn from_calendar(calendar: &Calendar, range: &DateRange) -> CalDirResult<Self> {
let remote = calendar
.remote()
.ok_or_else(|| CalDirError::RemoteNotFound(calendar.slug.to_string()))?;
let remote_events = remote.events(range).await?;
let local_events = calendar.events()?;
let known_event_ids = calendar.state().read().known_event_ids;
let local_by_key: HashMap<_, _> = local_events
.into_iter()
.map(|e| (event_key(&e.event), e))
.collect();
let remote_by_key: HashMap<_, _> = remote_events
.into_iter()
.map(|e| (event_key(&e), e))
.collect();
let mut to_push = Vec::new();
let mut to_pull = Vec::new();
for (key, local) in &local_by_key {
if remote_by_key.contains_key(key) {
continue; }
if known_event_ids.contains(&local.event.unique_id()) {
#[allow(clippy::collapsible_if)]
if let Some(diff) = EventDiff::get_diff(Some(local.event.clone()), None) {
if local.is_in_sync_range() {
to_pull.push(diff);
}
}
} else {
if let Some(diff) = EventDiff::get_diff(None, Some(local.event.clone())) {
to_push.push(diff);
}
}
}
for (key, remote) in &remote_by_key {
if local_by_key.contains_key(key) {
continue; }
if known_event_ids.contains(&remote.unique_id()) {
if let Some(diff) = EventDiff::get_diff(Some(remote.clone()), None) {
to_push.push(diff);
}
} else {
if let Some(diff) = EventDiff::get_diff(None, Some(remote.clone())) {
to_pull.push(diff);
}
}
}
for (key, local) in &local_by_key {
if let Some(remote) = remote_by_key.get(key) {
if local.event == *remote {
continue;
}
if local.is_newer_than(remote) {
if let Some(diff) =
EventDiff::get_diff(Some(remote.clone()), Some(local.event.clone()))
{
to_push.push(diff);
}
} else {
if let Some(diff) =
EventDiff::get_diff(Some(local.event.clone()), Some(remote.clone()))
{
to_pull.push(diff);
}
}
}
}
let sort_by_start =
|a: &EventDiff, b: &EventDiff| a.event().start.to_utc().cmp(&b.event().start.to_utc());
to_push.sort_by(sort_by_start);
to_pull.sort_by(sort_by_start);
Ok(CalendarDiff {
calendar: calendar.clone(),
to_push,
to_pull,
})
}
}
fn event_key(event: &Event) -> (String, Option<String>) {
(
event.uid.clone(),
event.recurrence_id.as_ref().map(|t| t.to_ics_string()),
)
}