diff --git a/src/provider/mod.rs b/src/provider/mod.rs index 55e1127..348a104 100644 --- a/src/provider/mod.rs +++ b/src/provider/mod.rs @@ -14,6 +14,7 @@ use crate::calendar::CalendarId; mod sync_progress; use sync_progress::SyncProgress; +pub use sync_progress::FeedbackSender; /// A data source that combines two `CalDavSource`s, which is able to sync both sources. /// @@ -64,22 +65,34 @@ where /// To be sure `local` accurately mirrors the `remote` source, you can run [`Provider::sync`] pub fn remote(&self) -> &R { &self.remote } - /// Performs a synchronisation between `local` and `remote`. + /// Performs a synchronisation between `local` and `remote`, and provide feeedback to the user about the progress. /// /// 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, `remote` always wins) /// /// It returns whether the sync was totally successful (details about errors are logged using the `log::*` macros). /// In case errors happened, the sync might have been partially executed, and you can safely run this function again, since it has been designed to gracefully recover from errors. - pub async fn sync(&mut self) -> bool { - let mut result = SyncProgress::new(); - if let Err(err) = self.run_sync(&mut result).await { - result.error(&format!("Sync terminated because of an error: {}", err)); - } - result.is_success() + pub async fn sync_with_feedback(&mut self, feedback_sender: FeedbackSender) -> bool { + let mut progress = SyncProgress::new_with_feedback_channel(feedback_sender); + self.run_sync(&mut progress).await } - async fn run_sync(&mut self, progress: &mut SyncProgress) -> Result<(), Box> { + /// Performs a synchronisation between `local` and `remote`, without giving any feedback. + /// + /// See [sync_with_feedback] + pub async fn sync(&mut self) -> bool { + let mut progress = SyncProgress::new(); + self.run_sync(&mut progress).await + } + + async fn run_sync(&mut self, progress: &mut SyncProgress) -> bool { + if let Err(err) = self.run_sync_inner(progress).await { + progress.error(&format!("Sync terminated because of an error: {}", err)); + } + progress.is_success() + } + + async fn run_sync_inner(&mut self, progress: &mut SyncProgress) -> Result<(), Box> { progress.info("Starting a sync."); let mut handled_calendars = HashSet::new(); diff --git a/src/provider/sync_progress.rs b/src/provider/sync_progress.rs index c0897f1..2140bd0 100644 --- a/src/provider/sync_progress.rs +++ b/src/provider/sync_progress.rs @@ -1,11 +1,65 @@ -/// A counter of errors that happen during a sync +//! Utilities to track the progression of a sync + +use std::fmt::{Display, Error, Formatter}; + +/// An event that happens during a sync +pub enum SyncEvent { + /// Sync has not started + NotStarted, + /// Sync has just started but no calendar is handled yet + Started, + /// Sync is in progress. + InProgress{ calendar: String, details: String}, + /// Sync is finished + Finished{ success: bool }, +} + +impl Display for SyncEvent { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + match self { + SyncEvent::NotStarted => write!(f, "Not started"), + SyncEvent::Started => write!(f, "Sync has started"), + SyncEvent::InProgress{calendar, details} => write!(f, "[{}] {}", calendar, details), + SyncEvent::Finished{success} => match success { + true => write!(f, "Sync successfully finished"), + false => write!(f, "Sync finished with errors"), + } + } + } +} + +impl Default for SyncEvent { + fn default() -> Self { + Self::NotStarted + } +} + + + +pub type FeedbackSender = tokio::sync::watch::Sender; +pub type FeedbackReceiver = tokio::sync::watch::Receiver; + +pub fn feedback_channel() -> (FeedbackSender, FeedbackReceiver) { + tokio::sync::watch::channel(SyncEvent::default()) +} + + + + +/// A structure that tracks the progression and the errors that happen during a sync pub struct SyncProgress { n_errors: u32, + feedback_channel: Option } impl SyncProgress { pub fn new() -> Self { - Self { n_errors: 0 } + Self { n_errors: 0, feedback_channel: None } } + pub fn new_with_feedback_channel(channel: FeedbackSender) -> Self { + Self { n_errors: 0, feedback_channel: Some(channel) } + } + + pub fn is_success(&self) -> bool { self.n_errors == 0 }