Better distinction ID/UID
This commit is contained in:
parent
56b86adf02
commit
2f7c14d0aa
13 changed files with 165 additions and 221 deletions
|
@ -3,13 +3,13 @@
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use chrono::{Utc};
|
use chrono::{Utc};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use kitchen_fridge::{client::Client, traits::CalDavSource};
|
use kitchen_fridge::{client::Client, traits::CalDavSource};
|
||||||
use kitchen_fridge::calendar::{CalendarId, SupportedComponents};
|
use kitchen_fridge::calendar::{CalendarId, SupportedComponents};
|
||||||
use kitchen_fridge::Item;
|
use kitchen_fridge::Item;
|
||||||
use kitchen_fridge::Task;
|
use kitchen_fridge::Task;
|
||||||
use kitchen_fridge::task::CompletionStatus;
|
use kitchen_fridge::task::CompletionStatus;
|
||||||
use kitchen_fridge::item::ItemId;
|
|
||||||
use kitchen_fridge::cache::Cache;
|
use kitchen_fridge::cache::Cache;
|
||||||
use kitchen_fridge::CalDavProvider;
|
use kitchen_fridge::CalDavProvider;
|
||||||
use kitchen_fridge::traits::BaseCalendar;
|
use kitchen_fridge::traits::BaseCalendar;
|
||||||
|
@ -100,7 +100,7 @@ async fn add_items_and_sync_again(provider: &mut CalDavProvider)
|
||||||
let changed_calendar_id: CalendarId = EXAMPLE_EXISTING_CALENDAR_URL.parse().unwrap();
|
let changed_calendar_id: CalendarId = EXAMPLE_EXISTING_CALENDAR_URL.parse().unwrap();
|
||||||
let new_task_name = "This is a new task we're adding as an example, with ÜTF-8 characters";
|
let new_task_name = "This is a new task we're adding as an example, with ÜTF-8 characters";
|
||||||
let new_task = Task::new(String::from(new_task_name), false, &changed_calendar_id);
|
let new_task = Task::new(String::from(new_task_name), false, &changed_calendar_id);
|
||||||
let new_id = new_task.id().clone();
|
let new_url = new_task.url().clone();
|
||||||
provider.local().get_calendar(&changed_calendar_id).await.unwrap()
|
provider.local().get_calendar(&changed_calendar_id).await.unwrap()
|
||||||
.lock().unwrap().add_item(Item::Task(new_task)).await.unwrap();
|
.lock().unwrap().add_item(Item::Task(new_task)).await.unwrap();
|
||||||
|
|
||||||
|
@ -112,20 +112,20 @@ async fn add_items_and_sync_again(provider: &mut CalDavProvider)
|
||||||
}
|
}
|
||||||
provider.local().save_to_folder().unwrap();
|
provider.local().save_to_folder().unwrap();
|
||||||
|
|
||||||
complete_item_and_sync_again(provider, &changed_calendar_id, &new_id).await;
|
complete_item_and_sync_again(provider, &changed_calendar_id, &new_url).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn complete_item_and_sync_again(
|
async fn complete_item_and_sync_again(
|
||||||
provider: &mut CalDavProvider,
|
provider: &mut CalDavProvider,
|
||||||
changed_calendar_id: &CalendarId,
|
changed_calendar_id: &CalendarId,
|
||||||
id_to_complete: &ItemId)
|
url_to_complete: &Url)
|
||||||
{
|
{
|
||||||
println!("\nNow, we'll mark this last task as completed, and run the sync again.");
|
println!("\nNow, we'll mark this last task as completed, and run the sync again.");
|
||||||
pause();
|
pause();
|
||||||
|
|
||||||
let completion_status = CompletionStatus::Completed(Some(Utc::now()));
|
let completion_status = CompletionStatus::Completed(Some(Utc::now()));
|
||||||
provider.local().get_calendar(changed_calendar_id).await.unwrap()
|
provider.local().get_calendar(changed_calendar_id).await.unwrap()
|
||||||
.lock().unwrap().get_item_by_id_mut(id_to_complete).await.unwrap()
|
.lock().unwrap().get_item_by_id_mut(url_to_complete).await.unwrap()
|
||||||
.unwrap_task_mut()
|
.unwrap_task_mut()
|
||||||
.set_completion_status(completion_status);
|
.set_completion_status(completion_status);
|
||||||
|
|
||||||
|
@ -136,13 +136,13 @@ async fn complete_item_and_sync_again(
|
||||||
}
|
}
|
||||||
provider.local().save_to_folder().unwrap();
|
provider.local().save_to_folder().unwrap();
|
||||||
|
|
||||||
remove_items_and_sync_again(provider, changed_calendar_id, id_to_complete).await;
|
remove_items_and_sync_again(provider, changed_calendar_id, url_to_complete).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn remove_items_and_sync_again(
|
async fn remove_items_and_sync_again(
|
||||||
provider: &mut CalDavProvider,
|
provider: &mut CalDavProvider,
|
||||||
changed_calendar_id: &CalendarId,
|
changed_calendar_id: &CalendarId,
|
||||||
id_to_remove: &ItemId)
|
id_to_remove: &Url)
|
||||||
{
|
{
|
||||||
println!("\nNow, we'll delete this last task, and run the sync again.");
|
println!("\nNow, we'll delete this last task, and run the sync again.");
|
||||||
pause();
|
pause();
|
||||||
|
|
|
@ -86,7 +86,7 @@ impl Cache {
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
Ok(cal) =>
|
Ok(cal) =>
|
||||||
data.calendars.insert(cal.id().clone(), Arc::new(Mutex::new(cal))),
|
data.calendars.insert(cal.url().clone(), Arc::new(Mutex::new(cal))),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -259,13 +259,13 @@ mod tests {
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut bucket_list = bucket_list.lock().unwrap();
|
let mut bucket_list = bucket_list.lock().unwrap();
|
||||||
let cal_id = bucket_list.id().clone();
|
let cal_url = bucket_list.url().clone();
|
||||||
bucket_list.add_item(Item::Task(Task::new(
|
bucket_list.add_item(Item::Task(Task::new(
|
||||||
String::from("Attend a concert of JS Bach"), false, &cal_id
|
String::from("Attend a concert of JS Bach"), false, &cal_url
|
||||||
))).await.unwrap();
|
))).await.unwrap();
|
||||||
|
|
||||||
bucket_list.add_item(Item::Task(Task::new(
|
bucket_list.add_item(Item::Task(Task::new(
|
||||||
String::from("Climb the Lighthouse of Alexandria"), true, &cal_id
|
String::from("Climb the Lighthouse of Alexandria"), true, &cal_url
|
||||||
))).await.unwrap();
|
))).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,12 @@ use std::error::Error;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use csscolorparser::Color;
|
use csscolorparser::Color;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::item::SyncStatus;
|
use crate::item::SyncStatus;
|
||||||
use crate::traits::{BaseCalendar, CompleteCalendar};
|
use crate::traits::{BaseCalendar, CompleteCalendar};
|
||||||
use crate::calendar::{CalendarId, SupportedComponents};
|
use crate::calendar::{CalendarId, SupportedComponents};
|
||||||
use crate::Item;
|
use crate::Item;
|
||||||
use crate::item::ItemId;
|
|
||||||
|
|
||||||
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
@ -23,14 +23,14 @@ use crate::mock_behaviour::MockBehaviour;
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct CachedCalendar {
|
pub struct CachedCalendar {
|
||||||
name: String,
|
name: String,
|
||||||
id: CalendarId,
|
url: Url,
|
||||||
supported_components: SupportedComponents,
|
supported_components: SupportedComponents,
|
||||||
color: Option<Color>,
|
color: Option<Color>,
|
||||||
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
mock_behaviour: Option<Arc<Mutex<MockBehaviour>>>,
|
mock_behaviour: Option<Arc<Mutex<MockBehaviour>>>,
|
||||||
|
|
||||||
items: HashMap<ItemId, Item>,
|
items: HashMap<Url, Item>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CachedCalendar {
|
impl CachedCalendar {
|
||||||
|
@ -65,7 +65,7 @@ impl CachedCalendar {
|
||||||
fn regular_add_or_update_item(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> {
|
fn regular_add_or_update_item(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> {
|
||||||
let ss_clone = item.sync_status().clone();
|
let ss_clone = item.sync_status().clone();
|
||||||
log::debug!("Adding or updating an item with {:?}", ss_clone);
|
log::debug!("Adding or updating an item with {:?}", ss_clone);
|
||||||
self.items.insert(item.id().clone(), item);
|
self.items.insert(item.url().clone(), item);
|
||||||
Ok(ss_clone)
|
Ok(ss_clone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,7 @@ impl CachedCalendar {
|
||||||
_ => item.set_sync_status(SyncStatus::random_synced()),
|
_ => item.set_sync_status(SyncStatus::random_synced()),
|
||||||
};
|
};
|
||||||
let ss_clone = item.sync_status().clone();
|
let ss_clone = item.sync_status().clone();
|
||||||
self.items.insert(item.id().clone(), item);
|
self.items.insert(item.url().clone(), item);
|
||||||
Ok(ss_clone)
|
Ok(ss_clone)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ impl CachedCalendar {
|
||||||
#[cfg(any(test, feature = "integration_tests"))]
|
#[cfg(any(test, feature = "integration_tests"))]
|
||||||
pub async fn has_same_observable_content_as(&self, other: &CachedCalendar) -> Result<bool, Box<dyn Error>> {
|
pub async fn has_same_observable_content_as(&self, other: &CachedCalendar) -> Result<bool, Box<dyn Error>> {
|
||||||
if self.name != other.name
|
if self.name != other.name
|
||||||
|| self.id != other.id
|
|| self.url != other.url
|
||||||
|| self.supported_components != other.supported_components
|
|| self.supported_components != other.supported_components
|
||||||
|| self.color != other.color
|
|| self.color != other.color
|
||||||
{
|
{
|
||||||
|
@ -119,35 +119,35 @@ impl CachedCalendar {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The non-async version of [`Self::get_item_ids`]
|
/// The non-async version of [`Self::get_item_ids`]
|
||||||
pub fn get_item_ids_sync(&self) -> Result<HashSet<ItemId>, Box<dyn Error>> {
|
pub fn get_item_ids_sync(&self) -> Result<HashSet<Url>, Box<dyn Error>> {
|
||||||
Ok(self.items.iter()
|
Ok(self.items.iter()
|
||||||
.map(|(id, _)| id.clone())
|
.map(|(url, _)| url.clone())
|
||||||
.collect()
|
.collect()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The non-async version of [`Self::get_items`]
|
/// The non-async version of [`Self::get_items`]
|
||||||
pub fn get_items_sync(&self) -> Result<HashMap<ItemId, &Item>, Box<dyn Error>> {
|
pub fn get_items_sync(&self) -> Result<HashMap<Url, &Item>, Box<dyn Error>> {
|
||||||
Ok(self.items.iter()
|
Ok(self.items.iter()
|
||||||
.map(|(id, item)| (id.clone(), item))
|
.map(|(url, item)| (url.clone(), item))
|
||||||
.collect()
|
.collect()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The non-async version of [`Self::get_item_by_id`]
|
/// The non-async version of [`Self::get_item_by_id`]
|
||||||
pub fn get_item_by_id_sync<'a>(&'a self, id: &ItemId) -> Option<&'a Item> {
|
pub fn get_item_by_id_sync<'a>(&'a self, id: &Url) -> Option<&'a Item> {
|
||||||
self.items.get(id)
|
self.items.get(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The non-async version of [`Self::get_item_by_id_mut`]
|
/// The non-async version of [`Self::get_item_by_id_mut`]
|
||||||
pub fn get_item_by_id_mut_sync<'a>(&'a mut self, id: &ItemId) -> Option<&'a mut Item> {
|
pub fn get_item_by_id_mut_sync<'a>(&'a mut self, id: &Url) -> Option<&'a mut Item> {
|
||||||
self.items.get_mut(id)
|
self.items.get_mut(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The non-async version of [`Self::add_item`]
|
/// The non-async version of [`Self::add_item`]
|
||||||
pub fn add_item_sync(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> {
|
pub fn add_item_sync(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> {
|
||||||
if self.items.contains_key(item.id()) {
|
if self.items.contains_key(item.url()) {
|
||||||
return Err(format!("Item {:?} cannot be added, it exists already", item.id()).into());
|
return Err(format!("Item {:?} cannot be added, it exists already", item.url()).into());
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "local_calendar_mocks_remote_calendars"))]
|
#[cfg(not(feature = "local_calendar_mocks_remote_calendars"))]
|
||||||
return self.regular_add_or_update_item(item);
|
return self.regular_add_or_update_item(item);
|
||||||
|
@ -158,8 +158,8 @@ impl CachedCalendar {
|
||||||
|
|
||||||
/// The non-async version of [`Self::update_item`]
|
/// The non-async version of [`Self::update_item`]
|
||||||
pub fn update_item_sync(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> {
|
pub fn update_item_sync(&mut self, item: Item) -> Result<SyncStatus, Box<dyn Error>> {
|
||||||
if self.items.contains_key(item.id()) == false {
|
if self.items.contains_key(item.url()) == false {
|
||||||
return Err(format!("Item {:?} cannot be updated, it does not already exist", item.id()).into());
|
return Err(format!("Item {:?} cannot be updated, it does not already exist", item.url()).into());
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "local_calendar_mocks_remote_calendars"))]
|
#[cfg(not(feature = "local_calendar_mocks_remote_calendars"))]
|
||||||
return self.regular_add_or_update_item(item);
|
return self.regular_add_or_update_item(item);
|
||||||
|
@ -169,7 +169,7 @@ impl CachedCalendar {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The non-async version of [`Self::mark_for_deletion`]
|
/// The non-async version of [`Self::mark_for_deletion`]
|
||||||
pub fn mark_for_deletion_sync(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>> {
|
pub fn mark_for_deletion_sync(&mut self, item_id: &Url) -> Result<(), Box<dyn Error>> {
|
||||||
match self.items.get_mut(item_id) {
|
match self.items.get_mut(item_id) {
|
||||||
None => Err("no item for this key".into()),
|
None => Err("no item for this key".into()),
|
||||||
Some(item) => {
|
Some(item) => {
|
||||||
|
@ -197,7 +197,7 @@ impl CachedCalendar {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The non-async version of [`Self::immediately_delete_item`]
|
/// The non-async version of [`Self::immediately_delete_item`]
|
||||||
pub fn immediately_delete_item_sync(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>> {
|
pub fn immediately_delete_item_sync(&mut self, item_id: &Url) -> Result<(), Box<dyn Error>> {
|
||||||
match self.items.remove(item_id) {
|
match self.items.remove(item_id) {
|
||||||
None => Err(format!("Item {} is absent from this calendar", item_id).into()),
|
None => Err(format!("Item {} is absent from this calendar", item_id).into()),
|
||||||
Some(_) => Ok(())
|
Some(_) => Ok(())
|
||||||
|
@ -213,8 +213,8 @@ impl BaseCalendar for CachedCalendar {
|
||||||
&self.name
|
&self.name
|
||||||
}
|
}
|
||||||
|
|
||||||
fn id(&self) -> &CalendarId {
|
fn url(&self) -> &Url {
|
||||||
&self.id
|
&self.url
|
||||||
}
|
}
|
||||||
|
|
||||||
fn supported_components(&self) -> SupportedComponents {
|
fn supported_components(&self) -> SupportedComponents {
|
||||||
|
@ -236,36 +236,36 @@ impl BaseCalendar for CachedCalendar {
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl CompleteCalendar for CachedCalendar {
|
impl CompleteCalendar for CachedCalendar {
|
||||||
fn new(name: String, id: CalendarId, supported_components: SupportedComponents, color: Option<Color>) -> Self {
|
fn new(name: String, url: CalendarId, supported_components: SupportedComponents, color: Option<Color>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name, id, supported_components, color,
|
name, url, supported_components, color,
|
||||||
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
||||||
mock_behaviour: None,
|
mock_behaviour: None,
|
||||||
items: HashMap::new(),
|
items: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_item_ids(&self) -> Result<HashSet<ItemId>, Box<dyn Error>> {
|
async fn get_item_ids(&self) -> Result<HashSet<Url>, Box<dyn Error>> {
|
||||||
self.get_item_ids_sync()
|
self.get_item_ids_sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_items(&self) -> Result<HashMap<ItemId, &Item>, Box<dyn Error>> {
|
async fn get_items(&self) -> Result<HashMap<Url, &Item>, Box<dyn Error>> {
|
||||||
self.get_items_sync()
|
self.get_items_sync()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_item_by_id<'a>(&'a self, id: &ItemId) -> Option<&'a Item> {
|
async fn get_item_by_id<'a>(&'a self, id: &Url) -> Option<&'a Item> {
|
||||||
self.get_item_by_id_sync(id)
|
self.get_item_by_id_sync(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
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: &Url) -> Option<&'a mut Item> {
|
||||||
self.get_item_by_id_mut_sync(id)
|
self.get_item_by_id_mut_sync(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn mark_for_deletion(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>> {
|
async fn mark_for_deletion(&mut self, item_id: &Url) -> Result<(), Box<dyn Error>> {
|
||||||
self.mark_for_deletion_sync(item_id)
|
self.mark_for_deletion_sync(item_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn immediately_delete_item(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>> {
|
async fn immediately_delete_item(&mut self, item_id: &Url) -> Result<(), Box<dyn Error>> {
|
||||||
self.immediately_delete_item_sync(item_id)
|
self.immediately_delete_item_sync(item_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -286,7 +286,7 @@ impl DavCalendar for CachedCalendar {
|
||||||
crate::traits::CompleteCalendar::new(name, resource.url().clone(), supported_components, color)
|
crate::traits::CompleteCalendar::new(name, resource.url().clone(), supported_components, color)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_item_version_tags(&self) -> Result<HashMap<ItemId, VersionTag>, Box<dyn Error>> {
|
async fn get_item_version_tags(&self) -> Result<HashMap<Url, VersionTag>, Box<dyn Error>> {
|
||||||
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
||||||
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_item_version_tags())?;
|
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_item_version_tags())?;
|
||||||
|
|
||||||
|
@ -307,14 +307,14 @@ impl DavCalendar for CachedCalendar {
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_item_by_id(&self, id: &ItemId) -> Result<Option<Item>, Box<dyn Error>> {
|
async fn get_item_by_id(&self, id: &Url) -> Result<Option<Item>, Box<dyn Error>> {
|
||||||
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
||||||
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_item_by_id())?;
|
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_get_item_by_id())?;
|
||||||
|
|
||||||
Ok(self.items.get(id).cloned())
|
Ok(self.items.get(id).cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_item(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>> {
|
async fn delete_item(&mut self, item_id: &Url) -> Result<(), Box<dyn Error>> {
|
||||||
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
#[cfg(feature = "local_calendar_mocks_remote_calendars")]
|
||||||
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_delete_item())?;
|
self.mock_behaviour.as_ref().map_or(Ok(()), |b| b.lock().unwrap().can_delete_item())?;
|
||||||
|
|
||||||
|
|
|
@ -5,13 +5,12 @@ use std::sync::Mutex;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use reqwest::{header::CONTENT_TYPE, header::CONTENT_LENGTH};
|
use reqwest::{header::CONTENT_TYPE, header::CONTENT_LENGTH};
|
||||||
use csscolorparser::Color;
|
use csscolorparser::Color;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::traits::BaseCalendar;
|
use crate::traits::BaseCalendar;
|
||||||
use crate::traits::DavCalendar;
|
use crate::traits::DavCalendar;
|
||||||
use crate::calendar::SupportedComponents;
|
use crate::calendar::SupportedComponents;
|
||||||
use crate::calendar::CalendarId;
|
|
||||||
use crate::item::Item;
|
use crate::item::Item;
|
||||||
use crate::item::ItemId;
|
|
||||||
use crate::item::VersionTag;
|
use crate::item::VersionTag;
|
||||||
use crate::item::SyncStatus;
|
use crate::item::SyncStatus;
|
||||||
use crate::resource::Resource;
|
use crate::resource::Resource;
|
||||||
|
@ -40,13 +39,13 @@ pub struct RemoteCalendar {
|
||||||
supported_components: SupportedComponents,
|
supported_components: SupportedComponents,
|
||||||
color: Option<Color>,
|
color: Option<Color>,
|
||||||
|
|
||||||
cached_version_tags: Mutex<Option<HashMap<ItemId, VersionTag>>>,
|
cached_version_tags: Mutex<Option<HashMap<Url, VersionTag>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl BaseCalendar for RemoteCalendar {
|
impl BaseCalendar for RemoteCalendar {
|
||||||
fn name(&self) -> &str { &self.name }
|
fn name(&self) -> &str { &self.name }
|
||||||
fn id(&self) -> &CalendarId { &self.resource.url() }
|
fn url(&self) -> &Url { &self.resource.url() }
|
||||||
fn supported_components(&self) -> crate::calendar::SupportedComponents {
|
fn supported_components(&self) -> crate::calendar::SupportedComponents {
|
||||||
self.supported_components
|
self.supported_components
|
||||||
}
|
}
|
||||||
|
@ -58,7 +57,7 @@ impl BaseCalendar for RemoteCalendar {
|
||||||
let ical_text = crate::ical::build_from(&item)?;
|
let ical_text = crate::ical::build_from(&item)?;
|
||||||
|
|
||||||
let response = reqwest::Client::new()
|
let response = reqwest::Client::new()
|
||||||
.put(item.id().as_url().clone())
|
.put(item.url().clone())
|
||||||
.header("If-None-Match", "*")
|
.header("If-None-Match", "*")
|
||||||
.header(CONTENT_TYPE, "text/calendar")
|
.header(CONTENT_TYPE, "text/calendar")
|
||||||
.header(CONTENT_LENGTH, ical_text.len())
|
.header(CONTENT_LENGTH, ical_text.len())
|
||||||
|
@ -73,7 +72,7 @@ impl BaseCalendar for RemoteCalendar {
|
||||||
|
|
||||||
let reply_hdrs = response.headers();
|
let reply_hdrs = response.headers();
|
||||||
match reply_hdrs.get("ETag") {
|
match reply_hdrs.get("ETag") {
|
||||||
None => Err(format!("No ETag in these response headers: {:?} (request was {:?})", reply_hdrs, item.id()).into()),
|
None => Err(format!("No ETag in these response headers: {:?} (request was {:?})", reply_hdrs, item.url()).into()),
|
||||||
Some(etag) => {
|
Some(etag) => {
|
||||||
let vtag_str = etag.to_str()?;
|
let vtag_str = etag.to_str()?;
|
||||||
let vtag = VersionTag::from(String::from(vtag_str));
|
let vtag = VersionTag::from(String::from(vtag_str));
|
||||||
|
@ -92,7 +91,7 @@ impl BaseCalendar for RemoteCalendar {
|
||||||
let ical_text = crate::ical::build_from(&item)?;
|
let ical_text = crate::ical::build_from(&item)?;
|
||||||
|
|
||||||
let request = reqwest::Client::new()
|
let request = reqwest::Client::new()
|
||||||
.put(item.id().as_url().clone())
|
.put(item.url().clone())
|
||||||
.header("If-Match", old_etag.as_str())
|
.header("If-Match", old_etag.as_str())
|
||||||
.header(CONTENT_TYPE, "text/calendar")
|
.header(CONTENT_TYPE, "text/calendar")
|
||||||
.header(CONTENT_LENGTH, ical_text.len())
|
.header(CONTENT_LENGTH, ical_text.len())
|
||||||
|
@ -107,7 +106,7 @@ impl BaseCalendar for RemoteCalendar {
|
||||||
|
|
||||||
let reply_hdrs = request.headers();
|
let reply_hdrs = request.headers();
|
||||||
match reply_hdrs.get("ETag") {
|
match reply_hdrs.get("ETag") {
|
||||||
None => Err(format!("No ETag in these response headers: {:?} (request was {:?})", reply_hdrs, item.id()).into()),
|
None => Err(format!("No ETag in these response headers: {:?} (request was {:?})", reply_hdrs, item.url()).into()),
|
||||||
Some(etag) => {
|
Some(etag) => {
|
||||||
let vtag_str = etag.to_str()?;
|
let vtag_str = etag.to_str()?;
|
||||||
let vtag = VersionTag::from(String::from(vtag_str));
|
let vtag = VersionTag::from(String::from(vtag_str));
|
||||||
|
@ -127,7 +126,7 @@ impl DavCalendar for RemoteCalendar {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn get_item_version_tags(&self) -> Result<HashMap<ItemId, VersionTag>, Box<dyn Error>> {
|
async fn get_item_version_tags(&self) -> Result<HashMap<Url, VersionTag>, Box<dyn Error>> {
|
||||||
if let Some(map) = &*self.cached_version_tags.lock().unwrap() {
|
if let Some(map) = &*self.cached_version_tags.lock().unwrap() {
|
||||||
log::debug!("Version tags are already cached.");
|
log::debug!("Version tags are already cached.");
|
||||||
return Ok(map.clone());
|
return Ok(map.clone());
|
||||||
|
@ -145,7 +144,7 @@ impl DavCalendar for RemoteCalendar {
|
||||||
continue;
|
continue;
|
||||||
},
|
},
|
||||||
Some(resource) => {
|
Some(resource) => {
|
||||||
ItemId::from(&resource)
|
resource.url().clone()
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -159,7 +158,7 @@ impl DavCalendar for RemoteCalendar {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
items.insert(item_id, version_tag);
|
items.insert(item_id.clone(), version_tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: the mutex cannot be locked during this whole async function, but it can safely be re-entrant (this will just waste an unnecessary request)
|
// Note: the mutex cannot be locked during this whole async function, but it can safely be re-entrant (this will just waste an unnecessary request)
|
||||||
|
@ -167,9 +166,9 @@ impl DavCalendar for RemoteCalendar {
|
||||||
Ok(items)
|
Ok(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_item_by_id(&self, id: &ItemId) -> Result<Option<Item>, Box<dyn Error>> {
|
async fn get_item_by_id(&self, id: &Url) -> Result<Option<Item>, Box<dyn Error>> {
|
||||||
let res = reqwest::Client::new()
|
let res = reqwest::Client::new()
|
||||||
.get(id.as_url().clone())
|
.get(id.clone())
|
||||||
.header(CONTENT_TYPE, "text/calendar")
|
.header(CONTENT_TYPE, "text/calendar")
|
||||||
.basic_auth(self.resource.username(), Some(self.resource.password()))
|
.basic_auth(self.resource.username(), Some(self.resource.password()))
|
||||||
.send()
|
.send()
|
||||||
|
@ -192,9 +191,9 @@ impl DavCalendar for RemoteCalendar {
|
||||||
Ok(Some(item))
|
Ok(Some(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn delete_item(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>> {
|
async fn delete_item(&mut self, item_id: &Url) -> Result<(), Box<dyn Error>> {
|
||||||
let del_response = reqwest::Client::new()
|
let del_response = reqwest::Client::new()
|
||||||
.delete(item_id.as_url().clone())
|
.delete(item_id.clone())
|
||||||
.basic_auth(self.resource.username(), Some(self.resource.password()))
|
.basic_auth(self.resource.username(), Some(self.resource.password()))
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
|
@ -216,7 +216,7 @@ impl Client {
|
||||||
|
|
||||||
let this_calendar = RemoteCalendar::new(display_name, this_calendar_url, supported_components, this_calendar_color);
|
let this_calendar = RemoteCalendar::new(display_name, this_calendar_url, supported_components, this_calendar_color);
|
||||||
log::info!("Found calendar {}", this_calendar.name());
|
log::info!("Found calendar {}", this_calendar.name());
|
||||||
calendars.insert(this_calendar.id().clone(), Arc::new(Mutex::new(this_calendar)));
|
calendars.insert(this_calendar.url().clone(), Arc::new(Mutex::new(this_calendar)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut replies = self.cached_replies.lock().unwrap();
|
let mut replies = self.cached_replies.lock().unwrap();
|
||||||
|
|
10
src/event.rs
10
src/event.rs
|
@ -2,15 +2,15 @@
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::item::ItemId;
|
|
||||||
use crate::item::SyncStatus;
|
use crate::item::SyncStatus;
|
||||||
|
|
||||||
/// 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.
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Event {
|
pub struct Event {
|
||||||
id: ItemId,
|
uid: String,
|
||||||
name: String,
|
name: String,
|
||||||
sync_status: SyncStatus,
|
sync_status: SyncStatus,
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,12 @@ impl Event {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> &ItemId {
|
pub fn url(&self) -> &Url {
|
||||||
&self.id
|
unimplemented!();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn uid(&self) -> &str {
|
pub fn uid(&self) -> &str {
|
||||||
unimplemented!()
|
&self.uid
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn name(&self) -> &str {
|
pub fn name(&self) -> &str {
|
||||||
|
|
|
@ -4,22 +4,22 @@ use std::error::Error;
|
||||||
|
|
||||||
use ical::parser::ical::component::{IcalCalendar, IcalEvent, IcalTodo};
|
use ical::parser::ical::component::{IcalCalendar, IcalEvent, IcalTodo};
|
||||||
use chrono::{DateTime, TimeZone, Utc};
|
use chrono::{DateTime, TimeZone, Utc};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::Item;
|
use crate::Item;
|
||||||
use crate::item::SyncStatus;
|
use crate::item::SyncStatus;
|
||||||
use crate::item::ItemId;
|
|
||||||
use crate::Task;
|
use crate::Task;
|
||||||
use crate::task::CompletionStatus;
|
use crate::task::CompletionStatus;
|
||||||
use crate::Event;
|
use crate::Event;
|
||||||
|
|
||||||
|
|
||||||
/// Parse an iCal file into the internal representation [`crate::Item`]
|
/// Parse an iCal file into the internal representation [`crate::Item`]
|
||||||
pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result<Item, Box<dyn Error>> {
|
pub fn parse(content: &str, item_url: Url, sync_status: SyncStatus) -> Result<Item, Box<dyn Error>> {
|
||||||
let mut reader = ical::IcalParser::new(content.as_bytes());
|
let mut reader = ical::IcalParser::new(content.as_bytes());
|
||||||
let parsed_item = match reader.next() {
|
let parsed_item = match reader.next() {
|
||||||
None => return Err(format!("Invalid iCal data to parse for item {}", item_id).into()),
|
None => return Err(format!("Invalid iCal data to parse for item {}", item_url).into()),
|
||||||
Some(item) => match item {
|
Some(item) => match item {
|
||||||
Err(err) => return Err(format!("Unable to parse iCal data for item {}: {}", item_id, err).into()),
|
Err(err) => return Err(format!("Unable to parse iCal data for item {}: {}", item_url, err).into()),
|
||||||
Ok(item) => item,
|
Ok(item) => item,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -80,15 +80,15 @@ pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result<
|
||||||
}
|
}
|
||||||
let name = match name {
|
let name = match name {
|
||||||
Some(name) => name,
|
Some(name) => name,
|
||||||
None => return Err(format!("Missing name for item {}", item_id).into()),
|
None => return Err(format!("Missing name for item {}", item_url).into()),
|
||||||
};
|
};
|
||||||
let uid = match uid {
|
let uid = match uid {
|
||||||
Some(uid) => uid,
|
Some(uid) => uid,
|
||||||
None => return Err(format!("Missing UID for item {}", item_id).into()),
|
None => return Err(format!("Missing UID for item {}", item_url).into()),
|
||||||
};
|
};
|
||||||
let last_modified = match last_modified {
|
let last_modified = match last_modified {
|
||||||
Some(dt) => dt,
|
Some(dt) => dt,
|
||||||
None => return Err(format!("Missing DTSTAMP for item {}, but this is required by RFC5545", item_id).into()),
|
None => return Err(format!("Missing DTSTAMP for item {}, but this is required by RFC5545", item_url).into()),
|
||||||
};
|
};
|
||||||
let completion_status = match completed {
|
let completion_status = match completed {
|
||||||
false => {
|
false => {
|
||||||
|
@ -100,7 +100,7 @@ pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result<
|
||||||
true => CompletionStatus::Completed(completion_date),
|
true => CompletionStatus::Completed(completion_date),
|
||||||
};
|
};
|
||||||
|
|
||||||
Item::Task(Task::new_with_parameters(name, uid, item_id, completion_status, sync_status, creation_date, last_modified, ical_prod_id, extra_parameters))
|
Item::Task(Task::new_with_parameters(name, uid, item_url, completion_status, sync_status, creation_date, last_modified, ical_prod_id, extra_parameters))
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -244,13 +244,13 @@ END:VCALENDAR
|
||||||
fn test_ical_parsing() {
|
fn test_ical_parsing() {
|
||||||
let version_tag = VersionTag::from(String::from("test-tag"));
|
let version_tag = VersionTag::from(String::from("test-tag"));
|
||||||
let sync_status = SyncStatus::Synced(version_tag);
|
let sync_status = SyncStatus::Synced(version_tag);
|
||||||
let item_id: ItemId = "http://some.id/for/testing".parse().unwrap();
|
let item_url: Url = "http://some.id/for/testing".parse().unwrap();
|
||||||
|
|
||||||
let item = parse(EXAMPLE_ICAL, item_id.clone(), sync_status.clone()).unwrap();
|
let item = parse(EXAMPLE_ICAL, item_url.clone(), sync_status.clone()).unwrap();
|
||||||
let task = item.unwrap_task();
|
let task = item.unwrap_task();
|
||||||
|
|
||||||
assert_eq!(task.name(), "Do not forget to do this");
|
assert_eq!(task.name(), "Do not forget to do this");
|
||||||
assert_eq!(task.id(), &item_id);
|
assert_eq!(task.url(), &item_url);
|
||||||
assert_eq!(task.uid(), "0633de27-8c32-42be-bcb8-63bc879c6185@some-domain.com");
|
assert_eq!(task.uid(), "0633de27-8c32-42be-bcb8-63bc879c6185@some-domain.com");
|
||||||
assert_eq!(task.completed(), false);
|
assert_eq!(task.completed(), false);
|
||||||
assert_eq!(task.completion_status(), &CompletionStatus::Uncompleted);
|
assert_eq!(task.completion_status(), &CompletionStatus::Uncompleted);
|
||||||
|
@ -262,9 +262,9 @@ END:VCALENDAR
|
||||||
fn test_completed_ical_parsing() {
|
fn test_completed_ical_parsing() {
|
||||||
let version_tag = VersionTag::from(String::from("test-tag"));
|
let version_tag = VersionTag::from(String::from("test-tag"));
|
||||||
let sync_status = SyncStatus::Synced(version_tag);
|
let sync_status = SyncStatus::Synced(version_tag);
|
||||||
let item_id: ItemId = "http://some.id/for/testing".parse().unwrap();
|
let item_url: Url = "http://some.id/for/testing".parse().unwrap();
|
||||||
|
|
||||||
let item = parse(EXAMPLE_ICAL_COMPLETED, item_id.clone(), sync_status.clone()).unwrap();
|
let item = parse(EXAMPLE_ICAL_COMPLETED, item_url.clone(), sync_status.clone()).unwrap();
|
||||||
let task = item.unwrap_task();
|
let task = item.unwrap_task();
|
||||||
|
|
||||||
assert_eq!(task.completed(), true);
|
assert_eq!(task.completed(), true);
|
||||||
|
@ -275,9 +275,9 @@ END:VCALENDAR
|
||||||
fn test_completed_without_date_ical_parsing() {
|
fn test_completed_without_date_ical_parsing() {
|
||||||
let version_tag = VersionTag::from(String::from("test-tag"));
|
let version_tag = VersionTag::from(String::from("test-tag"));
|
||||||
let sync_status = SyncStatus::Synced(version_tag);
|
let sync_status = SyncStatus::Synced(version_tag);
|
||||||
let item_id: ItemId = "http://some.id/for/testing".parse().unwrap();
|
let item_url: Url = "http://some.id/for/testing".parse().unwrap();
|
||||||
|
|
||||||
let item = parse(EXAMPLE_ICAL_COMPLETED_WITHOUT_A_COMPLETION_DATE, item_id.clone(), sync_status.clone()).unwrap();
|
let item = parse(EXAMPLE_ICAL_COMPLETED_WITHOUT_A_COMPLETION_DATE, item_url.clone(), sync_status.clone()).unwrap();
|
||||||
let task = item.unwrap_task();
|
let task = item.unwrap_task();
|
||||||
|
|
||||||
assert_eq!(task.completed(), true);
|
assert_eq!(task.completed(), true);
|
||||||
|
@ -288,9 +288,9 @@ END:VCALENDAR
|
||||||
fn test_multiple_items_in_ical() {
|
fn test_multiple_items_in_ical() {
|
||||||
let version_tag = VersionTag::from(String::from("test-tag"));
|
let version_tag = VersionTag::from(String::from("test-tag"));
|
||||||
let sync_status = SyncStatus::Synced(version_tag);
|
let sync_status = SyncStatus::Synced(version_tag);
|
||||||
let item_id: ItemId = "http://some.id/for/testing".parse().unwrap();
|
let item_url: Url = "http://some.id/for/testing".parse().unwrap();
|
||||||
|
|
||||||
let item = parse(EXAMPLE_MULTIPLE_ICAL, item_id.clone(), sync_status.clone());
|
let item = parse(EXAMPLE_MULTIPLE_ICAL, item_url.clone(), sync_status.clone());
|
||||||
assert!(item.is_err());
|
assert!(item.is_err());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
72
src/item.rs
72
src/item.rs
|
@ -1,17 +1,10 @@
|
||||||
//! CalDAV items (todo, events, journals...)
|
//! CalDAV items (todo, events, journals...)
|
||||||
// TODO: move Event and Task to nest them in crate::items::calendar::Calendar?
|
// TODO: move Event and Task to nest them in crate::items::calendar::Calendar?
|
||||||
|
|
||||||
use std::fmt::{Display, Formatter};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
|
|
||||||
use crate::resource::Resource;
|
|
||||||
use crate::calendar::CalendarId;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum Item {
|
pub enum Item {
|
||||||
|
@ -32,7 +25,7 @@ macro_rules! synthetise_common_getter {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Item {
|
impl Item {
|
||||||
synthetise_common_getter!(id, &ItemId);
|
synthetise_common_getter!(url, &Url);
|
||||||
synthetise_common_getter!(uid, &str);
|
synthetise_common_getter!(uid, &str);
|
||||||
synthetise_common_getter!(name, &str);
|
synthetise_common_getter!(name, &str);
|
||||||
synthetise_common_getter!(creation_date, Option<&DateTime<Utc>>);
|
synthetise_common_getter!(creation_date, Option<&DateTime<Utc>>);
|
||||||
|
@ -94,67 +87,6 @@ impl Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Hash)]
|
|
||||||
pub struct ItemId {
|
|
||||||
content: Url,
|
|
||||||
}
|
|
||||||
impl ItemId{
|
|
||||||
/// Generate a random ItemId.
|
|
||||||
pub fn random(parent_calendar: &CalendarId) -> Self {
|
|
||||||
let random = uuid::Uuid::new_v4().to_hyphenated().to_string();
|
|
||||||
let u = parent_calendar.join(&random).unwrap(/* this cannot panic since we've just created a string that is a valid URL */);
|
|
||||||
Self { content:u }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_url(&self) -> &Url {
|
|
||||||
&self.content
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<Url> for ItemId {
|
|
||||||
fn from(url: Url) -> Self {
|
|
||||||
Self { content: url }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl From<&Resource> for ItemId {
|
|
||||||
fn from(resource: &Resource) -> Self {
|
|
||||||
Self { content: resource.url().clone() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl FromStr for ItemId {
|
|
||||||
type Err = url::ParseError;
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
let u: Url = s.parse()?;
|
|
||||||
Ok(Self::from(u))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for ItemId {}
|
|
||||||
impl Display for ItemId {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
|
||||||
write!(f, "{}", self.content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used to support serde
|
|
||||||
impl Serialize for ItemId {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(self.content.as_str())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// Used to support serde
|
|
||||||
impl<'de> Deserialize<'de> for ItemId {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<ItemId, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
let u = Url::deserialize(deserializer)?;
|
|
||||||
Ok(ItemId{ content: u })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// A VersionTag is basically a CalDAV `ctag` or `etag`. Whenever it changes, this means the data has changed.
|
/// A VersionTag is basically a CalDAV `ctag` or `etag`. Whenever it changes, this means the data has changed.
|
||||||
|
|
|
@ -6,10 +6,11 @@ use std::error::Error;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::traits::{BaseCalendar, CalDavSource, DavCalendar};
|
use crate::traits::{BaseCalendar, CalDavSource, DavCalendar};
|
||||||
use crate::traits::CompleteCalendar;
|
use crate::traits::CompleteCalendar;
|
||||||
use crate::item::{ItemId, SyncStatus};
|
use crate::item::SyncStatus;
|
||||||
use crate::calendar::CalendarId;
|
use crate::calendar::CalendarId;
|
||||||
|
|
||||||
pub mod sync_progress;
|
pub mod sync_progress;
|
||||||
|
@ -400,7 +401,7 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn item_name(cal: &T, id: &ItemId) -> String {
|
async fn item_name(cal: &T, id: &Url) -> String {
|
||||||
cal.get_item_by_id(id).await.map(|item| item.name()).unwrap_or_default().to_string()
|
cal.get_item_by_id(id).await.map(|item| item.name()).unwrap_or_default().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
src/task.rs
23
src/task.rs
|
@ -4,10 +4,11 @@ use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use ical::property::Property;
|
use ical::property::Property;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::item::ItemId;
|
|
||||||
use crate::item::SyncStatus;
|
use crate::item::SyncStatus;
|
||||||
use crate::calendar::CalendarId;
|
use crate::calendar::CalendarId;
|
||||||
|
use crate::utils::random_url;
|
||||||
|
|
||||||
/// RFC5545 defines the completion as several optional fields, yet some combinations make no sense.
|
/// RFC5545 defines the completion as several optional fields, yet some combinations make no sense.
|
||||||
/// This enum provides an API that forbids such impossible combinations.
|
/// This enum provides an API that forbids such impossible combinations.
|
||||||
|
@ -33,10 +34,11 @@ impl CompletionStatus {
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct Task {
|
pub struct Task {
|
||||||
/// The task URL
|
/// The task URL
|
||||||
id: ItemId,
|
url: Url,
|
||||||
|
|
||||||
/// Persistent, globally unique identifier for the calendar component
|
/// Persistent, globally unique identifier for the calendar component
|
||||||
/// The [RFC](https://tools.ietf.org/html/rfc5545#page-117) recommends concatenating a timestamp with the server's domain name, but UUID are even better
|
/// The [RFC](https://tools.ietf.org/html/rfc5545#page-117) recommends concatenating a timestamp with the server's domain name.
|
||||||
|
/// UUID are even better so we'll generate them, but we have to support tasks from the server, that may have any arbitrary strings here.
|
||||||
uid: String,
|
uid: String,
|
||||||
|
|
||||||
/// The sync status of this item
|
/// The sync status of this item
|
||||||
|
@ -65,8 +67,8 @@ pub struct Task {
|
||||||
impl Task {
|
impl Task {
|
||||||
/// Create a brand new Task that is not on a server yet.
|
/// Create a brand new Task that is not on a server yet.
|
||||||
/// This will pick a new (random) task ID.
|
/// This will pick a new (random) task ID.
|
||||||
pub fn new(name: String, completed: bool, parent_calendar_id: &CalendarId) -> Self {
|
pub fn new(name: String, completed: bool, parent_calendar_url: &CalendarId) -> Self {
|
||||||
let new_item_id = ItemId::random(parent_calendar_id);
|
let new_url = random_url(parent_calendar_url);
|
||||||
let new_sync_status = SyncStatus::NotSynced;
|
let new_sync_status = SyncStatus::NotSynced;
|
||||||
let new_uid = Uuid::new_v4().to_hyphenated().to_string();
|
let new_uid = Uuid::new_v4().to_hyphenated().to_string();
|
||||||
let new_creation_date = Some(Utc::now());
|
let new_creation_date = Some(Utc::now());
|
||||||
|
@ -76,18 +78,18 @@ impl Task {
|
||||||
} else { CompletionStatus::Uncompleted };
|
} else { CompletionStatus::Uncompleted };
|
||||||
let ical_prod_id = crate::ical::default_prod_id();
|
let ical_prod_id = crate::ical::default_prod_id();
|
||||||
let extra_parameters = Vec::new();
|
let extra_parameters = Vec::new();
|
||||||
Self::new_with_parameters(name, new_uid, new_item_id, new_completion_status, new_sync_status, new_creation_date, new_last_modified, ical_prod_id, extra_parameters)
|
Self::new_with_parameters(name, new_uid, new_url, new_completion_status, new_sync_status, new_creation_date, new_last_modified, ical_prod_id, extra_parameters)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new Task instance, that may be synced on the server already
|
/// Create a new Task instance, that may be synced on the server already
|
||||||
pub fn new_with_parameters(name: String, uid: String, id: ItemId,
|
pub fn new_with_parameters(name: String, uid: String, new_url: Url,
|
||||||
completion_status: CompletionStatus,
|
completion_status: CompletionStatus,
|
||||||
sync_status: SyncStatus, creation_date: Option<DateTime<Utc>>, last_modified: DateTime<Utc>,
|
sync_status: SyncStatus, creation_date: Option<DateTime<Utc>>, last_modified: DateTime<Utc>,
|
||||||
ical_prod_id: String, extra_parameters: Vec<Property>,
|
ical_prod_id: String, extra_parameters: Vec<Property>,
|
||||||
) -> Self
|
) -> Self
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
id,
|
url: new_url,
|
||||||
uid,
|
uid,
|
||||||
name,
|
name,
|
||||||
completion_status,
|
completion_status,
|
||||||
|
@ -99,7 +101,7 @@ impl Task {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn id(&self) -> &ItemId { &self.id }
|
pub fn url(&self) -> &Url { &self.url }
|
||||||
pub fn uid(&self) -> &str { &self.uid }
|
pub fn uid(&self) -> &str { &self.uid }
|
||||||
pub fn name(&self) -> &str { &self.name }
|
pub fn name(&self) -> &str { &self.name }
|
||||||
pub fn completed(&self) -> bool { self.completion_status.is_completed() }
|
pub fn completed(&self) -> bool { self.completion_status.is_completed() }
|
||||||
|
@ -112,7 +114,8 @@ impl Task {
|
||||||
|
|
||||||
#[cfg(any(test, feature = "integration_tests"))]
|
#[cfg(any(test, feature = "integration_tests"))]
|
||||||
pub fn has_same_observable_content_as(&self, other: &Task) -> bool {
|
pub fn has_same_observable_content_as(&self, other: &Task) -> bool {
|
||||||
self.id == other.id
|
self.url == other.url
|
||||||
|
&& self.uid == other.uid
|
||||||
&& self.name == other.name
|
&& self.name == other.name
|
||||||
// sync status must be the same variant, but we ignore its embedded version tag
|
// sync status must be the same variant, but we ignore its embedded version tag
|
||||||
&& std::mem::discriminant(&self.sync_status) == std::mem::discriminant(&other.sync_status)
|
&& std::mem::discriminant(&self.sync_status) == std::mem::discriminant(&other.sync_status)
|
||||||
|
|
|
@ -6,10 +6,10 @@ use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use csscolorparser::Color;
|
use csscolorparser::Color;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::item::SyncStatus;
|
use crate::item::SyncStatus;
|
||||||
use crate::item::Item;
|
use crate::item::Item;
|
||||||
use crate::item::ItemId;
|
|
||||||
use crate::item::VersionTag;
|
use crate::item::VersionTag;
|
||||||
use crate::calendar::CalendarId;
|
use crate::calendar::CalendarId;
|
||||||
use crate::calendar::SupportedComponents;
|
use crate::calendar::SupportedComponents;
|
||||||
|
@ -40,8 +40,8 @@ pub trait BaseCalendar {
|
||||||
/// Returns the calendar name
|
/// Returns the calendar name
|
||||||
fn name(&self) -> &str;
|
fn name(&self) -> &str;
|
||||||
|
|
||||||
/// Returns the calendar unique ID
|
/// Returns the calendar URL
|
||||||
fn id(&self) -> &CalendarId;
|
fn url(&self) -> &Url;
|
||||||
|
|
||||||
/// 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;
|
||||||
|
@ -79,16 +79,16 @@ pub trait DavCalendar : BaseCalendar {
|
||||||
fn new(name: String, resource: Resource, supported_components: SupportedComponents, color: Option<Color>) -> Self;
|
fn new(name: String, resource: Resource, supported_components: SupportedComponents, color: Option<Color>) -> Self;
|
||||||
|
|
||||||
/// Get the IDs and the version tags of every item in this calendar
|
/// 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>>;
|
async fn get_item_version_tags(&self) -> Result<HashMap<Url, VersionTag>, Box<dyn Error>>;
|
||||||
|
|
||||||
/// Returns a particular item
|
/// Returns a particular item
|
||||||
async fn get_item_by_id(&self, id: &ItemId) -> Result<Option<Item>, Box<dyn Error>>;
|
async fn get_item_by_id(&self, id: &Url) -> Result<Option<Item>, Box<dyn Error>>;
|
||||||
|
|
||||||
/// Delete an item
|
/// Delete an item
|
||||||
async fn delete_item(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>>;
|
async fn delete_item(&mut self, item_id: &Url) -> Result<(), Box<dyn Error>>;
|
||||||
|
|
||||||
/// 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<Url>, Box<dyn Error>> {
|
||||||
let items = self.get_item_version_tags().await?;
|
let items = self.get_item_version_tags().await?;
|
||||||
Ok(items.iter()
|
Ok(items.iter()
|
||||||
.map(|(id, _tag)| id.clone())
|
.map(|(id, _tag)| id.clone())
|
||||||
|
@ -111,22 +111,22 @@ pub trait CompleteCalendar : BaseCalendar {
|
||||||
fn new(name: String, id: CalendarId, supported_components: SupportedComponents, color: Option<Color>) -> Self;
|
fn new(name: String, id: CalendarId, supported_components: SupportedComponents, color: Option<Color>) -> Self;
|
||||||
|
|
||||||
/// 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<Url>, Box<dyn Error>>;
|
||||||
|
|
||||||
/// Returns all items that this calendar contains
|
/// Returns all items that this calendar contains
|
||||||
async fn get_items(&self) -> Result<HashMap<ItemId, &Item>, Box<dyn Error>>;
|
async fn get_items(&self) -> Result<HashMap<Url, &Item>, Box<dyn Error>>;
|
||||||
|
|
||||||
/// Returns a particular item
|
/// Returns a particular item
|
||||||
async fn get_item_by_id<'a>(&'a self, id: &ItemId) -> Option<&'a Item>;
|
async fn get_item_by_id<'a>(&'a self, id: &Url) -> Option<&'a Item>;
|
||||||
|
|
||||||
/// 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: &Url) -> Option<&'a mut Item>;
|
||||||
|
|
||||||
/// Mark an item for deletion.
|
/// Mark an item for deletion.
|
||||||
/// This is required so that the upcoming sync will know it should also also delete this task from the server
|
/// This is required so that the upcoming sync will know it should also also delete this task from the server
|
||||||
/// (and then call [`CompleteCalendar::immediately_delete_item`] once it has been successfully deleted on the server)
|
/// (and then call [`CompleteCalendar::immediately_delete_item`] once it has been successfully deleted on the server)
|
||||||
async fn mark_for_deletion(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>>;
|
async fn mark_for_deletion(&mut self, item_id: &Url) -> Result<(), Box<dyn Error>>;
|
||||||
|
|
||||||
/// Immediately remove an item. See [`CompleteCalendar::mark_for_deletion`]
|
/// Immediately remove an item. See [`CompleteCalendar::mark_for_deletion`]
|
||||||
async fn immediately_delete_item(&mut self, item_id: &ItemId) -> Result<(), Box<dyn Error>>;
|
async fn immediately_delete_item(&mut self, item_id: &Url) -> Result<(), Box<dyn Error>>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use std::hash::Hash;
|
||||||
use std::io::{stdin, stdout, Read, Write};
|
use std::io::{stdin, stdout, Read, Write};
|
||||||
|
|
||||||
use minidom::Element;
|
use minidom::Element;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::traits::CompleteCalendar;
|
use crate::traits::CompleteCalendar;
|
||||||
use crate::traits::DavCalendar;
|
use crate::traits::DavCalendar;
|
||||||
|
@ -107,7 +108,7 @@ pub fn print_task(item: &Item) {
|
||||||
SyncStatus::LocallyModified(_) => "~",
|
SyncStatus::LocallyModified(_) => "~",
|
||||||
SyncStatus::LocallyDeleted(_) => "x",
|
SyncStatus::LocallyDeleted(_) => "x",
|
||||||
};
|
};
|
||||||
println!(" {}{} {}\t{}", completion, sync, task.name(), task.id());
|
println!(" {}{} {}\t{}", completion, sync, task.name(), task.url());
|
||||||
},
|
},
|
||||||
_ => return,
|
_ => return,
|
||||||
}
|
}
|
||||||
|
@ -148,3 +149,10 @@ pub fn pause() {
|
||||||
stdout.flush().unwrap();
|
stdout.flush().unwrap();
|
||||||
stdin().read_exact(&mut [0]).unwrap();
|
stdin().read_exact(&mut [0]).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Generate a random URL with a given prefix
|
||||||
|
pub fn random_url(parent_calendar: &Url) -> Url {
|
||||||
|
let random = uuid::Uuid::new_v4().to_hyphenated().to_string();
|
||||||
|
parent_calendar.join(&random).unwrap(/* this cannot panic since we've just created a string that is a valid URL */)
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|
||||||
|
@ -22,13 +23,13 @@ use kitchen_fridge::traits::CompleteCalendar;
|
||||||
use kitchen_fridge::traits::DavCalendar;
|
use kitchen_fridge::traits::DavCalendar;
|
||||||
use kitchen_fridge::cache::Cache;
|
use kitchen_fridge::cache::Cache;
|
||||||
use kitchen_fridge::Item;
|
use kitchen_fridge::Item;
|
||||||
use kitchen_fridge::item::ItemId;
|
|
||||||
use kitchen_fridge::item::SyncStatus;
|
use kitchen_fridge::item::SyncStatus;
|
||||||
use kitchen_fridge::Task;
|
use kitchen_fridge::Task;
|
||||||
use kitchen_fridge::task::CompletionStatus;
|
use kitchen_fridge::task::CompletionStatus;
|
||||||
use kitchen_fridge::calendar::cached_calendar::CachedCalendar;
|
use kitchen_fridge::calendar::cached_calendar::CachedCalendar;
|
||||||
use kitchen_fridge::provider::Provider;
|
use kitchen_fridge::provider::Provider;
|
||||||
use kitchen_fridge::mock_behaviour::MockBehaviour;
|
use kitchen_fridge::mock_behaviour::MockBehaviour;
|
||||||
|
use kitchen_fridge::utils::random_url;
|
||||||
|
|
||||||
pub enum LocatedState {
|
pub enum LocatedState {
|
||||||
/// Item does not exist yet or does not exist anymore
|
/// Item does not exist yet or does not exist anymore
|
||||||
|
@ -62,7 +63,7 @@ pub enum ChangeToApply {
|
||||||
|
|
||||||
|
|
||||||
pub struct ItemScenario {
|
pub struct ItemScenario {
|
||||||
id: ItemId,
|
url: Url,
|
||||||
initial_state: LocatedState,
|
initial_state: LocatedState,
|
||||||
local_changes_to_apply: Vec<ChangeToApply>,
|
local_changes_to_apply: Vec<ChangeToApply>,
|
||||||
remote_changes_to_apply: Vec<ChangeToApply>,
|
remote_changes_to_apply: Vec<ChangeToApply>,
|
||||||
|
@ -93,7 +94,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&first_cal),
|
url: random_url(&first_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: first_cal.clone(),
|
calendar: first_cal.clone(),
|
||||||
name: String::from("Task A"),
|
name: String::from("Task A"),
|
||||||
|
@ -111,7 +112,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&first_cal),
|
url: random_url(&first_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: first_cal.clone(),
|
calendar: first_cal.clone(),
|
||||||
name: String::from("Task B"),
|
name: String::from("Task B"),
|
||||||
|
@ -125,7 +126,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&first_cal),
|
url: random_url(&first_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: first_cal.clone(),
|
calendar: first_cal.clone(),
|
||||||
name: String::from("Task C"),
|
name: String::from("Task C"),
|
||||||
|
@ -139,7 +140,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&first_cal),
|
url: random_url(&first_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: first_cal.clone(),
|
calendar: first_cal.clone(),
|
||||||
name: String::from("Task D"),
|
name: String::from("Task D"),
|
||||||
|
@ -157,7 +158,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&first_cal),
|
url: random_url(&first_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: first_cal.clone(),
|
calendar: first_cal.clone(),
|
||||||
name: String::from("Task E"),
|
name: String::from("Task E"),
|
||||||
|
@ -175,7 +176,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&first_cal),
|
url: random_url(&first_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: first_cal.clone(),
|
calendar: first_cal.clone(),
|
||||||
name: String::from("Task F"),
|
name: String::from("Task F"),
|
||||||
|
@ -194,7 +195,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&second_cal),
|
url: random_url(&second_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: second_cal.clone(),
|
calendar: second_cal.clone(),
|
||||||
name: String::from("Task G"),
|
name: String::from("Task G"),
|
||||||
|
@ -212,7 +213,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&second_cal),
|
url: random_url(&second_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: second_cal.clone(),
|
calendar: second_cal.clone(),
|
||||||
name: String::from("Task H"),
|
name: String::from("Task H"),
|
||||||
|
@ -230,7 +231,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&second_cal),
|
url: random_url(&second_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: second_cal.clone(),
|
calendar: second_cal.clone(),
|
||||||
name: String::from("Task I"),
|
name: String::from("Task I"),
|
||||||
|
@ -249,7 +250,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&second_cal),
|
url: random_url(&second_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: second_cal.clone(),
|
calendar: second_cal.clone(),
|
||||||
name: String::from("Task J"),
|
name: String::from("Task J"),
|
||||||
|
@ -263,7 +264,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&second_cal),
|
url: random_url(&second_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: second_cal.clone(),
|
calendar: second_cal.clone(),
|
||||||
name: String::from("Task K"),
|
name: String::from("Task K"),
|
||||||
|
@ -281,7 +282,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&second_cal),
|
url: random_url(&second_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: second_cal.clone(),
|
calendar: second_cal.clone(),
|
||||||
name: String::from("Task L"),
|
name: String::from("Task L"),
|
||||||
|
@ -295,7 +296,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&second_cal),
|
url: random_url(&second_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: second_cal.clone(),
|
calendar: second_cal.clone(),
|
||||||
name: String::from("Task M"),
|
name: String::from("Task M"),
|
||||||
|
@ -313,7 +314,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&third_cal),
|
url: random_url(&third_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: third_cal.clone(),
|
calendar: third_cal.clone(),
|
||||||
name: String::from("Task N"),
|
name: String::from("Task N"),
|
||||||
|
@ -331,7 +332,7 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&third_cal),
|
url: random_url(&third_cal),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: third_cal.clone(),
|
calendar: third_cal.clone(),
|
||||||
name: String::from("Task O"),
|
name: String::from("Task O"),
|
||||||
|
@ -347,10 +348,10 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let id_p = ItemId::random(&third_cal);
|
let url_p = random_url(&third_cal);
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: id_p.clone(),
|
url: url_p.clone(),
|
||||||
initial_state: LocatedState::BothSynced( ItemState{
|
initial_state: LocatedState::BothSynced( ItemState{
|
||||||
calendar: third_cal.clone(),
|
calendar: third_cal.clone(),
|
||||||
name: String::from("Task P"),
|
name: String::from("Task P"),
|
||||||
|
@ -369,16 +370,16 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let id_q = ItemId::random(&third_cal);
|
let url_q = random_url(&third_cal);
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: id_q.clone(),
|
url: url_q.clone(),
|
||||||
initial_state: LocatedState::None,
|
initial_state: LocatedState::None,
|
||||||
local_changes_to_apply: Vec::new(),
|
local_changes_to_apply: Vec::new(),
|
||||||
remote_changes_to_apply: vec![ChangeToApply::Create(third_cal.clone(), Item::Task(
|
remote_changes_to_apply: vec![ChangeToApply::Create(third_cal.clone(), Item::Task(
|
||||||
Task::new_with_parameters(
|
Task::new_with_parameters(
|
||||||
String::from("Task Q, created on the server"),
|
String::from("Task Q, created on the server"),
|
||||||
id_q.to_string(), id_q,
|
url_q.to_string(), url_q,
|
||||||
CompletionStatus::Uncompleted,
|
CompletionStatus::Uncompleted,
|
||||||
SyncStatus::random_synced(), Some(Utc::now()), Utc::now(), "prod_id".to_string(), Vec::new() )
|
SyncStatus::random_synced(), Some(Utc::now()), Utc::now(), "prod_id".to_string(), Vec::new() )
|
||||||
))],
|
))],
|
||||||
|
@ -390,15 +391,15 @@ pub fn scenarii_basic() -> Vec<ItemScenario> {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let id_r = ItemId::random(&third_cal);
|
let url_r = random_url(&third_cal);
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: id_r.clone(),
|
url: url_r.clone(),
|
||||||
initial_state: LocatedState::None,
|
initial_state: LocatedState::None,
|
||||||
local_changes_to_apply: vec![ChangeToApply::Create(third_cal.clone(), Item::Task(
|
local_changes_to_apply: vec![ChangeToApply::Create(third_cal.clone(), Item::Task(
|
||||||
Task::new_with_parameters(
|
Task::new_with_parameters(
|
||||||
String::from("Task R, created locally"),
|
String::from("Task R, created locally"),
|
||||||
id_r.to_string(), id_r,
|
url_r.to_string(), url_r,
|
||||||
CompletionStatus::Uncompleted,
|
CompletionStatus::Uncompleted,
|
||||||
SyncStatus::NotSynced, Some(Utc::now()), Utc::now(), "prod_id".to_string(), Vec::new() )
|
SyncStatus::NotSynced, Some(Utc::now()), Utc::now(), "prod_id".to_string(), Vec::new() )
|
||||||
))],
|
))],
|
||||||
|
@ -423,7 +424,7 @@ pub fn scenarii_first_sync_to_local() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&cal1),
|
url: random_url(&cal1),
|
||||||
initial_state: LocatedState::Remote( ItemState{
|
initial_state: LocatedState::Remote( ItemState{
|
||||||
calendar: cal1.clone(),
|
calendar: cal1.clone(),
|
||||||
name: String::from("Task A1"),
|
name: String::from("Task A1"),
|
||||||
|
@ -441,7 +442,7 @@ pub fn scenarii_first_sync_to_local() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&cal2),
|
url: random_url(&cal2),
|
||||||
initial_state: LocatedState::Remote( ItemState{
|
initial_state: LocatedState::Remote( ItemState{
|
||||||
calendar: cal2.clone(),
|
calendar: cal2.clone(),
|
||||||
name: String::from("Task A2"),
|
name: String::from("Task A2"),
|
||||||
|
@ -459,7 +460,7 @@ pub fn scenarii_first_sync_to_local() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&cal1),
|
url: random_url(&cal1),
|
||||||
initial_state: LocatedState::Remote( ItemState{
|
initial_state: LocatedState::Remote( ItemState{
|
||||||
calendar: cal1.clone(),
|
calendar: cal1.clone(),
|
||||||
name: String::from("Task B1"),
|
name: String::from("Task B1"),
|
||||||
|
@ -487,7 +488,7 @@ pub fn scenarii_first_sync_to_server() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&cal3),
|
url: random_url(&cal3),
|
||||||
initial_state: LocatedState::Local( ItemState{
|
initial_state: LocatedState::Local( ItemState{
|
||||||
calendar: cal3.clone(),
|
calendar: cal3.clone(),
|
||||||
name: String::from("Task A3"),
|
name: String::from("Task A3"),
|
||||||
|
@ -505,7 +506,7 @@ pub fn scenarii_first_sync_to_server() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&cal4),
|
url: random_url(&cal4),
|
||||||
initial_state: LocatedState::Local( ItemState{
|
initial_state: LocatedState::Local( ItemState{
|
||||||
calendar: cal4.clone(),
|
calendar: cal4.clone(),
|
||||||
name: String::from("Task A4"),
|
name: String::from("Task A4"),
|
||||||
|
@ -523,7 +524,7 @@ pub fn scenarii_first_sync_to_server() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&cal3),
|
url: random_url(&cal3),
|
||||||
initial_state: LocatedState::Local( ItemState{
|
initial_state: LocatedState::Local( ItemState{
|
||||||
calendar: cal3.clone(),
|
calendar: cal3.clone(),
|
||||||
name: String::from("Task B3"),
|
name: String::from("Task B3"),
|
||||||
|
@ -551,7 +552,7 @@ pub fn scenarii_transient_task() -> Vec<ItemScenario> {
|
||||||
|
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: ItemId::random(&cal),
|
url: random_url(&cal),
|
||||||
initial_state: LocatedState::Local( ItemState{
|
initial_state: LocatedState::Local( ItemState{
|
||||||
calendar: cal.clone(),
|
calendar: cal.clone(),
|
||||||
name: String::from("A task, so that the calendar actually exists"),
|
name: String::from("A task, so that the calendar actually exists"),
|
||||||
|
@ -567,16 +568,16 @@ pub fn scenarii_transient_task() -> Vec<ItemScenario> {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let id_transient = ItemId::random(&cal);
|
let url_transient = random_url(&cal);
|
||||||
tasks.push(
|
tasks.push(
|
||||||
ItemScenario {
|
ItemScenario {
|
||||||
id: id_transient.clone(),
|
url: url_transient.clone(),
|
||||||
initial_state: LocatedState::None,
|
initial_state: LocatedState::None,
|
||||||
local_changes_to_apply: vec![
|
local_changes_to_apply: vec![
|
||||||
ChangeToApply::Create(cal, Item::Task(
|
ChangeToApply::Create(cal, Item::Task(
|
||||||
Task::new_with_parameters(
|
Task::new_with_parameters(
|
||||||
String::from("A transient task that will be deleted before the sync"),
|
String::from("A transient task that will be deleted before the sync"),
|
||||||
id_transient.to_string(), id_transient,
|
url_transient.to_string(), url_transient,
|
||||||
CompletionStatus::Uncompleted,
|
CompletionStatus::Uncompleted,
|
||||||
SyncStatus::NotSynced, Some(Utc::now()), Utc::now(),
|
SyncStatus::NotSynced, Some(Utc::now()), Utc::now(),
|
||||||
"prod_id".to_string(), Vec::new() )
|
"prod_id".to_string(), Vec::new() )
|
||||||
|
@ -637,8 +638,8 @@ async fn populate_test_provider(scenarii: &[ItemScenario], mock_behaviour: Arc<M
|
||||||
let new_item = Item::Task(
|
let new_item = Item::Task(
|
||||||
Task::new_with_parameters(
|
Task::new_with_parameters(
|
||||||
state.name.clone(),
|
state.name.clone(),
|
||||||
item.id.to_string(),
|
item.url.to_string(),
|
||||||
item.id.clone(),
|
item.url.clone(),
|
||||||
completion_status,
|
completion_status,
|
||||||
sync_status,
|
sync_status,
|
||||||
Some(now),
|
Some(now),
|
||||||
|
@ -676,12 +677,12 @@ async fn apply_changes_on_provider(provider: &mut Provider<Cache, CachedCalendar
|
||||||
|
|
||||||
let mut calendar_id = initial_calendar_id.clone();
|
let mut calendar_id = initial_calendar_id.clone();
|
||||||
for local_change in &item.local_changes_to_apply {
|
for local_change in &item.local_changes_to_apply {
|
||||||
calendar_id = Some(apply_change(provider.local(), calendar_id, &item.id, local_change, false).await);
|
calendar_id = Some(apply_change(provider.local(), calendar_id, &item.url, local_change, false).await);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut calendar_id = initial_calendar_id;
|
let mut calendar_id = initial_calendar_id;
|
||||||
for remote_change in &item.remote_changes_to_apply {
|
for remote_change in &item.remote_changes_to_apply {
|
||||||
calendar_id = Some(apply_change(provider.remote(), calendar_id, &item.id, remote_change, true).await);
|
calendar_id = Some(apply_change(provider.remote(), calendar_id, &item.url, remote_change, true).await);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -707,14 +708,14 @@ async fn get_or_insert_calendar(source: &mut Cache, id: &CalendarId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply a single change on a given source, and returns the calendar ID that was modified
|
/// Apply a single change on a given source, and returns the calendar ID that was modified
|
||||||
async fn apply_change<S, C>(source: &S, calendar_id: Option<CalendarId>, item_id: &ItemId, change: &ChangeToApply, is_remote: bool) -> CalendarId
|
async fn apply_change<S, C>(source: &S, calendar_id: Option<CalendarId>, item_url: &Url, change: &ChangeToApply, is_remote: bool) -> CalendarId
|
||||||
where
|
where
|
||||||
S: CalDavSource<C>,
|
S: CalDavSource<C>,
|
||||||
C: CompleteCalendar + DavCalendar, // in this test, we're using a calendar that mocks both kinds
|
C: CompleteCalendar + DavCalendar, // in this test, we're using a calendar that mocks both kinds
|
||||||
{
|
{
|
||||||
match calendar_id {
|
match calendar_id {
|
||||||
Some(cal) => {
|
Some(cal) => {
|
||||||
apply_changes_on_an_existing_item(source, &cal, item_id, change, is_remote).await;
|
apply_changes_on_an_existing_item(source, &cal, item_url, change, is_remote).await;
|
||||||
cal
|
cal
|
||||||
},
|
},
|
||||||
None => {
|
None => {
|
||||||
|
@ -723,14 +724,14 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn apply_changes_on_an_existing_item<S, C>(source: &S, calendar_id: &CalendarId, item_id: &ItemId, change: &ChangeToApply, is_remote: bool)
|
async fn apply_changes_on_an_existing_item<S, C>(source: &S, calendar_id: &CalendarId, item_url: &Url, change: &ChangeToApply, is_remote: bool)
|
||||||
where
|
where
|
||||||
S: CalDavSource<C>,
|
S: CalDavSource<C>,
|
||||||
C: CompleteCalendar + DavCalendar, // in this test, we're using a calendar that mocks both kinds
|
C: CompleteCalendar + DavCalendar, // in this test, we're using a calendar that mocks both kinds
|
||||||
{
|
{
|
||||||
let cal = source.get_calendar(calendar_id).await.unwrap();
|
let cal = source.get_calendar(calendar_id).await.unwrap();
|
||||||
let mut cal = cal.lock().unwrap();
|
let mut cal = cal.lock().unwrap();
|
||||||
let task = cal.get_item_by_id_mut(item_id).await.unwrap().unwrap_task_mut();
|
let task = cal.get_item_by_id_mut(item_url).await.unwrap().unwrap_task_mut();
|
||||||
|
|
||||||
match change {
|
match change {
|
||||||
ChangeToApply::Rename(new_name) => {
|
ChangeToApply::Rename(new_name) => {
|
||||||
|
@ -753,8 +754,8 @@ where
|
||||||
},
|
},
|
||||||
ChangeToApply::Remove => {
|
ChangeToApply::Remove => {
|
||||||
match is_remote {
|
match is_remote {
|
||||||
false => cal.mark_for_deletion(item_id).await.unwrap(),
|
false => cal.mark_for_deletion(item_url).await.unwrap(),
|
||||||
true => cal.delete_item(item_id).await.unwrap(),
|
true => cal.delete_item(item_url).await.unwrap(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
ChangeToApply::Create(_calendar_id, _item) => {
|
ChangeToApply::Create(_calendar_id, _item) => {
|
||||||
|
|
Loading…
Add table
Reference in a new issue