From 3bf1fed5b9565e1a3cea67bfc43773f69c9b52d4 Mon Sep 17 00:00:00 2001 From: daladim Date: Fri, 16 Apr 2021 09:05:30 +0200 Subject: [PATCH] Added item's creation_date --- src/event.rs | 4 +++ src/ical/builder.rs | 75 ++++++++++++++++++++++++++++++++++----------- src/ical/parser.rs | 7 ++++- src/item.rs | 7 +++++ src/task.rs | 13 ++++++-- 5 files changed, 84 insertions(+), 22 deletions(-) diff --git a/src/event.rs b/src/event.rs index fbb0d26..6287be9 100644 --- a/src/event.rs +++ b/src/event.rs @@ -32,6 +32,10 @@ impl Event { &self.name } + pub fn creation_date(&self) -> Option<&DateTime> { + unimplemented!() + } + pub fn last_modified(&self) -> &DateTime { unimplemented!() } diff --git a/src/ical/builder.rs b/src/ical/builder.rs index d511382..59a3bd0 100644 --- a/src/ical/builder.rs +++ b/src/ical/builder.rs @@ -3,7 +3,7 @@ use std::error::Error; use chrono::{DateTime, Utc}; -use ics::properties::{Completed, LastModified, Status, Summary}; +use ics::properties::{Completed, Created, LastModified, PercentComplete, Status, Summary}; use ics::{ICalendar, ToDo}; use crate::item::Item; @@ -21,17 +21,24 @@ pub fn build_from(item: &Item) -> Result> { item.uid(), s_last_modified.clone(), ); + + item.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())); match item { Item::Task(t) => { - t.completion_date().map(|dt| todo.push( - Completed::new(format_date_time(dt)) - )); - - let status = if t.completed() { Status::completed() } else { Status::needs_action() }; - todo.push(status); + if t.completed() { + todo.push(PercentComplete::new("100")); + t.completion_date().map(|dt| todo.push( + Completed::new(format_date_time(dt)) + )); + todo.push(Status::completed()); + } else { + todo.push(Status::needs_action()); + } }, _ => { unimplemented!() @@ -55,29 +62,61 @@ mod tests { use crate::Task; #[test] - fn test_ical_from_task() { - let cal_id = "http://my.calend.ar/id".parse().unwrap(); - let now = Utc::now(); - let s_now = format_date_time(&now); + fn test_ical_from_completed_task() { + let (s_now, uid, ical) = build_task(true); - let mut task = Item::Task(Task::new( - String::from("This is a task with ÜTF-8 characters"), true, &cal_id - )); - task.unwrap_task_mut().set_completed_on(Some(now)); let expected_ical = format!("BEGIN:VCALENDAR\r\n\ VERSION:2.0\r\n\ PRODID:-//{}//{}//EN\r\n\ BEGIN:VTODO\r\n\ UID:{}\r\n\ DTSTAMP:{}\r\n\ + CREATED:{}\r\n\ + LAST-MODIFIED:{}\r\n\ SUMMARY:This is a task with ÜTF-8 characters\r\n\ + PERCENT-COMPLETE:100\r\n\ COMPLETED:{}\r\n\ STATUS:COMPLETED\r\n\ END:VTODO\r\n\ - END:VCALENDAR\r\n", ORG_NAME, PRODUCT_NAME, task.uid(), s_now, s_now); + END:VCALENDAR\r\n", ORG_NAME, PRODUCT_NAME, uid, s_now, s_now, s_now, s_now); - let ical = build_from(&task); - assert_eq!(ical.unwrap(), expected_ical); + assert_eq!(ical, expected_ical); + } + + #[test] + fn test_ical_from_uncompleted_task() { + let (s_now, uid, ical) = build_task(false); + + let expected_ical = format!("BEGIN:VCALENDAR\r\n\ + VERSION:2.0\r\n\ + PRODID:-//{}//{}//EN\r\n\ + BEGIN:VTODO\r\n\ + UID:{}\r\n\ + DTSTAMP:{}\r\n\ + CREATED:{}\r\n\ + LAST-MODIFIED:{}\r\n\ + SUMMARY:This is a task with ÜTF-8 characters\r\n\ + STATUS:NEEDS-ACTION\r\n\ + END:VTODO\r\n\ + END:VCALENDAR\r\n", ORG_NAME, PRODUCT_NAME, uid, s_now, s_now, s_now); + + assert_eq!(ical, expected_ical); + } + + fn build_task(completed: bool) -> (String, String, String) { + let cal_id = "http://my.calend.ar/id".parse().unwrap(); + let now = Utc::now(); + let s_now = format_date_time(&now); + + let mut task = Item::Task(Task::new( + String::from("This is a task with ÜTF-8 characters"), completed, &cal_id + )); + if completed { + task.unwrap_task_mut().set_completed_on(Some(now)); + } + + let ical = build_from(&task).unwrap(); + (s_now, task.uid().to_string(), ical) } #[test] diff --git a/src/ical/parser.rs b/src/ical/parser.rs index 8fb218a..ca4fc1a 100644 --- a/src/ical/parser.rs +++ b/src/ical/parser.rs @@ -34,6 +34,7 @@ pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result< let mut completed = false; let mut last_modified = None; let mut completion_date = None; + let mut creation_date = None; for prop in &todo.properties { if prop.name == "SUMMARY" { name = prop.value.clone(); @@ -61,6 +62,10 @@ pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result< // the calendar component was last revised in the calendar store. completion_date = parse_date_time_from_property(&prop.value) } + if prop.name == "CREATED" { + // The property can be specified once, but is not mandatory + creation_date = parse_date_time_from_property(&prop.value) + } } let name = match name { Some(name) => name, @@ -80,7 +85,7 @@ pub fn parse(content: &str, item_id: ItemId, sync_status: SyncStatus) -> Result< log::warn!("Inconsistant iCal data: completion date is {:?} but completion status is {:?}", completion_date, completed); } - Item::Task(Task::new_with_parameters(name, uid, item_id, sync_status, last_modified, completion_date)) + Item::Task(Task::new_with_parameters(name, uid, item_id, sync_status, creation_date, last_modified, completion_date)) }, }; diff --git a/src/item.rs b/src/item.rs index fd37569..77365d0 100644 --- a/src/item.rs +++ b/src/item.rs @@ -40,6 +40,13 @@ impl Item { } } + pub fn creation_date(&self) -> Option<&DateTime> { + match self { + Item::Event(e) => e.creation_date(), + Item::Task(t) => t.creation_date(), + } + } + pub fn last_modified(&self) -> &DateTime { match self { Item::Event(e) => e.last_modified(), diff --git a/src/task.rs b/src/task.rs index 827c90a..275df87 100644 --- a/src/task.rs +++ b/src/task.rs @@ -18,6 +18,9 @@ pub struct Task { /// The sync status of this item sync_status: SyncStatus, + /// The time this item was created. + /// This is not required by RFC5545. This will be populated in tasks created by this crate, but can be None for tasks coming from a server + creation_date: Option>, /// The last time this item was modified last_modified: DateTime, @@ -34,20 +37,23 @@ impl Task { let new_item_id = ItemId::random(parent_calendar_id); let new_sync_status = SyncStatus::NotSynced; let new_uid = Uuid::new_v4().to_hyphenated().to_string(); + let new_creation_date = Some(Utc::now()); let new_last_modified = Utc::now(); let new_completion_date = if completed { Some(Utc::now()) } else { None }; - Self::new_with_parameters(name, new_uid, new_item_id, new_sync_status, new_last_modified, new_completion_date) + Self::new_with_parameters(name, new_uid, new_item_id, new_sync_status, new_creation_date, new_last_modified, new_completion_date) } /// Create a new Task instance, that may be synced already pub fn new_with_parameters(name: String, uid: String, id: ItemId, - sync_status: SyncStatus, last_modified: DateTime, completion_date: Option>) -> Self { + sync_status: SyncStatus, creation_date: Option>, last_modified: DateTime, completion_date: Option>) -> Self + { Self { id, uid, name, sync_status, completion_date, + creation_date, last_modified, } } @@ -56,8 +62,9 @@ impl Task { pub fn uid(&self) -> &str { &self.uid } pub fn name(&self) -> &str { &self.name } pub fn completed(&self) -> bool { self.completion_date.is_some() } - pub fn sync_status(&self) -> &SyncStatus { &self.sync_status } + pub fn sync_status(&self) -> &SyncStatus { &self.sync_status } pub fn last_modified(&self) -> &DateTime { &self.last_modified } + pub fn creation_date(&self) -> Option<&DateTime> { self.creation_date.as_ref() } pub fn completion_date(&self) -> Option<&DateTime> { self.completion_date.as_ref() } pub fn has_same_observable_content_as(&self, other: &Task) -> bool {