2021-02-25 00:53:50 +01:00
|
|
|
//! This module provides a local cache for CalDAV data
|
|
|
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use std::path::Path;
|
|
|
|
use std::error::Error;
|
2021-03-04 23:09:00 +01:00
|
|
|
use std::collections::HashMap;
|
2021-03-18 23:59:06 +01:00
|
|
|
use std::sync::{Arc, Mutex};
|
2021-03-21 00:11:35 +01:00
|
|
|
use std::ffi::OsStr;
|
2021-02-25 00:53:50 +01:00
|
|
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use async_trait::async_trait;
|
|
|
|
|
|
|
|
use crate::traits::CalDavSource;
|
2021-03-28 01:22:24 +01:00
|
|
|
use crate::traits::BaseCalendar;
|
2021-03-04 23:09:00 +01:00
|
|
|
use crate::traits::CompleteCalendar;
|
2021-03-01 23:39:16 +01:00
|
|
|
use crate::calendar::cached_calendar::CachedCalendar;
|
2021-03-05 23:32:42 +01:00
|
|
|
use crate::calendar::CalendarId;
|
2021-04-04 00:35:59 +02:00
|
|
|
use crate::calendar::SupportedComponents;
|
2021-02-25 00:53:50 +01:00
|
|
|
|
2021-04-13 23:32:07 +02:00
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
|
|
|
use crate::mock_behaviour::MockBehaviour;
|
|
|
|
|
2021-03-21 00:11:35 +01:00
|
|
|
const MAIN_FILE: &str = "data.json";
|
2021-02-25 00:53:50 +01:00
|
|
|
|
2021-03-21 00:11:35 +01:00
|
|
|
/// A CalDAV source that stores its item in a local folder
|
|
|
|
#[derive(Debug)]
|
2021-02-25 00:53:50 +01:00
|
|
|
pub struct Cache {
|
2021-03-21 00:11:35 +01:00
|
|
|
backing_folder: PathBuf,
|
2021-02-25 00:53:50 +01:00
|
|
|
data: CachedData,
|
2021-04-04 00:35:59 +02:00
|
|
|
|
2021-04-13 23:32:07 +02:00
|
|
|
/// In tests, we may add forced errors to this object
|
2021-04-04 00:35:59 +02:00
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
2021-04-13 23:32:07 +02:00
|
|
|
mock_behaviour: Option<Arc<Mutex<MockBehaviour>>>,
|
2021-02-25 00:53:50 +01:00
|
|
|
}
|
|
|
|
|
2021-03-21 00:11:35 +01:00
|
|
|
#[derive(Default, Debug, Serialize, Deserialize)]
|
2021-02-25 00:53:50 +01:00
|
|
|
struct CachedData {
|
2021-03-21 00:11:35 +01:00
|
|
|
#[serde(skip)]
|
2021-03-18 23:59:06 +01:00
|
|
|
calendars: HashMap<CalendarId, Arc<Mutex<CachedCalendar>>>,
|
2021-02-25 00:53:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Cache {
|
2021-04-04 00:35:59 +02:00
|
|
|
/// 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")]
|
2021-04-13 23:32:07 +02:00
|
|
|
pub fn set_mock_behaviour(&mut self, mock_behaviour: Option<Arc<Mutex<MockBehaviour>>>) {
|
|
|
|
self.mock_behaviour = mock_behaviour;
|
2021-04-04 00:35:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2021-03-21 00:11:35 +01:00
|
|
|
/// Get the path to the cache folder
|
|
|
|
pub fn cache_folder() -> PathBuf {
|
|
|
|
return PathBuf::from(String::from("~/.config/my-tasks/cache/"))
|
2021-02-25 00:53:50 +01:00
|
|
|
}
|
|
|
|
|
2021-03-21 00:11:35 +01:00
|
|
|
/// Initialize a cache from the content of a valid backing folder if it exists.
|
2021-02-28 18:02:01 +01:00
|
|
|
/// Returns an error otherwise
|
2021-03-21 00:11:35 +01:00
|
|
|
pub fn from_folder(folder: &Path) -> Result<Self, Box<dyn Error>> {
|
|
|
|
// Load shared data...
|
|
|
|
let main_file = folder.join(MAIN_FILE);
|
|
|
|
let mut data: CachedData = match std::fs::File::open(&main_file) {
|
2021-02-28 18:02:01 +01:00
|
|
|
Err(err) => {
|
2021-03-21 00:11:35 +01:00
|
|
|
return Err(format!("Unable to open file {:?}: {}", main_file, err).into());
|
2021-02-25 00:53:50 +01:00
|
|
|
},
|
|
|
|
Ok(file) => serde_json::from_reader(file)?,
|
|
|
|
};
|
|
|
|
|
2021-03-21 00:11:35 +01:00
|
|
|
// ...and every calendar
|
|
|
|
for entry in std::fs::read_dir(folder)? {
|
|
|
|
match entry {
|
|
|
|
Err(err) => {
|
|
|
|
log::error!("Unable to read dir: {:?}", err);
|
|
|
|
continue;
|
|
|
|
},
|
|
|
|
Ok(entry) => {
|
|
|
|
let cal_path = entry.path();
|
|
|
|
log::debug!("Considering {:?}", cal_path);
|
|
|
|
if cal_path.extension() == Some(OsStr::new("cal")) {
|
|
|
|
match Self::load_calendar(&cal_path) {
|
|
|
|
Err(err) => {
|
|
|
|
log::error!("Unable to load calendar {:?} from cache: {:?}", cal_path, err);
|
|
|
|
continue;
|
|
|
|
},
|
|
|
|
Ok(cal) =>
|
|
|
|
data.calendars.insert(cal.id().clone(), Arc::new(Mutex::new(cal))),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-25 00:53:50 +01:00
|
|
|
Ok(Self{
|
2021-03-21 00:11:35 +01:00
|
|
|
backing_folder: PathBuf::from(folder),
|
2021-02-25 00:53:50 +01:00
|
|
|
data,
|
2021-04-04 00:35:59 +02:00
|
|
|
|
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
2021-04-13 23:32:07 +02:00
|
|
|
mock_behaviour: None,
|
2021-02-25 00:53:50 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-03-21 00:11:35 +01:00
|
|
|
fn load_calendar(path: &Path) -> Result<CachedCalendar, Box<dyn Error>> {
|
|
|
|
let file = std::fs::File::open(&path)?;
|
|
|
|
Ok(serde_json::from_reader(file)?)
|
|
|
|
}
|
|
|
|
|
2021-02-25 00:53:50 +01:00
|
|
|
/// Initialize a cache with the default contents
|
2021-03-21 00:11:35 +01:00
|
|
|
pub fn new(folder_path: &Path) -> Self {
|
2021-02-25 00:53:50 +01:00
|
|
|
Self{
|
2021-03-21 00:11:35 +01:00
|
|
|
backing_folder: PathBuf::from(folder_path),
|
2021-02-25 00:53:50 +01:00
|
|
|
data: CachedData::default(),
|
2021-04-04 00:35:59 +02:00
|
|
|
|
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
2021-04-13 23:32:07 +02:00
|
|
|
mock_behaviour: None,
|
2021-02-25 00:53:50 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-21 00:11:35 +01:00
|
|
|
/// Store the current Cache to its backing folder
|
|
|
|
fn save_to_folder(&mut self) -> Result<(), std::io::Error> {
|
|
|
|
let folder = &self.backing_folder;
|
|
|
|
std::fs::create_dir_all(folder)?;
|
|
|
|
|
|
|
|
// Save the general data
|
|
|
|
let main_file_path = folder.join(MAIN_FILE);
|
|
|
|
let file = std::fs::File::create(&main_file_path)?;
|
|
|
|
serde_json::to_writer(file, &self.data)?;
|
|
|
|
|
|
|
|
// Save each calendar
|
|
|
|
for (cal_id, cal_mutex) in &self.data.calendars {
|
|
|
|
let file_name = sanitize_filename::sanitize(cal_id.as_str()) + ".cal";
|
|
|
|
let cal_file = folder.join(file_name);
|
|
|
|
let file = std::fs::File::create(&cal_file)?;
|
|
|
|
let cal = cal_mutex.lock().unwrap();
|
|
|
|
serde_json::to_writer(file, &*cal)?;
|
|
|
|
}
|
|
|
|
Ok(())
|
2021-02-25 00:53:50 +01:00
|
|
|
}
|
|
|
|
|
2021-02-28 18:02:01 +01:00
|
|
|
|
2021-03-04 23:09:00 +01:00
|
|
|
/// Compares two Caches to check they have the same current content
|
|
|
|
///
|
2021-04-03 17:42:55 +02:00
|
|
|
/// This is not a complete equality test: some attributes (sync status...) may differ
|
2021-04-04 01:02:37 +02:00
|
|
|
pub async fn has_same_observable_content_as(&self, other: &Self) -> Result<bool, Box<dyn Error>> {
|
2021-03-04 23:09:00 +01:00
|
|
|
let calendars_l = self.get_calendars().await?;
|
|
|
|
let calendars_r = other.get_calendars().await?;
|
|
|
|
|
2021-04-04 01:02:37 +02:00
|
|
|
if crate::utils::keys_are_the_same(&calendars_l, &calendars_r) == false {
|
2021-03-21 00:11:35 +01:00
|
|
|
log::debug!("Different keys for calendars");
|
2021-03-05 23:32:42 +01:00
|
|
|
return Ok(false);
|
|
|
|
}
|
|
|
|
|
2021-04-03 17:42:55 +02:00
|
|
|
for (calendar_id, cal_l) in calendars_l {
|
|
|
|
log::debug!("Comparing calendars {}", calendar_id);
|
2021-03-18 23:59:06 +01:00
|
|
|
let cal_l = cal_l.lock().unwrap();
|
2021-04-03 17:42:55 +02:00
|
|
|
let cal_r = match calendars_r.get(&calendar_id) {
|
2021-03-18 23:59:06 +01:00
|
|
|
Some(c) => c.lock().unwrap(),
|
2021-03-05 23:32:42 +01:00
|
|
|
None => return Err("should not happen, we've just tested keys are the same".into()),
|
|
|
|
};
|
|
|
|
|
2021-04-04 01:02:37 +02:00
|
|
|
// TODO: check calendars have the same names/ID/whatever
|
|
|
|
if cal_l.has_same_observable_content_as(&cal_r).await? == false {
|
|
|
|
log::debug!("Different calendars");
|
|
|
|
return Ok(false)
|
2021-03-28 01:22:24 +01:00
|
|
|
}
|
2021-04-04 01:02:37 +02:00
|
|
|
|
2021-03-28 01:22:24 +01:00
|
|
|
}
|
2021-03-04 23:09:00 +01:00
|
|
|
Ok(true)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 23:32:42 +01:00
|
|
|
|
2021-02-25 00:53:50 +01:00
|
|
|
|
|
|
|
#[async_trait]
|
2021-03-01 23:39:16 +01:00
|
|
|
impl CalDavSource<CachedCalendar> for Cache {
|
2021-03-18 23:59:06 +01:00
|
|
|
async fn get_calendars(&self) -> Result<HashMap<CalendarId, Arc<Mutex<CachedCalendar>>>, Box<dyn Error>> {
|
2021-04-13 23:32:07 +02:00
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
|
|
|
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_calendars())?;
|
|
|
|
|
2021-03-18 23:59:06 +01:00
|
|
|
Ok(self.data.calendars.iter()
|
|
|
|
.map(|(id, cal)| (id.clone(), cal.clone()))
|
|
|
|
.collect()
|
|
|
|
)
|
2021-02-25 00:53:50 +01:00
|
|
|
}
|
|
|
|
|
2021-03-21 19:27:55 +01:00
|
|
|
async fn get_calendar(&self, id: &CalendarId) -> Option<Arc<Mutex<CachedCalendar>>> {
|
|
|
|
self.data.calendars.get(id).map(|arc| arc.clone())
|
2021-02-26 17:55:23 +01:00
|
|
|
}
|
2021-03-31 08:32:28 +02:00
|
|
|
|
2021-04-04 00:35:59 +02:00
|
|
|
async fn create_calendar(&mut self, id: CalendarId, name: String, supported_components: SupportedComponents) -> Result<Arc<Mutex<CachedCalendar>>, Box<dyn Error>> {
|
2021-03-31 08:32:28 +02:00
|
|
|
log::debug!("Inserting local calendar {}", id);
|
2021-04-13 23:32:07 +02:00
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
|
|
|
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_create_calendar())?;
|
|
|
|
|
2021-04-04 00:35:59 +02:00
|
|
|
let new_calendar = CachedCalendar::new(name, id.clone(), supported_components);
|
2021-04-03 09:24:20 +02:00
|
|
|
let arc = Arc::new(Mutex::new(new_calendar));
|
2021-04-04 00:35:59 +02:00
|
|
|
|
|
|
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
2021-04-13 23:32:07 +02:00
|
|
|
if let Some(behaviour) = &self.mock_behaviour {
|
|
|
|
arc.lock().unwrap().set_mock_behaviour(Some(Arc::clone(behaviour)));
|
|
|
|
};
|
2021-04-04 00:35:59 +02:00
|
|
|
|
2021-04-03 09:24:20 +02:00
|
|
|
match self.data.calendars.insert(id, arc.clone()) {
|
2021-03-31 08:32:28 +02:00
|
|
|
Some(_) => Err("Attempt to insert calendar failed: there is alredy such a calendar.".into()),
|
2021-04-04 00:35:59 +02:00
|
|
|
None => Ok(arc),
|
2021-03-31 08:32:28 +02:00
|
|
|
}
|
|
|
|
}
|
2021-02-25 00:53:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
use url::Url;
|
|
|
|
use crate::calendar::SupportedComponents;
|
|
|
|
|
2021-03-21 00:11:35 +01:00
|
|
|
#[tokio::test]
|
|
|
|
async fn serde_cache() {
|
|
|
|
let _ = env_logger::builder().is_test(true).try_init();
|
|
|
|
|
|
|
|
let cache_path = PathBuf::from(String::from("test_cache/"));
|
2021-02-25 00:53:50 +01:00
|
|
|
|
|
|
|
let mut cache = Cache::new(&cache_path);
|
|
|
|
|
2021-04-04 00:35:59 +02:00
|
|
|
let _ = cache.create_calendar(
|
|
|
|
Url::parse("https://caldav.com/shopping").unwrap(),
|
|
|
|
"shopping list".to_string(),
|
|
|
|
SupportedComponents::TODO,
|
|
|
|
).await.unwrap();
|
2021-03-31 08:32:28 +02:00
|
|
|
|
2021-02-25 00:53:50 +01:00
|
|
|
|
2021-03-21 00:11:35 +01:00
|
|
|
cache.save_to_folder().unwrap();
|
|
|
|
|
|
|
|
let retrieved_cache = Cache::from_folder(&cache_path).unwrap();
|
|
|
|
assert_eq!(cache.backing_folder, retrieved_cache.backing_folder);
|
2021-04-04 01:02:37 +02:00
|
|
|
let test = cache.has_same_observable_content_as(&retrieved_cache).await;
|
2021-03-21 00:11:35 +01:00
|
|
|
println!("Equal? {:?}", test);
|
|
|
|
assert_eq!(test.unwrap(), true);
|
2021-02-25 00:53:50 +01:00
|
|
|
}
|
|
|
|
}
|