diff --git a/src/lib.rs b/src/lib.rs index 1df735f..27c6cac 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,9 +14,10 @@ pub mod calendar; pub use calendar::Calendar; mod task; pub use task::Task; +pub mod provider; +pub use provider::Provider; pub mod client; -pub mod provider; pub mod cache; pub mod settings; diff --git a/src/provider.rs b/src/provider.rs index 926514d..e2663b0 100644 --- a/src/provider.rs +++ b/src/provider.rs @@ -1,3 +1,101 @@ //! This modules abstracts data sources and merges them in a single virtual one -pub struct Provider {} +use std::error::Error; + +use chrono::{DateTime, Utc}; + +use crate::traits::CalDavSource; +use crate::Calendar; +use crate::Task; + + +pub struct Provider +where + S: CalDavSource, + L: CalDavSource, +{ + /// The remote server + server: S, + /// The local cache + local: L, + + /// The last time the provider successfully synchronized both sources + last_sync: DateTime, +} + +impl Provider +where + S: CalDavSource, + L: CalDavSource, +{ + /// Create a provider that will merge both sources + pub fn new(server: S, local: L, last_sync: DateTime) -> Self { + Self { server, local, last_sync } + } + + pub fn server(&self) -> &S { &self.server } + pub fn local(&self) -> &L { &self.local } + + pub async fn sync(&mut self) -> Result<(), Box> { + self.pull_items_from_server().await?; + self.resolve_conflicts().await; + self.push_items_to_server().await; + + // what to do with errors? Return Err directly? Go ahead as far as we can? + Ok(()) + } + + pub async fn pull_items_from_server(&mut self) -> Result<(), Box> { + let cals_server = self.server.get_calendars_mut().await?; + + for cal_server in cals_server { + let cal_local = match self.local.get_calendar_mut(cal_server.url().clone()).await { + None => { + log::error!("TODO: implement here"); + continue; + }, + Some(cal) => cal, + }; + + let server_mod = cal_server.get_tasks_modified_since(Some(self.last_sync), None); + let local_mod = cal_local.get_tasks_modified_since(Some(self.last_sync), None); + + let mut tasks_to_add_to_local = Vec::new(); + for (new_id, new_item) in &server_mod { + tasks_to_add_to_local.push((*new_item).clone()); + } + + let mut tasks_to_add_to_server = Vec::new(); + for (new_id, new_item) in &local_mod { + if server_mod.contains_key(new_id) { + log::warn!("Conflict for task {} ({}). Using the server version.", new_item.name(), new_id); + continue; + } + tasks_to_add_to_server.push((*new_item).clone()); + } + + move_to_calendar(&mut tasks_to_add_to_local, cal_local); + move_to_calendar(&mut tasks_to_add_to_server, cal_server); + } + + Ok(()) + } + + pub async fn resolve_conflicts(&mut self) { + log::error!("We should do something here"); + } + + pub async fn push_items_to_server(&mut self) { + + } + +} + + +fn move_to_calendar(tasks: &mut Vec, calendar: &mut Calendar) { + while tasks.len() > 0 { + let task = tasks.remove(0); + calendar.add_task(task); + } +} + diff --git a/src/task.rs b/src/task.rs index a3d95ab..c80037d 100644 --- a/src/task.rs +++ b/src/task.rs @@ -1,10 +1,12 @@ +use std::fmt::{Display, Formatter}; + use chrono::{Utc, DateTime}; use serde::{Deserialize, Serialize}; // TODO: turn into this one day // pub type TaskId = String; // This is an HTML "etag" -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Hash, Serialize, Deserialize)] pub struct TaskId { content: String, } @@ -14,6 +16,12 @@ impl TaskId{ Self { content:u } } } +impl Eq for TaskId {} +impl Display for TaskId { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { + write!(f, "{}", self.content) + } +} /// A to-do task #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/tests/sync.rs b/tests/sync.rs index 32a144a..36fc893 100644 --- a/tests/sync.rs +++ b/tests/sync.rs @@ -18,18 +18,35 @@ async fn test_sync() { let mut provider = populate_test_provider().await; provider.sync().await.unwrap(); - let cal_server = provider.server().get_calendars().await.unwrap(); - let cal_local = provider.local().get_calendars().await.unwrap(); - assert_eq!(cal_server, cal_local, "{:#?}\n{:#?}", cal_server, cal_local); + let cals_server = provider.server().get_calendars().await.unwrap(); + let cals_local = provider.local().get_calendars().await.unwrap(); + print_calendar_list(cals_local); + panic!(); + + //assert_eq!(cal_server, cal_local, "{:#?}\n{:#?}", cal_server, cal_local); panic!("TODO: also check that the contents are expected!"); } +/// A debug utility that pretty-prints calendars +fn print_calendar_list(cals: &Vec) { + for cal in cals { + println!("CAL {}", cal.url()); + for (_, item) in cal.get_tasks(None) { + let completion = if item.completed() {"✓"} else {" "}; + println!(" {} {}", completion, item.name()); + } + } +} + /// Populate sources with the following: /// * At the last sync: both sources had A, B, C, D, E, F, G, H at last sync /// * Before the newer sync, this will be the content of the sources: /// * server: A, C, D, E', F', G~, H , I -/// * cache: A, B, D', E, F'', G , H~, J +/// * cache: A, B, D', E, F'', G , H~, J +/// +/// Hence, here is the expected result after the sync: +/// * both: A, D', E', F', G~, H~, I, J /// /// Notes: /// * X': name has been modified since the last sync