Major overhaul: more generics!
This commit is contained in:
parent
eaa6d8a61e
commit
caaddf910c
12 changed files with 314 additions and 163 deletions
|
@ -1,17 +1,37 @@
|
|||
use my_tasks::client::Client;
|
||||
use std::path::Path;
|
||||
|
||||
use my_tasks::{client::Client, traits::CalDavSource};
|
||||
use my_tasks::cache::Cache;
|
||||
use my_tasks::Provider;
|
||||
use my_tasks::settings::URL;
|
||||
use my_tasks::settings::USERNAME;
|
||||
use my_tasks::settings::PASSWORD;
|
||||
|
||||
|
||||
const CACHE_FILE: &str = "caldav_cache.json";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// This is just a function to silence "unused function" warning
|
||||
/*
|
||||
let cache_path = Path::new(CACHE_FILE);
|
||||
|
||||
let mut client = Client::new(URL, USERNAME, PASSWORD).unwrap();
|
||||
let calendars = client.get_calendars().await.unwrap();
|
||||
let _ = calendars.iter()
|
||||
.map(|cal| println!(" {}\t{}", cal.name(), cal.url().as_str()))
|
||||
.collect::<()>();
|
||||
let _ = client.get_tasks(&calendars[3].url()).await;
|
||||
let mut cache = match Cache::from_file(&cache_path) {
|
||||
Ok(cache) => cache,
|
||||
Err(err) => {
|
||||
log::warn!("Invalid cache file: {}. Using a default cache", err);
|
||||
Cache::new(&cache_path)
|
||||
}
|
||||
};
|
||||
let provider = Provider::new(client, cache);
|
||||
|
||||
let cals = provider.local().get_calendars().await.unwrap();
|
||||
println!("---- before sync -----");
|
||||
my_tasks::utils::print_calendar_list(cals);
|
||||
|
||||
provider.sync();
|
||||
println!("---- after sync -----");
|
||||
my_tasks::utils::print_calendar_list(cals);
|
||||
*/
|
||||
|
||||
}
|
||||
|
|
21
src/cache.rs
21
src/cache.rs
|
@ -11,7 +11,8 @@ use chrono::{DateTime, Utc};
|
|||
|
||||
use crate::traits::CalDavSource;
|
||||
use crate::traits::SyncSlave;
|
||||
use crate::Calendar;
|
||||
use crate::traits::PartialCalendar;
|
||||
use crate::calendar::cached_calendar::CachedCalendar;
|
||||
|
||||
|
||||
/// A CalDAV source that stores its item in a local file
|
||||
|
@ -23,7 +24,7 @@ pub struct Cache {
|
|||
|
||||
#[derive(Default, Debug, PartialEq, Serialize, Deserialize)]
|
||||
struct CachedData {
|
||||
calendars: Vec<Calendar>,
|
||||
calendars: Vec<CachedCalendar>,
|
||||
last_sync: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
|
@ -76,33 +77,33 @@ impl Cache {
|
|||
}
|
||||
|
||||
|
||||
pub fn add_calendar(&mut self, calendar: Calendar) {
|
||||
pub fn add_calendar(&mut self, calendar: CachedCalendar) {
|
||||
self.data.calendars.push(calendar);
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CalDavSource for Cache {
|
||||
async fn get_calendars(&self) -> Result<&Vec<Calendar>, Box<dyn Error>> {
|
||||
impl CalDavSource<CachedCalendar> for Cache {
|
||||
async fn get_calendars(&self) -> Result<&Vec<CachedCalendar>, Box<dyn Error>> {
|
||||
Ok(&self.data.calendars)
|
||||
}
|
||||
|
||||
async fn get_calendars_mut(&mut self) -> Result<Vec<&mut Calendar>, Box<dyn Error>> {
|
||||
async fn get_calendars_mut(&mut self) -> Result<Vec<&mut CachedCalendar>, Box<dyn Error>> {
|
||||
Ok(
|
||||
self.data.calendars.iter_mut()
|
||||
.collect()
|
||||
)
|
||||
}
|
||||
|
||||
async fn get_calendar(&self, url: Url) -> Option<&Calendar> {
|
||||
async fn get_calendar(&self, url: Url) -> Option<&CachedCalendar> {
|
||||
for cal in &self.data.calendars {
|
||||
if cal.url() == &url {
|
||||
return Some(cal);
|
||||
}
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
async fn get_calendar_mut(&mut self, url: Url) -> Option<&mut Calendar> {
|
||||
async fn get_calendar_mut(&mut self, url: Url) -> Option<&mut CachedCalendar> {
|
||||
for cal in &mut self.data.calendars {
|
||||
if cal.url() == &url {
|
||||
return Some(cal);
|
||||
|
@ -135,7 +136,7 @@ mod tests {
|
|||
|
||||
let mut cache = Cache::new(&cache_path);
|
||||
|
||||
let cal1 = Calendar::new("shopping list".to_string(),
|
||||
let cal1 = CachedCalendar::new("shopping list".to_string(),
|
||||
Url::parse("https://caldav.com/shopping").unwrap(),
|
||||
SupportedComponents::TODO);
|
||||
cache.add_calendar(cal1);
|
||||
|
|
124
src/calendar/cached_calendar.rs
Normal file
124
src/calendar/cached_calendar.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
use std::convert::TryFrom;
|
||||
use std::error::Error;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use url::Url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::traits::{PartialCalendar, CompleteCalendar};
|
||||
use crate::calendar::{SupportedComponents, SearchFilter};
|
||||
use crate::Item;
|
||||
use crate::item::ItemId;
|
||||
|
||||
|
||||
/// A calendar used by the [`cache`](crate::cache) module
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct CachedCalendar {
|
||||
name: String,
|
||||
url: Url,
|
||||
supported_components: SupportedComponents,
|
||||
|
||||
items: Vec<Item>,
|
||||
deleted_items: BTreeMap<DateTime<Utc>, ItemId>,
|
||||
}
|
||||
|
||||
impl CachedCalendar {
|
||||
/// Create a new calendar
|
||||
pub fn new(name: String, url: Url, supported_components: SupportedComponents) -> Self {
|
||||
Self {
|
||||
name, url, supported_components,
|
||||
items: Vec::new(),
|
||||
deleted_items: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the list of tasks that this calendar contains
|
||||
pub fn get_tasks(&self) -> HashMap<ItemId, &Item> {
|
||||
self.get_tasks_modified_since(None)
|
||||
}
|
||||
/// Returns the tasks that have been last-modified after `since`
|
||||
pub fn get_tasks_modified_since(&self, since: Option<DateTime<Utc>>) -> HashMap<ItemId, &Item> {
|
||||
self.get_items_modified_since(since, Some(SearchFilter::Tasks))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialCalendar for CachedCalendar {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn url(&self) -> &Url {
|
||||
&self.url
|
||||
}
|
||||
|
||||
fn supported_components(&self) -> SupportedComponents {
|
||||
self.supported_components
|
||||
}
|
||||
|
||||
fn add_item(&mut self, item: Item) {
|
||||
self.items.push(item);
|
||||
}
|
||||
|
||||
fn delete_item(&mut self, item_id: &ItemId) {
|
||||
self.items.retain(|i| i.id() != item_id);
|
||||
self.deleted_items.insert(Utc::now(), item_id.clone());
|
||||
}
|
||||
|
||||
fn get_items_modified_since(&self, since: Option<DateTime<Utc>>, filter: Option<SearchFilter>) -> HashMap<ItemId, &Item> {
|
||||
let filter = filter.unwrap_or_default();
|
||||
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for item in &self.items {
|
||||
match since {
|
||||
None => (),
|
||||
Some(since) => if item.last_modified() < since {
|
||||
continue;
|
||||
},
|
||||
}
|
||||
|
||||
match filter {
|
||||
SearchFilter::Tasks => {
|
||||
if item.is_task() == false {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
map.insert(item.id().clone(), item);
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
fn get_item_by_id_mut(&mut self, id: &ItemId) -> Option<&mut Item> {
|
||||
for item in &mut self.items {
|
||||
if item.id() == id {
|
||||
return Some(item);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
fn find_missing_items_compared_to(&self, other: &dyn PartialCalendar) -> Vec<ItemId> {
|
||||
unimplemented!("todo");
|
||||
}
|
||||
}
|
||||
|
||||
impl CompleteCalendar for CachedCalendar {
|
||||
/// Returns the items that have been deleted after `since`
|
||||
fn get_items_deleted_since(&self, since: DateTime<Utc>) -> Vec<ItemId> {
|
||||
self.deleted_items.range(since..)
|
||||
.map(|(_key, value)| value.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the list of items that this calendar contains
|
||||
fn get_items(&self) -> HashMap<ItemId, &Item> {
|
||||
self.get_items_modified_since(None, None)
|
||||
}
|
||||
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
pub mod cached_calendar;
|
||||
|
||||
use std::convert::TryFrom;
|
||||
use std::error::Error;
|
||||
use std::collections::HashMap;
|
||||
|
@ -66,116 +68,3 @@ impl Default for SearchFilter {
|
|||
SearchFilter::All
|
||||
}
|
||||
}
|
||||
|
||||
/// A Caldav Calendar
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Calendar {
|
||||
name: String,
|
||||
url: Url,
|
||||
supported_components: SupportedComponents,
|
||||
|
||||
items: Vec<Item>,
|
||||
deleted_items: BTreeMap<DateTime<Utc>, ItemId>,
|
||||
}
|
||||
|
||||
impl Calendar {
|
||||
/// Create a new calendar
|
||||
pub fn new(name: String, url: Url, supported_components: SupportedComponents) -> Self {
|
||||
Self {
|
||||
name, url, supported_components,
|
||||
items: Vec::new(),
|
||||
deleted_items: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the calendar name
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
/// Returns the calendar URL
|
||||
pub fn url(&self) -> &Url {
|
||||
&self.url
|
||||
}
|
||||
|
||||
/// Returns whether this calDAV calendar supports to-do items
|
||||
pub fn supports_todo(&self) -> bool {
|
||||
self.supported_components.contains(SupportedComponents::TODO)
|
||||
}
|
||||
|
||||
/// Returns whether this calDAV calendar supports calendar items
|
||||
pub fn supports_events(&self) -> bool {
|
||||
self.supported_components.contains(SupportedComponents::EVENT)
|
||||
}
|
||||
|
||||
/// Add an item into this calendar
|
||||
pub fn add_item(&mut self, item: Item) {
|
||||
self.items.push(item);
|
||||
}
|
||||
|
||||
/// Remove an item from this calendar
|
||||
pub fn delete_item(&mut self, item_id: &ItemId) {
|
||||
self.items.retain(|i| i.id() != item_id);
|
||||
self.deleted_items.insert(Utc::now(), item_id.clone());
|
||||
}
|
||||
|
||||
/// Returns the list of items that this calendar contains
|
||||
pub fn get_items(&self) -> HashMap<ItemId, &Item> {
|
||||
self.get_items_modified_since(None, None)
|
||||
}
|
||||
/// Returns the items that have been last-modified after `since`
|
||||
pub fn get_items_modified_since(&self, since: Option<DateTime<Utc>>, filter: Option<SearchFilter>) -> HashMap<ItemId, &Item> {
|
||||
let filter = filter.unwrap_or_default();
|
||||
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for item in &self.items {
|
||||
match since {
|
||||
None => (),
|
||||
Some(since) => if item.last_modified() < since {
|
||||
continue;
|
||||
},
|
||||
}
|
||||
|
||||
match filter {
|
||||
SearchFilter::Tasks => {
|
||||
if item.is_task() == false {
|
||||
continue;
|
||||
}
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
map.insert(item.id().clone(), item);
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
/// Returns the items that have been deleted after `since`
|
||||
pub fn get_items_deleted_since(&self, since: DateTime<Utc>) -> Vec<ItemId> {
|
||||
self.deleted_items.range(since..)
|
||||
.map(|(_key, value)| value.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns a particular item
|
||||
pub fn get_item_by_id_mut(&mut self, id: &ItemId) -> Option<&mut Item> {
|
||||
for item in &mut self.items {
|
||||
if item.id() == id {
|
||||
return Some(item);
|
||||
}
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
|
||||
/// Returns the list of tasks that this calendar contains
|
||||
pub fn get_tasks(&self) -> HashMap<ItemId, &Item> {
|
||||
self.get_tasks_modified_since(None)
|
||||
}
|
||||
/// Returns the tasks that have been last-modified after `since`
|
||||
pub fn get_tasks_modified_since(&self, since: Option<DateTime<Utc>>) -> HashMap<ItemId, &Item> {
|
||||
self.get_items_modified_since(since, Some(SearchFilter::Tasks))
|
||||
}
|
||||
}
|
||||
|
|
38
src/calendar/remote_calendar.rs
Normal file
38
src/calendar/remote_calendar.rs
Normal file
|
@ -0,0 +1,38 @@
|
|||
use crate::traits::PartialCalendar;
|
||||
|
||||
/// A CalDAV calendar created by a [`Client`](crate::client::Client).
|
||||
pub struct RemoteCalendar {
|
||||
name: String,
|
||||
url: Url,
|
||||
supported_components: SupportedComponents
|
||||
}
|
||||
|
||||
impl PartialCalendar for RemoteCalendar {
|
||||
fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn supported_components(&self) -> crate::calendar::SupportedComponents {
|
||||
self.supported_components
|
||||
}
|
||||
|
||||
fn get_items_modified_since(&self, since: Option<DateTime<Utc>>, filter: Option<crate::calendar::SearchFilter>)
|
||||
-> HashMap<ItemId, &Item>
|
||||
{
|
||||
log::error!("Not implemented");
|
||||
HashMap::new()
|
||||
}
|
||||
|
||||
fn get_item_by_id_mut(&mut self, id: &ItemId) -> Option<&mut Item> {
|
||||
log::error!("Not implemented");
|
||||
None
|
||||
}
|
||||
|
||||
fn add_item(&mut self, item: Item) {
|
||||
log::error!("Not implemented");
|
||||
}
|
||||
|
||||
fn delete_item(&mut self, item_id: &ItemId) {
|
||||
log::error!("Not implemented");
|
||||
}
|
||||
}
|
|
@ -2,14 +2,19 @@
|
|||
|
||||
use std::error::Error;
|
||||
use std::convert::TryFrom;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use reqwest::Method;
|
||||
use reqwest::header::CONTENT_TYPE;
|
||||
use minidom::Element;
|
||||
use url::Url;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::utils::{find_elem, find_elems};
|
||||
use crate::calendar::Calendar;
|
||||
use crate::calendar::cached_calendar::CachedCalendar;
|
||||
use crate::traits::PartialCalendar;
|
||||
use crate::traits::CalDavSource;
|
||||
|
||||
|
||||
static DAVCLIENT_BODY: &str = r#"
|
||||
<d:propfind xmlns:d="DAV:">
|
||||
|
@ -69,7 +74,7 @@ pub struct Client {
|
|||
|
||||
principal: Option<Url>,
|
||||
calendar_home_set: Option<Url>,
|
||||
calendars: Option<Vec<Calendar>>,
|
||||
calendars: Option<Vec<CachedCalendar>>,
|
||||
}
|
||||
|
||||
impl Client {
|
||||
|
@ -148,7 +153,7 @@ impl Client {
|
|||
}
|
||||
|
||||
/// Return the list of calendars, or fetch from server if not known yet
|
||||
pub async fn get_calendars(&mut self) -> Result<Vec<Calendar>, Box<dyn Error>> {
|
||||
pub async fn get_calendars(&mut self) -> Result<Vec<CachedCalendar>, Box<dyn Error>> {
|
||||
if let Some(c) = &self.calendars {
|
||||
return Ok(c.to_vec());
|
||||
}
|
||||
|
@ -206,7 +211,7 @@ impl Client {
|
|||
},
|
||||
Ok(sc) => sc,
|
||||
};
|
||||
let this_calendar = Calendar::new(display_name, this_calendar_url, supported_components);
|
||||
let this_calendar = CachedCalendar::new(display_name, this_calendar_url, supported_components);
|
||||
log::info!("Found calendar {}", this_calendar.name());
|
||||
calendars.push(this_calendar);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
pub mod traits;
|
||||
|
||||
pub mod calendar;
|
||||
pub use calendar::Calendar;
|
||||
pub use calendar::cached_calendar::CachedCalendar;
|
||||
mod item;
|
||||
pub use item::Item;
|
||||
mod task;
|
||||
|
|
|
@ -1,39 +1,49 @@
|
|||
//! This modules abstracts data sources and merges them in a single virtual one
|
||||
|
||||
use std::error::Error;
|
||||
use std::{error::Error, marker::PhantomData};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::traits::CalDavSource;
|
||||
use crate::traits::{CalDavSource, CompleteCalendar};
|
||||
use crate::traits::SyncSlave;
|
||||
use crate::Calendar;
|
||||
use crate::traits::PartialCalendar;
|
||||
use crate::calendar::cached_calendar::CachedCalendar;
|
||||
use crate::Item;
|
||||
use crate::item::ItemId;
|
||||
|
||||
|
||||
/// A data source that combines two `CalDavSources` (usually a server and a local cache), which is able to sync both sources.
|
||||
pub struct Provider<S, L>
|
||||
pub struct Provider<L, T, S, U>
|
||||
where
|
||||
S: CalDavSource,
|
||||
L: CalDavSource + SyncSlave,
|
||||
L: CalDavSource<T> + SyncSlave,
|
||||
T: CompleteCalendar,
|
||||
S: CalDavSource<U>,
|
||||
U: PartialCalendar,
|
||||
{
|
||||
/// The remote server
|
||||
server: S,
|
||||
/// The local cache
|
||||
local: L,
|
||||
|
||||
phantom_t: PhantomData<T>,
|
||||
phantom_u: PhantomData<U>,
|
||||
}
|
||||
|
||||
impl<S,L> Provider<S, L>
|
||||
impl<L, T, S, U> Provider<L, T, S, U>
|
||||
where
|
||||
S: CalDavSource,
|
||||
L: CalDavSource + SyncSlave,
|
||||
L: CalDavSource<T> + SyncSlave,
|
||||
T: CompleteCalendar,
|
||||
S: CalDavSource<U>,
|
||||
U: PartialCalendar,
|
||||
{
|
||||
/// Create a provider.
|
||||
///
|
||||
/// `server` is usually a [`Client`](crate::client::Client), `local` is usually a [`Cache`](crate::cache::Cache).
|
||||
/// However, both can be interchangeable. The only difference is that `server` always wins in case of a sync conflict
|
||||
pub fn new(server: S, local: L) -> Self {
|
||||
Self { server, local }
|
||||
Self { server, local,
|
||||
phantom_t: PhantomData, phantom_u: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the data source described as the `server`
|
||||
|
@ -62,9 +72,9 @@ where
|
|||
Some(cal) => cal,
|
||||
};
|
||||
|
||||
let server_mod = cal_server.get_tasks_modified_since(last_sync);
|
||||
let server_mod = cal_server.get_items_modified_since(last_sync, None);
|
||||
let server_del = match last_sync {
|
||||
Some(date) => cal_server.get_items_deleted_since(date),
|
||||
Some(date) => cal_server.find_missing_items_compared_to(cal_local),
|
||||
None => Vec::new(),
|
||||
};
|
||||
let local_del = match last_sync {
|
||||
|
@ -91,7 +101,7 @@ where
|
|||
|
||||
|
||||
// Push local changes to the server
|
||||
let local_mod = cal_local.get_tasks_modified_since(last_sync);
|
||||
let local_mod = cal_local.get_items_modified_since(last_sync, None);
|
||||
|
||||
let mut tasks_to_add_to_server = Vec::new();
|
||||
let mut tasks_id_to_remove_from_server = Vec::new();
|
||||
|
@ -122,14 +132,14 @@ where
|
|||
}
|
||||
|
||||
|
||||
fn move_to_calendar(items: &mut Vec<Item>, calendar: &mut Calendar) {
|
||||
fn move_to_calendar<C: PartialCalendar>(items: &mut Vec<Item>, calendar: &mut C) {
|
||||
while items.len() > 0 {
|
||||
let item = items.remove(0);
|
||||
calendar.add_item(item);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_from_calendar(ids: &Vec<ItemId>, calendar: &mut Calendar) {
|
||||
fn remove_from_calendar<C: PartialCalendar>(ids: &Vec<ItemId>, calendar: &mut C) {
|
||||
for id in ids {
|
||||
log::info!(" Removing {:?} from local calendar", id);
|
||||
calendar.delete_item(id);
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
use std::error::Error;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use url::Url;
|
||||
use chrono::{DateTime, Utc};
|
||||
|
||||
use crate::Calendar;
|
||||
use crate::calendar::cached_calendar::CachedCalendar;
|
||||
use crate::item::Item;
|
||||
use crate::item::ItemId;
|
||||
|
||||
#[async_trait]
|
||||
pub trait CalDavSource {
|
||||
pub trait CalDavSource<T: PartialCalendar> {
|
||||
/// Returns the current calendars that this source contains
|
||||
/// This function may trigger an update (that can be a long process, or that can even fail, e.g. in case of a remote server)
|
||||
async fn get_calendars(&self) -> Result<&Vec<Calendar>, Box<dyn Error>>;
|
||||
async fn get_calendars(&self) -> Result<&Vec<T>, Box<dyn Error>>;
|
||||
/// Returns the current calendars that this source contains
|
||||
/// This function may trigger an update (that can be a long process, or that can even fail, e.g. in case of a remote server)
|
||||
async fn get_calendars_mut(&mut self) -> Result<Vec<&mut Calendar>, Box<dyn Error>>;
|
||||
async fn get_calendars_mut(&mut self) -> Result<Vec<&mut T>, Box<dyn Error>>;
|
||||
|
||||
//
|
||||
//
|
||||
|
@ -21,9 +24,9 @@ pub trait CalDavSource {
|
|||
// TODO: search key should be a reference
|
||||
//
|
||||
/// Returns the calendar matching the URL
|
||||
async fn get_calendar(&self, url: Url) -> Option<&Calendar>;
|
||||
async fn get_calendar(&self, url: Url) -> Option<&T>;
|
||||
/// Returns the calendar matching the URL
|
||||
async fn get_calendar_mut(&mut self, url: Url) -> Option<&mut Calendar>;
|
||||
async fn get_calendar_mut(&mut self, url: Url) -> Option<&mut T>;
|
||||
|
||||
}
|
||||
|
||||
|
@ -34,3 +37,61 @@ pub trait SyncSlave {
|
|||
/// Update the last sync timestamp to now, or to a custom time in case `timepoint` is `Some`
|
||||
fn update_last_sync(&mut self, timepoint: Option<DateTime<Utc>>);
|
||||
}
|
||||
|
||||
/// A calendar we have a partial knowledge of.
|
||||
///
|
||||
/// Usually, this is a calendar from a remote source, that is synced to a CompleteCalendar
|
||||
pub trait PartialCalendar {
|
||||
/// Returns the calendar name
|
||||
fn name(&self) -> &str;
|
||||
|
||||
/// Returns the calendar URL
|
||||
fn url(&self) -> &Url;
|
||||
|
||||
/// Returns the supported kinds of components for this calendar
|
||||
fn supported_components(&self) -> crate::calendar::SupportedComponents;
|
||||
|
||||
/// Returns the items that have been last-modified after `since`
|
||||
fn get_items_modified_since(&self, since: Option<DateTime<Utc>>, filter: Option<crate::calendar::SearchFilter>)
|
||||
-> HashMap<ItemId, &Item>;
|
||||
|
||||
/// Returns a particular item
|
||||
fn get_item_by_id_mut(&mut self, id: &ItemId) -> Option<&mut Item>;
|
||||
|
||||
/// Add an item into this calendar
|
||||
fn add_item(&mut self, item: Item);
|
||||
|
||||
/// Remove an item from this calendar
|
||||
fn delete_item(&mut self, item_id: &ItemId);
|
||||
|
||||
/// Compares with another calendar and lists missing items
|
||||
/// This function is a sort of replacement for `get_items_deleted_since`, that is not available on PartialCalendars
|
||||
fn find_missing_items_compared_to(&self, other: &dyn PartialCalendar) -> Vec<ItemId>;
|
||||
}
|
||||
|
||||
/// A calendar we always know everything about.
|
||||
///
|
||||
/// Usually, this is a calendar fully stored on a local disk
|
||||
pub trait CompleteCalendar : PartialCalendar {
|
||||
/// Returns the items that have been deleted after `since`
|
||||
///
|
||||
/// See also [`PartialCalendar::get_items_deleted_since`]
|
||||
fn get_items_deleted_since(&self, since: DateTime<Utc>) -> Vec<ItemId>;
|
||||
|
||||
/// Returns the list of items that this calendar contains
|
||||
fn get_items(&self) -> HashMap<ItemId, &Item>;
|
||||
}
|
||||
|
||||
|
||||
|
||||
impl PartialCalendar {
|
||||
/// Returns whether this calDAV calendar supports to-do items
|
||||
pub fn supports_todo(&self) -> bool {
|
||||
self.supported_components().contains(crate::calendar::SupportedComponents::TODO)
|
||||
}
|
||||
|
||||
/// Returns whether this calDAV calendar supports calendar items
|
||||
pub fn supports_events(&self) -> bool {
|
||||
self.supported_components().contains(crate::calendar::SupportedComponents::EVENT)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
use minidom::Element;
|
||||
|
||||
use crate::Calendar;
|
||||
use crate::traits::CompleteCalendar;
|
||||
|
||||
/// Walks an XML tree and returns every element that has the given name
|
||||
pub fn find_elems<S: AsRef<str>>(root: &Element, searched_name: S) -> Vec<&Element> {
|
||||
|
@ -53,7 +53,7 @@ pub fn print_xml(element: &Element) {
|
|||
}
|
||||
|
||||
/// A debug utility that pretty-prints calendars
|
||||
pub fn print_calendar_list(cals: &Vec<Calendar>) {
|
||||
pub fn print_calendar_list<C: CompleteCalendar>(cals: &Vec<C>) {
|
||||
for cal in cals {
|
||||
println!("CAL {}", cal.url());
|
||||
for (_, item) in cal.get_items() {
|
||||
|
|
|
@ -7,6 +7,8 @@ use minidom::Element;
|
|||
use url::Url;
|
||||
|
||||
use my_tasks::client::Client;
|
||||
use my_tasks::traits::PartialCalendar;
|
||||
|
||||
use my_tasks::settings::URL;
|
||||
use my_tasks::settings::USERNAME;
|
||||
use my_tasks::settings::PASSWORD;
|
||||
|
@ -24,7 +26,7 @@ static EXAMPLE_TASKS_BODY_LAST_MODIFIED: &str = r#"
|
|||
<C:comp-filter name="VCALENDAR">
|
||||
<C:comp-filter name="VTODO">
|
||||
<C:prop-filter name="LAST-MODIFIED">
|
||||
<C:time-range start="20210220T000000Z"
|
||||
<C:time-range start="20210228T002308Z"
|
||||
end="20260105T000000Z"/>
|
||||
</C:prop-filter>
|
||||
</C:comp-filter>
|
||||
|
@ -45,7 +47,7 @@ async fn test_client() {
|
|||
.map(|cal| println!(" {}\t{}", cal.name(), cal.url().as_str()))
|
||||
.collect::<()>();
|
||||
|
||||
let _ = client.get_tasks(&calendars[3].url()).await;
|
||||
let _ = client.get_tasks(&calendars[0].url()).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
|
|
@ -4,10 +4,11 @@ use chrono::{Utc, TimeZone};
|
|||
use url::Url;
|
||||
|
||||
use my_tasks::traits::CalDavSource;
|
||||
use my_tasks::traits::{PartialCalendar, CompleteCalendar};
|
||||
use my_tasks::cache::Cache;
|
||||
use my_tasks::Item;
|
||||
use my_tasks::Task;
|
||||
use my_tasks::Calendar;
|
||||
use my_tasks::calendar::cached_calendar::CachedCalendar;
|
||||
use my_tasks::Provider;
|
||||
|
||||
#[tokio::test]
|
||||
|
@ -44,7 +45,7 @@ async fn test_sync() {
|
|||
/// * X': name has been modified since the last sync
|
||||
/// * F'/F'': name conflict
|
||||
/// * G✓: task has been marked as completed
|
||||
async fn populate_test_provider() -> Provider<Cache, Cache> {
|
||||
async fn populate_test_provider() -> Provider<Cache, CachedCalendar, Cache, CachedCalendar> {
|
||||
let mut server = Cache::new(&PathBuf::from(String::from("server.json")));
|
||||
let mut local = Cache::new(&PathBuf::from(String::from("local.json")));
|
||||
|
||||
|
@ -80,7 +81,7 @@ async fn populate_test_provider() -> Provider<Cache, Cache> {
|
|||
|
||||
// Step 1
|
||||
// Build the calendar as it was at the time of the sync
|
||||
let mut calendar = Calendar::new("a list".into(), Url::parse("http://todo.list/cal").unwrap(), my_tasks::calendar::SupportedComponents::TODO);
|
||||
let mut calendar = CachedCalendar::new("a list".into(), Url::parse("http://todo.list/cal").unwrap(), my_tasks::calendar::SupportedComponents::TODO);
|
||||
calendar.add_item(task_a);
|
||||
calendar.add_item(task_b);
|
||||
calendar.add_item(task_c);
|
||||
|
|
Loading…
Add table
Reference in a new issue