Trait is closer to what caldav servers provide

This commit is contained in:
daladim 2021-03-22 22:06:43 +01:00
parent 7af147e417
commit 86f3566532
11 changed files with 122 additions and 94 deletions

View file

@ -14,7 +14,6 @@ use async_trait::async_trait;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use crate::traits::CalDavSource; use crate::traits::CalDavSource;
use crate::traits::SyncSlave;
use crate::traits::PartialCalendar; use crate::traits::PartialCalendar;
use crate::traits::CompleteCalendar; use crate::traits::CompleteCalendar;
use crate::calendar::cached_calendar::CachedCalendar; use crate::calendar::cached_calendar::CachedCalendar;
@ -192,16 +191,6 @@ impl CalDavSource<CachedCalendar> for Cache {
} }
} }
impl SyncSlave for Cache {
fn get_last_sync(&self) -> Option<DateTime<Utc>> {
self.data.last_sync
}
fn update_last_sync(&mut self, timepoint: Option<DateTime<Utc>>) {
self.data.last_sync = Some(timepoint.unwrap_or_else(|| Utc::now()));
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View file

@ -10,6 +10,7 @@ use crate::traits::{PartialCalendar, CompleteCalendar};
use crate::calendar::{CalendarId, SupportedComponents, SearchFilter}; use crate::calendar::{CalendarId, SupportedComponents, SearchFilter};
use crate::Item; use crate::Item;
use crate::item::ItemId; use crate::item::ItemId;
use crate::item::VersionTag;
/// A calendar used by the [`cache`](crate::cache) module /// A calendar used by the [`cache`](crate::cache) module
@ -69,6 +70,32 @@ impl PartialCalendar for CachedCalendar {
Ok(()) Ok(())
} }
async fn get_item_version_tags(&self) -> Result<HashMap<ItemId, VersionTag>, Box<dyn Error>> {
Ok(self.items.iter()
.map(|(id, item)| (id.clone(), item.version_tag().clone()))
.collect()
)
}
async fn get_item_by_id_mut<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item> {
self.items.get_mut(id)
}
}
#[async_trait]
impl CompleteCalendar for CachedCalendar {
/// Returns the items that have been deleted after `since`
async fn get_items_deleted_since(&self, since: DateTime<Utc>) -> Result<HashSet<ItemId>, Box<dyn Error>> {
Ok(self.deleted_items.range(since..)
.map(|(_key, id)| id.clone())
.collect())
}
/// Returns the list of items that this calendar contains
async fn get_items(&self) -> Result<HashMap<ItemId, &Item>, Box<dyn Error>> {
self.get_items_modified_since(None, None).await
}
async fn get_items_modified_since(&self, since: Option<DateTime<Utc>>, filter: Option<SearchFilter>) -> Result<HashMap<ItemId, &Item>, Box<dyn Error>> { async fn get_items_modified_since(&self, since: Option<DateTime<Utc>>, filter: Option<SearchFilter>) -> Result<HashMap<ItemId, &Item>, Box<dyn Error>> {
let filter = filter.unwrap_or_default(); let filter = filter.unwrap_or_default();
@ -96,28 +123,4 @@ impl PartialCalendar for CachedCalendar {
Ok(map) Ok(map)
} }
async fn get_item_ids(&self) -> Result<HashSet<ItemId>, Box<dyn Error>> {
Ok(self.items.keys().cloned().collect())
}
async fn get_item_by_id_mut<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item> {
self.items.get_mut(id)
}
}
#[async_trait]
impl CompleteCalendar for CachedCalendar {
/// Returns the items that have been deleted after `since`
async fn get_items_deleted_since(&self, since: DateTime<Utc>) -> Result<HashSet<ItemId>, Box<dyn Error>> {
Ok(self.deleted_items.range(since..)
.map(|(_key, id)| id.clone())
.collect())
}
/// Returns the list of items that this calendar contains
async fn get_items(&self) -> Result<HashMap<ItemId, &Item>, Box<dyn Error>> {
self.get_items_modified_since(None, None).await
}
} }

View file

@ -7,8 +7,9 @@ use async_trait::async_trait;
use crate::traits::PartialCalendar; use crate::traits::PartialCalendar;
use crate::calendar::SupportedComponents; use crate::calendar::SupportedComponents;
use crate::calendar::CalendarId; use crate::calendar::CalendarId;
use crate::item::ItemId;
use crate::item::Item; use crate::item::Item;
use crate::item::ItemId;
use crate::item::VersionTag;
use crate::resource::Resource; use crate::resource::Resource;
static TASKS_BODY: &str = r#" static TASKS_BODY: &str = r#"
@ -51,14 +52,6 @@ impl PartialCalendar for RemoteCalendar {
self.supported_components self.supported_components
} }
/// Returns the items that have been last-modified after `since`
async fn get_items_modified_since(&self, since: Option<DateTime<Utc>>, _filter: Option<crate::calendar::SearchFilter>)
-> Result<HashMap<ItemId, &Item>, Box<dyn Error>>
{
log::error!("Not implemented");
Ok(HashMap::new())
}
/// Get the IDs of all current items in this calendar /// Get the IDs of all current items in this calendar
async fn get_item_ids(&self) -> Result<HashSet<ItemId>, Box<dyn Error>> { async fn get_item_ids(&self) -> Result<HashSet<ItemId>, Box<dyn Error>> {
let responses = crate::client::sub_request_and_extract_elems(&self.resource, "REPORT", TASKS_BODY.to_string(), "response").await?; let responses = crate::client::sub_request_and_extract_elems(&self.resource, "REPORT", TASKS_BODY.to_string(), "response").await?;
@ -82,6 +75,11 @@ impl PartialCalendar for RemoteCalendar {
Ok(item_ids) Ok(item_ids)
} }
async fn get_item_version_tags(&self) -> Result<HashMap<ItemId, VersionTag>, Box<dyn Error>> {
log::error!("Not implemented");
Ok(HashMap::new())
}
/// Returns a particular item /// Returns a particular item
async fn get_item_by_id_mut<'a>(&'a mut self, _id: &ItemId) -> Option<&'a mut Item> { async fn get_item_by_id_mut<'a>(&'a mut self, _id: &ItemId) -> Option<&'a mut Item> {
log::error!("Not implemented"); log::error!("Not implemented");

View file

@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize};
use chrono::{Utc, DateTime}; use chrono::{Utc, DateTime};
use crate::item::ItemId; use crate::item::ItemId;
use crate::item::VersionTag;
/// TODO: implement Event one day. /// TODO: implement Event one day.
/// This crate currently only supports tasks, not calendar events. /// This crate currently only supports tasks, not calendar events.
@ -12,6 +13,7 @@ pub struct Event {
id: ItemId, id: ItemId,
name: String, name: String,
last_modified: DateTime<Utc>, last_modified: DateTime<Utc>,
version_tag: VersionTag,
} }
impl Event { impl Event {
@ -26,4 +28,8 @@ impl Event {
pub fn last_modified(&self) -> DateTime<Utc> { pub fn last_modified(&self) -> DateTime<Utc> {
self.last_modified self.last_modified
} }
pub fn version_tag(&self) -> &VersionTag {
&self.version_tag
}
} }

View file

@ -7,6 +7,8 @@ use url::Url;
use crate::resource::Resource; use crate::resource::Resource;
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub enum Item { pub enum Item {
Event(crate::event::Event), Event(crate::event::Event),
@ -35,6 +37,13 @@ impl Item {
} }
} }
pub fn version_tag(&self) -> &VersionTag {
match self {
Item::Event(e) => e.version_tag(),
Item::Task(t) => t.version_tag(),
}
}
pub fn is_event(&self) -> bool { pub fn is_event(&self) -> bool {
match &self { match &self {
Item::Event(_) => true, Item::Event(_) => true,
@ -109,3 +118,24 @@ impl Display for ItemId {
write!(f, "{}", self.content) write!(f, "{}", self.content)
} }
} }
/// A VersionTag is basically a CalDAV `ctag` or `etag`. Whenever it changes, this means the data has changed.
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct VersionTag {
tag: String
}
impl From<String> for VersionTag {
fn from(tag: String) -> VersionTag {
Self { tag }
}
}
impl VersionTag {
/// Generate a random VesionTag. This is only useful in tests
pub fn random() -> Self {
let random = uuid::Uuid::new_v4().to_hyphenated().to_string();
Self { tag: random }
}
}

View file

@ -15,6 +15,7 @@ pub use calendar::cached_calendar::CachedCalendar;
mod item; mod item;
pub use item::Item; pub use item::Item;
pub use item::ItemId; pub use item::ItemId;
pub use item::VersionTag;
mod task; mod task;
pub use task::Task; pub use task::Task;
mod event; mod event;

View file

@ -7,7 +7,6 @@ use std::marker::PhantomData;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
use crate::traits::{CalDavSource, CompleteCalendar}; use crate::traits::{CalDavSource, CompleteCalendar};
use crate::traits::SyncSlave;
use crate::traits::PartialCalendar; use crate::traits::PartialCalendar;
use crate::Item; use crate::Item;
use crate::item::ItemId; use crate::item::ItemId;
@ -16,7 +15,7 @@ use crate::item::ItemId;
/// A data source that combines two `CalDavSources` (usually a server and a local cache), which is able to sync both sources. /// A data source that combines two `CalDavSources` (usually a server and a local cache), which is able to sync both sources.
pub struct Provider<L, T, S, U> pub struct Provider<L, T, S, U>
where where
L: CalDavSource<T> + SyncSlave, L: CalDavSource<T>,
T: CompleteCalendar, T: CompleteCalendar,
S: CalDavSource<U>, S: CalDavSource<U>,
U: PartialCalendar + Sync + Send, U: PartialCalendar + Sync + Send,
@ -32,7 +31,7 @@ where
impl<L, T, S, U> Provider<L, T, S, U> impl<L, T, S, U> Provider<L, T, S, U>
where where
L: CalDavSource<T> + SyncSlave, L: CalDavSource<T>,
T: CompleteCalendar, T: CompleteCalendar,
S: CalDavSource<U>, S: CalDavSource<U>,
U: PartialCalendar + Sync + Send, U: PartialCalendar + Sync + Send,
@ -51,19 +50,14 @@ where
pub fn server(&self) -> &S { &self.server } pub fn server(&self) -> &S { &self.server }
/// Returns the data source described as the `local` /// Returns the data source described as the `local`
pub fn local(&self) -> &L { &self.local } pub fn local(&self) -> &L { &self.local }
/// Returns the last time the `local` source has been synced
pub fn last_sync_timestamp(&self) -> Option<DateTime<Utc>> {
self.local.get_last_sync()
}
/// Performs a synchronisation between `local` and `server`. /// Performs a synchronisation between `local` and `server`.
/// ///
/// This bidirectional sync applies additions/deletions made on a source to the other source. /// This bidirectional sync applies additions/deletions made on a source to the other source.
/// In case of conflicts (the same item has been modified on both ends since the last sync, `server` always wins) /// In case of conflicts (the same item has been modified on both ends since the last sync, `server` always wins)
pub async fn sync(&mut self) -> Result<(), Box<dyn Error>> { pub async fn sync(&mut self) -> Result<(), Box<dyn Error>> {
let last_sync = self.local.get_last_sync(); log::info!("Starting a sync.");
log::info!("Starting a sync. Last sync was at {:?}", last_sync); /*
let cals_server = self.server.get_calendars().await?; let cals_server = self.server.get_calendars().await?;
for (id, cal_server) in cals_server { for (id, cal_server) in cals_server {
let mut cal_server = cal_server.lock().unwrap(); let mut cal_server = cal_server.lock().unwrap();
@ -77,8 +71,8 @@ where
}; };
let mut cal_local = cal_local.lock().unwrap(); let mut cal_local = cal_local.lock().unwrap();
// Step 1 - "Server always wins", so a delteion from the server must be applied locally, even if it was locally modified. // Step 1 - "Server always wins", so a deletion from the server must be applied locally, even if it was locally modified.
let mut local_dels = match last_sync { let mut local_dels = match cal_local.get_last_sync() {
None => HashSet::new(), None => HashSet::new(),
Some(date) => cal_local.get_items_deleted_since(date).await?, Some(date) => cal_local.get_items_deleted_since(date).await?,
}; };
@ -138,7 +132,7 @@ where
} }
self.local.update_last_sync(None); self.local.update_last_sync(None);
*/
Ok(()) Ok(())
} }
} }

View file

@ -2,6 +2,7 @@ use chrono::{Utc, DateTime};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::item::ItemId; use crate::item::ItemId;
use crate::item::VersionTag;
/// A to-do task /// A to-do task
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -11,6 +12,8 @@ pub struct Task {
/// The last modification date of this task /// The last modification date of this task
last_modified: DateTime<Utc>, last_modified: DateTime<Utc>,
/// The version tag of this item
version_tag: VersionTag,
/// The display name of the task /// The display name of the task
name: String, name: String,
@ -20,11 +23,12 @@ pub struct Task {
impl Task { impl Task {
/// Create a new Task /// Create a new Task
pub fn new(name: String, id: ItemId, last_modified: DateTime<Utc>) -> Self { pub fn new(name: String, id: ItemId, last_modified: DateTime<Utc>, version_tag: VersionTag) -> Self {
Self { Self {
id, id,
name, name,
last_modified, last_modified,
version_tag,
completed: false, completed: false,
} }
} }
@ -33,6 +37,7 @@ impl Task {
pub fn name(&self) -> &str { &self.name } pub fn name(&self) -> &str { &self.name }
pub fn completed(&self) -> bool { self.completed } pub fn completed(&self) -> bool { self.completed }
pub fn last_modified(&self) -> DateTime<Utc> { self.last_modified } pub fn last_modified(&self) -> DateTime<Utc> { self.last_modified }
pub fn version_tag(&self) -> &VersionTag { &self.version_tag }
fn update_last_modified(&mut self) { fn update_last_modified(&mut self) {
self.last_modified = Utc::now(); self.last_modified = Utc::now();

View file

@ -7,6 +7,7 @@ use chrono::{DateTime, Utc};
use crate::item::Item; use crate::item::Item;
use crate::item::ItemId; use crate::item::ItemId;
use crate::item::VersionTag;
use crate::calendar::CalendarId; use crate::calendar::CalendarId;
#[async_trait] #[async_trait]
@ -18,14 +19,6 @@ pub trait CalDavSource<T: PartialCalendar> {
async fn get_calendar(&self, id: &CalendarId) -> Option<Arc<Mutex<T>>>; async fn get_calendar(&self, id: &CalendarId) -> Option<Arc<Mutex<T>>>;
} }
pub trait SyncSlave {
/// Returns the last time this source successfully synced from a master source (e.g. from a server)
/// (or None in case it has never been synchronized)
fn get_last_sync(&self) -> Option<DateTime<Utc>>;
/// Update the last sync timestamp to now, or to a custom time in case `timepoint` is `Some`
fn update_last_sync(&mut self, timepoint: Option<DateTime<Utc>>);
}
/// A calendar we have a partial knowledge of. /// A calendar we have a partial knowledge of.
/// ///
/// Usually, this is a calendar from a remote source, that is synced to a CompleteCalendar /// Usually, this is a calendar from a remote source, that is synced to a CompleteCalendar
@ -40,12 +33,8 @@ pub trait PartialCalendar {
/// Returns the supported kinds of components for this calendar /// Returns the supported kinds of components for this calendar
fn supported_components(&self) -> crate::calendar::SupportedComponents; fn supported_components(&self) -> crate::calendar::SupportedComponents;
/// Returns the items that have been last-modified after `since` /// Get the IDs and the version tags of every item in this calendar
async fn get_items_modified_since(&self, since: Option<DateTime<Utc>>, filter: Option<crate::calendar::SearchFilter>) async fn get_item_version_tags(&self) -> Result<HashMap<ItemId, VersionTag>, Box<dyn Error>>;
-> Result<HashMap<ItemId, &Item>, Box<dyn Error>>;
/// Get the IDs of all current items in this calendar
async fn get_item_ids(&self) -> Result<HashSet<ItemId>, Box<dyn Error>>;
/// Returns a particular item /// Returns a particular item
async fn get_item_by_id_mut<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item>; async fn get_item_by_id_mut<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item>;
@ -67,6 +56,14 @@ pub trait PartialCalendar {
self.supported_components().contains(crate::calendar::SupportedComponents::EVENT) self.supported_components().contains(crate::calendar::SupportedComponents::EVENT)
} }
/// Get the IDs of all current items in this calendar
async fn get_item_ids(&self) -> Result<HashSet<ItemId>, Box<dyn Error>> {
let items = self.get_item_version_tags().await?;
Ok(items.iter()
.map(|(id, _tag)| id.clone())
.collect())
}
/// Finds the IDs of the items that are missing compared to a reference set /// Finds the IDs of the items that are missing compared to a reference set
async fn find_deletions_from(&self, reference_set: HashSet<ItemId>) -> Result<HashSet<ItemId>, Box<dyn Error>> { async fn find_deletions_from(&self, reference_set: HashSet<ItemId>) -> Result<HashSet<ItemId>, Box<dyn Error>> {
let current_items = self.get_item_ids().await?; let current_items = self.get_item_ids().await?;
@ -84,6 +81,10 @@ pub trait CompleteCalendar : PartialCalendar {
/// See also [`PartialCalendar::get_items_deleted_since`] /// See also [`PartialCalendar::get_items_deleted_since`]
async fn get_items_deleted_since(&self, since: DateTime<Utc>) -> Result<HashSet<ItemId>, Box<dyn Error>>; async fn get_items_deleted_since(&self, since: DateTime<Utc>) -> Result<HashSet<ItemId>, Box<dyn Error>>;
/// Returns the items that have been last-modified after `since`
async fn get_items_modified_since(&self, since: Option<DateTime<Utc>>, filter: Option<crate::calendar::SearchFilter>)
-> Result<HashMap<ItemId, &Item>, Box<dyn Error>>;
/// Returns the list of items that this calendar contains /// Returns the list of items that this calendar contains
async fn get_items(&self) -> Result<HashMap<ItemId, &Item>, Box<dyn Error>>; async fn get_items(&self) -> Result<HashMap<ItemId, &Item>, Box<dyn Error>>;
} }

View file

@ -53,9 +53,9 @@ async fn show_calendars() {
} }
println!(" Most recent changes:"); println!(" Most recent changes:");
for (_id, task) in cal.get_items_modified_since(None, None).await.unwrap() { // for (_id, task) in cal.get_item_version_tags(None, None).await.unwrap() {
my_tasks::utils::print_task(task); // my_tasks::utils::print_task(task);
} // }
} }
} }

View file

@ -4,11 +4,12 @@ use std::sync::{Arc, Mutex};
use chrono::{Utc, TimeZone}; use chrono::{Utc, TimeZone};
use url::Url; use url::Url;
use my_tasks::traits::{CalDavSource, SyncSlave}; use my_tasks::traits::CalDavSource;
use my_tasks::traits::PartialCalendar; use my_tasks::traits::PartialCalendar;
use my_tasks::cache::Cache; use my_tasks::cache::Cache;
use my_tasks::Item; use my_tasks::Item;
use my_tasks::ItemId; use my_tasks::ItemId;
use my_tasks::VersionTag;
use my_tasks::Task; use my_tasks::Task;
use my_tasks::calendar::cached_calendar::CachedCalendar; use my_tasks::calendar::cached_calendar::CachedCalendar;
use my_tasks::Provider; use my_tasks::Provider;
@ -53,23 +54,23 @@ async fn populate_test_provider() -> Provider<Cache, CachedCalendar, Cache, Cach
let cal_id = Url::parse("http://todo.list/cal").unwrap(); let cal_id = Url::parse("http://todo.list/cal").unwrap();
let task_a = Item::Task(Task::new("task A".into(), ItemId::random(), Utc.ymd(2000, 1, 1).and_hms(0, 0, 0))); let task_a = Item::Task(Task::new("task A".into(), ItemId::random(), Utc.ymd(2000, 1, 1).and_hms(0, 0, 0), VersionTag::random()));
let task_b = Item::Task(Task::new("task B".into(), ItemId::random(), Utc.ymd(2000, 1, 2).and_hms(0, 0, 0))); let task_b = Item::Task(Task::new("task B".into(), ItemId::random(), Utc.ymd(2000, 1, 2).and_hms(0, 0, 0), VersionTag::random()));
let task_c = Item::Task(Task::new("task C".into(), ItemId::random(), Utc.ymd(2000, 1, 3).and_hms(0, 0, 0))); let task_c = Item::Task(Task::new("task C".into(), ItemId::random(), Utc.ymd(2000, 1, 3).and_hms(0, 0, 0), VersionTag::random()));
let task_d = Item::Task(Task::new("task D".into(), ItemId::random(), Utc.ymd(2000, 1, 4).and_hms(0, 0, 0))); let task_d = Item::Task(Task::new("task D".into(), ItemId::random(), Utc.ymd(2000, 1, 4).and_hms(0, 0, 0), VersionTag::random()));
let task_e = Item::Task(Task::new("task E".into(), ItemId::random(), Utc.ymd(2000, 1, 5).and_hms(0, 0, 0))); let task_e = Item::Task(Task::new("task E".into(), ItemId::random(), Utc.ymd(2000, 1, 5).and_hms(0, 0, 0), VersionTag::random()));
let task_f = Item::Task(Task::new("task F".into(), ItemId::random(), Utc.ymd(2000, 1, 6).and_hms(0, 0, 0))); let task_f = Item::Task(Task::new("task F".into(), ItemId::random(), Utc.ymd(2000, 1, 6).and_hms(0, 0, 0), VersionTag::random()));
let task_g = Item::Task(Task::new("task G".into(), ItemId::random(), Utc.ymd(2000, 1, 7).and_hms(0, 0, 0))); let task_g = Item::Task(Task::new("task G".into(), ItemId::random(), Utc.ymd(2000, 1, 7).and_hms(0, 0, 0), VersionTag::random()));
let task_h = Item::Task(Task::new("task H".into(), ItemId::random(), Utc.ymd(2000, 1, 8).and_hms(0, 0, 0))); let task_h = Item::Task(Task::new("task H".into(), ItemId::random(), Utc.ymd(2000, 1, 8).and_hms(0, 0, 0), VersionTag::random()));
let task_i = Item::Task(Task::new("task I".into(), ItemId::random(), Utc.ymd(2000, 1, 9).and_hms(0, 0, 0))); let task_i = Item::Task(Task::new("task I".into(), ItemId::random(), Utc.ymd(2000, 1, 9).and_hms(0, 0, 0), VersionTag::random()));
let task_j = Item::Task(Task::new("task J".into(), ItemId::random(), Utc.ymd(2000, 1, 10).and_hms(0, 0, 0))); let task_j = Item::Task(Task::new("task J".into(), ItemId::random(), Utc.ymd(2000, 1, 10).and_hms(0, 0, 0), VersionTag::random()));
let task_k = Item::Task(Task::new("task K".into(), ItemId::random(), Utc.ymd(2000, 1, 11).and_hms(0, 0, 0))); let task_k = Item::Task(Task::new("task K".into(), ItemId::random(), Utc.ymd(2000, 1, 11).and_hms(0, 0, 0), VersionTag::random()));
let task_l = Item::Task(Task::new("task L".into(), ItemId::random(), Utc.ymd(2000, 1, 12).and_hms(0, 0, 0))); let task_l = Item::Task(Task::new("task L".into(), ItemId::random(), Utc.ymd(2000, 1, 12).and_hms(0, 0, 0), VersionTag::random()));
let task_m = Item::Task(Task::new("task M".into(), ItemId::random(), Utc.ymd(2000, 1, 12).and_hms(0, 0, 0))); let task_m = Item::Task(Task::new("task M".into(), ItemId::random(), Utc.ymd(2000, 1, 12).and_hms(0, 0, 0), VersionTag::random()));
let last_sync = task_m.last_modified(); // let last_sync = task_m.last_modified();
local.update_last_sync(Some(last_sync)); // local.update_last_sync(Some(last_sync));
assert!(last_sync < Utc::now()); // assert!(last_sync < Utc::now());
let task_b_id = task_b.id().clone(); let task_b_id = task_b.id().clone();
let task_c_id = task_c.id().clone(); let task_c_id = task_c.id().clone();
@ -129,7 +130,7 @@ async fn populate_test_provider() -> Provider<Cache, CachedCalendar, Cache, Cach
cal_server.delete_item(&task_l_id).await.unwrap(); cal_server.delete_item(&task_l_id).await.unwrap();
let task_n = Item::Task(Task::new("task N (new from server)".into(), ItemId::random(), Utc::now())); let task_n = Item::Task(Task::new("task N (new from server)".into(), ItemId::random(), Utc::now(), VersionTag::random()));
cal_server.add_item(task_n).await; cal_server.add_item(task_n).await;
@ -158,7 +159,7 @@ async fn populate_test_provider() -> Provider<Cache, CachedCalendar, Cache, Cach
cal_local.delete_item(&task_k_id).await.unwrap(); cal_local.delete_item(&task_k_id).await.unwrap();
cal_local.delete_item(&task_l_id).await.unwrap(); cal_local.delete_item(&task_l_id).await.unwrap();
let task_o = Item::Task(Task::new("task O (new from local)".into(), ItemId::random(), Utc::now())); let task_o = Item::Task(Task::new("task O (new from local)".into(), ItemId::random(), Utc::now(), VersionTag::random()));
cal_local.add_item(task_o).await; cal_local.add_item(task_o).await;
Provider::new(server, local) Provider::new(server, local)