From f7acadc3e289283c4fe76fce5ff6df3a6f36594c Mon Sep 17 00:00:00 2001 From: daladim Date: Thu, 4 Nov 2021 23:44:54 +0100 Subject: [PATCH] Unhandled lines from iCal files are stored --- src/ical/builder.rs | 67 +++++++++++++++++++++++++++++++-------------- src/ical/parser.rs | 7 +++-- src/task.rs | 13 +++++++-- 3 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/ical/builder.rs b/src/ical/builder.rs index 11ca71d..182e6c9 100644 --- a/src/ical/builder.rs +++ b/src/ical/builder.rs @@ -5,7 +5,11 @@ use std::error::Error; use chrono::{DateTime, Utc}; use ics::properties::{Completed, Created, LastModified, PercentComplete, Status, Summary}; use ics::{ICalendar, ToDo}; +use ics::components::Parameter as IcsParameter; +use ics::components::Property as IcsProperty; +use ical::property::Property as IcalProperty; +use crate::Task; use crate::item::Item; use crate::task::CompletionStatus; use crate::settings::{ORG_NAME, PRODUCT_NAME}; @@ -16,37 +20,43 @@ fn ical_product_id() -> String { /// Create an iCal item from a `crate::item::Item` pub fn build_from(item: &Item) -> Result> { - let s_last_modified = format_date_time(item.last_modified()); + match item { + Item::Task(t) => build_from_task(t), + _ => unimplemented!(), + } +} + +pub fn build_from_task(task: &Task) -> Result> { + let s_last_modified = format_date_time(task.last_modified()); let mut todo = ToDo::new( - item.uid(), + task.uid(), s_last_modified.clone(), ); - item.creation_date().map(|dt| + task.creation_date().map(|dt| todo.push(Created::new(format_date_time(dt))) ); todo.push(LastModified::new(s_last_modified)); - todo.push(Summary::new(item.name())); + todo.push(Summary::new(task.name())); - match item { - Item::Task(t) => { - match t.completion_status() { - CompletionStatus::Uncompleted => { - todo.push(Status::needs_action()); - }, - CompletionStatus::Completed(completion_date) => { - todo.push(PercentComplete::new("100")); - completion_date.as_ref().map(|dt| todo.push( - Completed::new(format_date_time(dt)) - )); - todo.push(Status::completed()); - } - } - }, - _ => { - unimplemented!() + match task.completion_status() { + CompletionStatus::Uncompleted => { + todo.push(Status::needs_action()); }, + CompletionStatus::Completed(completion_date) => { + todo.push(PercentComplete::new("100")); + completion_date.as_ref().map(|dt| todo.push( + Completed::new(format_date_time(dt)) + )); + todo.push(Status::completed()); + } + } + + // Also add fields that we have not handled + for ical_property in task.extra_parameters() { + let ics_property = ical_to_ics_property(ical_property.clone()); + todo.push(ics_property); } let mut calendar = ICalendar::new("2.0", ical_product_id()); @@ -60,6 +70,21 @@ fn format_date_time(dt: &DateTime) -> String { } +fn ical_to_ics_property(prop: IcalProperty) -> IcsProperty<'static> { + let mut ics_prop = match prop.value { + Some(value) => IcsProperty::new(prop.name, value), + None => IcsProperty::new(prop.name, ""), + }; + prop.params.map(|v| { + for (key, vec_values) in v { + let values = vec_values.join(";"); + ics_prop.add(IcsParameter::new(key, values)); + } + }); + ics_prop +} + + #[cfg(test)] mod tests { use super::*; diff --git a/src/ical/parser.rs b/src/ical/parser.rs index 0a0cb07..4a683af 100644 --- a/src/ical/parser.rs +++ b/src/ical/parser.rs @@ -36,6 +36,8 @@ pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result< let mut last_modified = None; let mut completion_date = None; let mut creation_date = None; + let mut extra_parameters = Vec::new(); + for prop in &todo.properties { match prop.name.as_str() { "SUMMARY" => { name = prop.value.clone() }, @@ -67,7 +69,8 @@ pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result< } } _ => { - // This field is not supported. + // This field is not supported. Let's store it anyway, so that we are able to re-create an identical iCal file + extra_parameters.push(prop.clone()); } } } @@ -93,7 +96,7 @@ pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result< true => CompletionStatus::Completed(completion_date), }; - Item::Task(Task::new_with_parameters(name, uid, item_id, completion_status, sync_status, creation_date, last_modified)) + Item::Task(Task::new_with_parameters(name, uid, item_id, completion_status, sync_status, creation_date, last_modified, extra_parameters)) }, }; diff --git a/src/task.rs b/src/task.rs index 00f7e31..e7f0568 100644 --- a/src/task.rs +++ b/src/task.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use uuid::Uuid; use chrono::{DateTime, Utc}; +use ical::property::Property; use crate::item::ItemId; use crate::item::SyncStatus; @@ -51,6 +52,9 @@ pub struct Task { /// The display name of the task name: String, + /// Extra parameters that have not been parsed from the iCal file (because they're not supported (yet) by this crate). + /// They are needed to serialize this item into an equivalent iCal file + extra_parameters: Vec, } @@ -66,13 +70,16 @@ impl Task { let new_completion_status = if completed { CompletionStatus::Completed(Some(Utc::now())) } else { CompletionStatus::Uncompleted }; - Self::new_with_parameters(name, new_uid, new_item_id, new_completion_status, new_sync_status, new_creation_date, new_last_modified) + 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, extra_parameters) } /// Create a new Task instance, that may be synced on the server already pub fn new_with_parameters(name: String, uid: String, id: ItemId, completion_status: CompletionStatus, - sync_status: SyncStatus, creation_date: Option>, last_modified: DateTime) -> Self + sync_status: SyncStatus, creation_date: Option>, last_modified: DateTime, + extra_parameters: Vec, + ) -> Self { Self { id, @@ -82,6 +89,7 @@ impl Task { sync_status, creation_date, last_modified, + extra_parameters, } } @@ -93,6 +101,7 @@ impl Task { pub fn last_modified(&self) -> &DateTime { &self.last_modified } pub fn creation_date(&self) -> Option<&DateTime> { self.creation_date.as_ref() } pub fn completion_status(&self) -> &CompletionStatus { &self.completion_status } + pub fn extra_parameters(&self) -> &[Property] { &self.extra_parameters } #[cfg(any(test, feature = "integration_tests"))] pub fn has_same_observable_content_as(&self, other: &Task) -> bool {