use std::collections::{btree_map, BTreeMap};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::model::{Etag, RemoteSeriesId, SeriesId};
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
struct Entry {
#[serde(default, skip_serializing_if = "Option::is_none")]
etag: Option<Etag>,
#[serde(default, skip_serializing_if = "Option::is_none")]
last_sync: Option<DateTime<Utc>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
last_modified: Option<DateTime<Utc>>,
}
impl Entry {
fn is_empty(&self) -> bool {
self.etag.is_none() && self.last_sync.is_none() && self.last_modified.is_none()
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub(crate) struct Export {
id: Uuid,
remote: RemoteSeriesId,
#[serde(flatten)]
entry: Entry,
}
#[derive(Default)]
pub struct Database {
data: BTreeMap<(Uuid, RemoteSeriesId), Entry>,
}
impl Database {
pub(crate) fn export(&self) -> impl Iterator<Item = Export> {
self.data
.clone()
.into_iter()
.map(|((id, remote), entry)| Export { id, remote, entry })
.filter(|export| !export.entry.is_empty())
}
pub(crate) fn import_push(&mut self, sync: Export) {
if !sync.entry.is_empty() {
self.data.insert((sync.id, sync.remote), sync.entry);
}
}
#[must_use]
pub(crate) fn import_last_sync(
&mut self,
series_id: &SeriesId,
remote_id: &RemoteSeriesId,
now: &DateTime<Utc>,
) -> bool {
let e = self.data.entry((*series_id.id(), *remote_id)).or_default();
e.last_sync.replace(*now) != Some(*now)
}
#[must_use]
pub(crate) fn update_last_modified(
&mut self,
series_id: &SeriesId,
remote_id: &RemoteSeriesId,
last_modified: Option<&DateTime<Utc>>,
) -> bool {
let e = self.data.entry((*series_id.id(), *remote_id)).or_default();
if let Some(last_modified) = last_modified {
e.last_modified.replace(*last_modified) != Some(*last_modified)
} else {
e.last_modified.take().is_some()
}
}
#[must_use]
pub(crate) fn series_update_sync(
&mut self,
series_id: &SeriesId,
remote_id: &RemoteSeriesId,
now: &DateTime<Utc>,
last_modified: Option<&DateTime<Utc>>,
) -> bool {
let e = self.data.entry((*series_id.id(), *remote_id)).or_default();
let mut updated = e.last_sync.replace(*now) != Some(*now);
if let Some(last_modified) = last_modified {
updated |= e.last_modified.replace(*last_modified) != Some(*last_modified);
} else {
updated |= e.last_modified.take().is_some();
}
updated
}
#[must_use]
pub(crate) fn update_last_etag(
&mut self,
id: &SeriesId,
remote_id: &RemoteSeriesId,
etag: Etag,
) -> bool {
match self.data.entry((*id.id(), *remote_id)) {
btree_map::Entry::Vacant(e) => {
let e = e.insert(Entry::default());
e.etag = Some(etag);
true
}
btree_map::Entry::Occupied(mut e) => {
if e.get().etag.as_ref() == Some(&etag) {
return false;
}
e.get_mut().etag = Some(etag);
true
}
}
}
pub(crate) fn last_sync(
&self,
id: &SeriesId,
remote_id: &RemoteSeriesId,
) -> Option<&DateTime<Utc>> {
self.data.get(&(*id.id(), *remote_id))?.last_sync.as_ref()
}
pub(crate) fn last_modified(
&self,
id: &SeriesId,
remote_id: &RemoteSeriesId,
) -> Option<&DateTime<Utc>> {
self.data
.get(&(*id.id(), *remote_id))?
.last_modified
.as_ref()
}
pub(crate) fn last_etag(&self, id: &SeriesId, remote_id: &RemoteSeriesId) -> Option<&Etag> {
let entry = self.data.get(&(*id.id(), *remote_id))?;
entry.etag.as_ref()
}
pub(crate) fn clear(&mut self) {
self.data.clear();
}
}