Fixed initial sync

This commit is contained in:
daladim 2021-04-04 00:35:59 +02:00
parent 842e34fb85
commit f2dd528d33
9 changed files with 233 additions and 62 deletions

View file

@ -17,6 +17,7 @@ use crate::traits::BaseCalendar;
use crate::traits::CompleteCalendar;
use crate::calendar::cached_calendar::CachedCalendar;
use crate::calendar::CalendarId;
use crate::calendar::SupportedComponents;
const MAIN_FILE: &str = "data.json";
@ -25,6 +26,9 @@ const MAIN_FILE: &str = "data.json";
pub struct Cache {
backing_folder: PathBuf,
data: CachedData,
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
is_mocking_remote_source: bool,
}
#[derive(Default, Debug, Serialize, Deserialize)]
@ -34,6 +38,13 @@ struct CachedData {
}
impl Cache {
/// Activate the "mocking remote source" features (i.e. tell its children calendars that they are mocked remote calendars)
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
pub fn set_is_mocking_remote_source(&mut self) {
self.is_mocking_remote_source = true;
}
/// Get the path to the cache folder
pub fn cache_folder() -> PathBuf {
return PathBuf::from(String::from("~/.config/my-tasks/cache/"))
@ -78,6 +89,9 @@ impl Cache {
Ok(Self{
backing_folder: PathBuf::from(folder),
data,
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
is_mocking_remote_source: false,
})
}
@ -91,6 +105,9 @@ impl Cache {
Self{
backing_folder: PathBuf::from(folder_path),
data: CachedData::default(),
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
is_mocking_remote_source: false,
}
}
@ -198,13 +215,19 @@ impl CalDavSource<CachedCalendar> for Cache {
self.data.calendars.get(id).map(|arc| arc.clone())
}
async fn insert_calendar(&mut self, new_calendar: CachedCalendar) -> Result<Arc<Mutex<CachedCalendar>>, Box<dyn Error>> {
let id = new_calendar.id().clone();
async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents) -> Result<Arc<Mutex<CachedCalendar>>, Box<dyn Error>> {
log::debug!("Inserting local calendar {}", id);
let new_calendar = CachedCalendar::new(name, id.clone(), supported_components);
let arc = Arc::new(Mutex::new(new_calendar));
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
if self.is_mocking_remote_source {
arc.lock().unwrap().set_is_mocking_remote_calendar();
}
match self.data.calendars.insert(id, arc.clone()) {
Some(_) => Err("Attempt to insert calendar failed: there is alredy such a calendar.".into()),
None => Ok(arc) ,
None => Ok(arc),
}
}
}
@ -224,10 +247,11 @@ mod tests {
let mut cache = Cache::new(&cache_path);
let cal1 = CachedCalendar::new("shopping list".to_string(),
Url::parse("https://caldav.com/shopping").unwrap(),
SupportedComponents::TODO);
cache.insert_calendar(cal1).await.unwrap();
let _ = cache.create_calendar(
Url::parse("https://caldav.com/shopping").unwrap(),
"shopping list".to_string(),
SupportedComponents::TODO,
).await.unwrap();
cache.save_to_folder().unwrap();

View file

@ -156,11 +156,16 @@ impl CompleteCalendar for CachedCalendar {
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
use crate::{item::VersionTag,
traits::DavCalendar};
traits::DavCalendar,
resource::Resource};
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
#[async_trait]
impl DavCalendar for CachedCalendar {
fn new(name: String, resource: Resource, supported_components: SupportedComponents) -> Self {
crate::traits::CompleteCalendar::new(name, resource.url().clone(), supported_components)
}
async fn get_item_version_tags(&self) -> Result<HashMap<ItemId, VersionTag>, Box<dyn Error>> {
use crate::item::SyncStatus;

View file

@ -40,15 +40,6 @@ pub struct RemoteCalendar {
cached_version_tags: Mutex<Option<HashMap<ItemId, VersionTag>>>,
}
impl RemoteCalendar {
pub fn new(name: String, resource: Resource, supported_components: SupportedComponents) -> Self {
Self {
name, resource, supported_components,
cached_version_tags: Mutex::new(None),
}
}
}
#[async_trait]
impl BaseCalendar for RemoteCalendar {
fn name(&self) -> &str { &self.name }
@ -65,6 +56,14 @@ impl BaseCalendar for RemoteCalendar {
#[async_trait]
impl DavCalendar for RemoteCalendar {
fn new(name: String, resource: Resource, supported_components: SupportedComponents) -> Self {
Self {
name, resource, supported_components,
cached_version_tags: Mutex::new(None),
}
}
async fn get_item_version_tags(&self) -> Result<HashMap<ItemId, VersionTag>, Box<dyn Error>> {
if let Some(map) = &*self.cached_version_tags.lock().unwrap() {
log::debug!("Version tags are already cached.");

View file

@ -14,8 +14,10 @@ use crate::resource::Resource;
use crate::utils::{find_elem, find_elems};
use crate::calendar::remote_calendar::RemoteCalendar;
use crate::calendar::CalendarId;
use crate::calendar::SupportedComponents;
use crate::traits::CalDavSource;
use crate::traits::BaseCalendar;
use crate::traits::DavCalendar;
static DAVCLIENT_BODY: &str = r#"
@ -222,17 +224,17 @@ impl CalDavSource<RemoteCalendar> for Client {
};
}
async fn get_calendar(&self, id: &CalendarId) -> Option<Arc<Mutex<RemoteCalendar>>> {
self.cached_replies.lock().unwrap()
.calendars
.as_ref()
.and_then(|cals| cals.get(id))
.map(|cal| cal.clone())
}
}
async fn insert_calendar(&mut self, _new_calendar: RemoteCalendar) -> Result<Arc<Mutex<RemoteCalendar>>, Box<dyn Error>> {
async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents) -> Result<Arc<Mutex<RemoteCalendar>>, Box<dyn Error>> {
todo!();
}
}

View file

@ -9,6 +9,7 @@ use crate::traits::{CalDavSource, DavCalendar};
use crate::traits::CompleteCalendar;
use crate::item::SyncStatus;
use crate::calendar::SupportedComponents;
use crate::calendar::CalendarId;
/// A data source that combines two `CalDavSource`s (usually a server and a local cache), which is able to sync both sources.
/// This can be used for integration tests, where the remote source is mocked by a `Cache`.
@ -57,22 +58,27 @@ where
pub async fn sync(&mut self) -> Result<(), Box<dyn Error>> {
log::info!("Starting a sync.");
let mut handled_calendars = HashSet::new();
// Sync every remote calendar
let cals_remote = self.remote.get_calendars().await?;
for (cal_id, cal_remote) in cals_remote {
let cal_local = loop {
if let Some(cal) = self.local.get_calendar(&cal_id).await {
break cal;
}
let cal_local = self.get_or_insert_local_counterpart_calendar(&cal_id).await;
// This calendar does not exist locally yet, let's add it
log::error!("TODO: what name/SP should we choose?");
let new_calendar = T::new(String::from("new calendar"), cal_id.clone(), SupportedComponents::TODO);
if let Err(err) = Self::sync_calendar_pair(cal_local, cal_remote).await {
log::warn!("Unable to sync calendar {}: {}, skipping this time.", cal_id, err);
}
handled_calendars.insert(cal_id);
}
if let Err(err) = self.local.insert_calendar(new_calendar).await {
log::warn!("Unable to create local calendar {}: {}. Skipping it.", cal_id, err);
continue;
}
};
// Sync every local calendar that would not be in the remote yet
let cals_local = self.local.get_calendars().await?;
for (cal_id, cal_local) in cals_local {
if handled_calendars.contains(&cal_id) {
continue;
}
let cal_remote = self.get_or_insert_remote_counterpart_calendar(&cal_id).await;
if let Err(err) = Self::sync_calendar_pair(cal_local, cal_remote).await {
log::warn!("Unable to sync calendar {}: {}, skipping this time.", cal_id, err);
@ -83,6 +89,46 @@ where
}
async fn get_or_insert_local_counterpart_calendar(&mut self, cal_id: &CalendarId) -> Arc<Mutex<T>> {
loop {
if let Some(cal) = self.local.get_calendar(&cal_id).await {
break cal;
}
// This calendar does not exist locally yet, let's add it
log::debug!("Adding a local calendar {}", cal_id);
if let Err(err) = self.local.create_calendar(
cal_id.clone(),
String::from("new calendar"),
SupportedComponents::TODO,
).await {
log::warn!("Unable to create local calendar {}: {}. Skipping it.", cal_id, err);
continue;
}
}
}
async fn get_or_insert_remote_counterpart_calendar(&mut self, cal_id: &CalendarId) -> Arc<Mutex<U>> {
loop {
if let Some(cal) = self.remote.get_calendar(&cal_id).await {
break cal;
}
// This calendar does not exist in the remote yet, let's add it
log::debug!("Adding a remote calendar {}", cal_id);
if let Err(err) = self.remote.create_calendar(
cal_id.clone(),
String::from("new calendar"),
SupportedComponents::TODO,
).await {
log::warn!("Unable to create remote calendar {}: {}. Skipping it.", cal_id, err);
continue;
}
}
}
async fn sync_calendar_pair(cal_local: Arc<Mutex<T>>, cal_remote: Arc<Mutex<U>>) -> Result<(), Box<dyn Error>> {
let mut cal_remote = cal_remote.lock().unwrap();
let mut cal_local = cal_local.lock().unwrap();

View file

@ -10,6 +10,7 @@ use crate::item::ItemId;
use crate::item::VersionTag;
use crate::calendar::CalendarId;
use crate::calendar::SupportedComponents;
use crate::resource::Resource;
/// This trait must be implemented by data sources (either local caches or remote CalDAV clients)
#[async_trait]
@ -19,8 +20,9 @@ pub trait CalDavSource<T: BaseCalendar> {
async fn get_calendars(&self) -> Result<HashMap<CalendarId, Arc<Mutex<T>>>, Box<dyn Error>>;
/// Returns the calendar matching the ID
async fn get_calendar(&self, id: &CalendarId) -> Option<Arc<Mutex<T>>>;
/// Insert a calendar if it did not exist, and return it
async fn insert_calendar(&mut self, new_calendar: T) -> Result<Arc<Mutex<T>>, Box<dyn Error>>;
/// Create a calendar if it did not exist, and return it
async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents)
-> Result<Arc<Mutex<T>>, Box<dyn Error>>;
}
/// This trait contains functions that are common to all calendars
@ -55,6 +57,9 @@ pub trait BaseCalendar {
/// Functions availabe for calendars that are backed by a CalDAV server
#[async_trait]
pub trait DavCalendar : BaseCalendar {
/// Create a new calendar
fn new(name: String, resource: Resource, supported_components: SupportedComponents) -> Self;
/// Get the IDs and the version tags of every item in this calendar
async fn get_item_version_tags(&self) -> Result<HashMap<ItemId, VersionTag>, Box<dyn Error>>;

View file

@ -99,19 +99,14 @@ where
pub fn print_task(item: &Item) {
match item {
Item::Task(task) => {
let mut status = String::new();
if task.completed() {
status += "";
} else {
status += " ";
}
match task.sync_status() {
SyncStatus::NotSynced => { status += " "; },
SyncStatus::Synced(_) => { status += "="; },
SyncStatus::LocallyModified(_) => { status += "~"; },
SyncStatus::LocallyDeleted(_) => { status += "x"; },
}
println!(" {} {}\t{}", status, task.name(), task.id());
let completion = if task.completed() { "" } else { " " };
let sync = match task.sync_status() {
SyncStatus::NotSynced => ".",
SyncStatus::Synced(_) => "=",
SyncStatus::LocallyModified(_) => "~",
SyncStatus::LocallyDeleted(_) => "x",
};
println!(" {}{} {}\t{}", completion, sync, task.name(), task.id());
},
_ => return,
}