diff options
Diffstat (limited to 'src/icalwrap/icalvcalendar.rs')
-rw-r--r-- | src/icalwrap/icalvcalendar.rs | 670 |
1 files changed, 0 insertions, 670 deletions
diff --git a/src/icalwrap/icalvcalendar.rs b/src/icalwrap/icalvcalendar.rs deleted file mode 100644 index ced299a..0000000 --- a/src/icalwrap/icalvcalendar.rs +++ /dev/null @@ -1,670 +0,0 @@ -use std::ffi::{CStr, CString}; -use std::io; -use std::path::{Path, PathBuf}; -use std::rc::Rc; - -use super::IcalComponent; -use super::IcalTime; -use super::IcalVEvent; -use crate::ical; -use crate::khevent::KhEvent; - -pub struct IcalVCalendar { - comp: Rc<IcalComponentOwner>, - path: Option<PathBuf>, - instance_timestamp: Option<IcalTime>, -} - -pub struct IcalEventIter<'a> { - iter: ical::icalcompiter, - parent: &'a IcalVCalendar, -} - -impl IcalComponent for IcalVCalendar { - fn get_ptr(&self) -> *mut ical::icalcomponent { - self.comp.ptr - } - - fn as_component(&self) -> &dyn IcalComponent { - self - } -} - -impl Clone for IcalVCalendar { - fn clone(&self) -> Self { - let new_comp_ptr = unsafe { ical::icalcomponent_new_clone(self.comp.ptr) }; - let mut new_calendar = IcalVCalendar::from_ptr(new_comp_ptr); - new_calendar.path = self.path.clone(); - new_calendar.instance_timestamp = self.instance_timestamp.clone(); - new_calendar - } -} - -impl IcalVCalendar { - fn from_ptr(ptr: *mut ical::icalcomponent) -> Self { - IcalVCalendar { - comp: Rc::new(IcalComponentOwner { ptr }), - path: None, - instance_timestamp: None, - } - } - - pub fn shallow_copy(&self) -> Self { - IcalVCalendar { - comp: self.comp.clone(), - path: self.path.clone(), - instance_timestamp: self.instance_timestamp.clone(), - } - } - - //pub fn with_internal_timestamp(mut self, datetime: &IcalTime) -> IcalVCalendar { - //self.instance_timestamp = Some(datetime.clone()); - //self - //} - - pub fn with_path(mut self, path: &Path) -> IcalVCalendar { - self.path = Some(path.to_path_buf()); - self - } - - //TODO should probably be private - pub fn from_str(str: &str, path: Option<&Path>) -> io::Result<Self> { - unsafe { - let c_str = CString::new(str).unwrap(); - let parsed_cal = ical::icalparser_parse_string(c_str.as_ptr()); - if parsed_cal.is_null() { - return Err(io::Error::new(io::ErrorKind::Other, "calendar has no path")); - } - - let kind = ical::icalcomponent_isa(parsed_cal); - if kind != ical::icalcomponent_kind_ICAL_VCALENDAR_COMPONENT { - let kind = CStr::from_ptr(ical::icalcomponent_kind_to_string(kind)).to_string_lossy(); - return Err(io::Error::new( - io::ErrorKind::Other, - format!("expected VCALENDAR component, got {}", kind), - )); - } - - let mut cal = IcalVCalendar::from_ptr(parsed_cal); - cal.path = path.map(|path| path.to_path_buf()); - - Ok(cal) - } - } - - pub fn with_normalize(self) -> Self { - unsafe { - ical::icalcomponent_normalize(self.get_ptr()); - } - self - } - - pub fn to_string(&self) -> String { - unsafe { - let ical_cstr = CStr::from_ptr(ical::icalcomponent_as_ical_string(self.get_ptr())); - ical_cstr.to_string_lossy().into_owned() - } - } - - pub fn get_uid(&self) -> String { - unsafe { - let uid_cstr = CStr::from_ptr(ical::icalcomponent_get_uid( - self.get_principal_event().get_ptr(), - )); - uid_cstr.to_string_lossy().into_owned() - } - } - - pub fn with_uid(mut self, uid: &str) -> Result<Self, String> { - { - let events = self.events_iter(); - if events.unique_uid_count() > 1 { - return Err(format!( - "More than one event in file: {}", - self.get_path_as_string().unwrap_or_else(|| "".to_string()) - )); - } - let events = self.events_iter(); - let uid_cstr = CString::new(uid).unwrap(); - for event in events { - unsafe { - ical::icalcomponent_set_uid(event.get_ptr(), uid_cstr.as_ptr()); - } - } - } - self.path = self - .path - .map(|path| path.with_file_name(uid.to_owned() + ".ics")); - Ok(self) - } - - pub fn with_dtstamp_now(self) -> Self { - let dtstamp = IcalTime::utc(); - unsafe { - ical::icalcomponent_set_dtstamp(self.get_ptr(), *dtstamp); - } - self - } - - pub fn with_dtstart(self, dtstart: &IcalTime) -> Self { - let event = self.get_principal_event(); - unsafe { - let timezone = event.get_dtstart().and_then(|x| x.get_timezone()); - match timezone { - Some(timezone) => { - ical::icalcomponent_set_dtstart(event.get_ptr(), *dtstart.with_timezone(&timezone)); - } - None => { - ical::icalcomponent_set_dtstart(event.get_ptr(), **dtstart); - } - } - } - self - } - - pub fn with_dtend(self, dtend: &IcalTime) -> Self { - let event = self.get_principal_event(); - unsafe { - let timezone = event.get_dtend().and_then(|x| x.get_timezone()); - match timezone { - Some(timezone) => { - ical::icalcomponent_set_dtend(event.get_ptr(), *dtend.with_timezone(&timezone)); - } - None => { - ical::icalcomponent_set_dtend(event.get_ptr(), **dtend); - } - } - } - self - } - - pub fn with_location(self, location: &str) -> Self { - let event = self.get_principal_event(); - unsafe { - let c_str = CString::new(location).unwrap(); - ical::icalcomponent_set_location(event.get_ptr(), c_str.as_ptr()); - } - self - } - - pub fn with_summary(self, summary: &str) -> Self { - let event = self.get_principal_event(); - unsafe { - let c_str = CString::new(summary).unwrap(); - ical::icalcomponent_set_summary(event.get_ptr(), c_str.as_ptr()); - } - self - } - - pub fn with_last_modified_now(self) -> Self { - let event = self.get_principal_event(); - unsafe { - let now_icaltime = IcalTime::utc(); - - let last_modified_kind = ical::icalproperty_kind_ICAL_LASTMODIFIED_PROPERTY; - if let Some(prop) = event.get_property(last_modified_kind) { - ical::icalproperty_set_lastmodified(prop.ptr, *now_icaltime); - } else { - let prop_lastmod = ical::icalproperty_new_lastmodified(*now_icaltime); - ical::icalcomponent_add_property(event.get_ptr(), prop_lastmod); - } - } - self - } - - pub fn with_remove_property(self, property_name: &str) -> (Self, usize) { - let property_kind = unsafe { - let c_str = CString::new(property_name).unwrap(); - ical::icalproperty_string_to_kind(c_str.as_ptr()) - }; - - let count = unsafe { IcalComponent::remove_property_all(&self, property_kind) }; - (self, count) - } - - pub fn with_keep_uid(self, uid_to_keep: &str) -> Self { - unsafe { - ical::icalcomponent_get_first_component( - self.comp.ptr, - ical::icalcomponent_kind_ICAL_ANY_COMPONENT, - ); - - loop { - let comp = ical::icalcomponent_get_current_component(self.comp.ptr); - if comp.is_null() { - return self; - } - let uid_ptr = ical::icalcomponent_get_uid(comp); - if !uid_ptr.is_null() { - let uid = CStr::from_ptr(uid_ptr).to_string_lossy(); - if uid != uid_to_keep { - ical::icalcomponent_remove_component(self.comp.ptr, comp); - continue; - } - } - ical::icalcomponent_get_next_component( - self.comp.ptr, - ical::icalcomponent_kind_ICAL_ANY_COMPONENT, - ); - } - } - } - - pub fn get_path_as_string(&self) -> Option<String> { - self.path.as_ref().map(|path| format!("{}", path.display())) - } - - pub fn get_path(&self) -> Option<&PathBuf> { - self.path.as_ref() - } - - pub fn get_calendar_name(&self) -> Option<String> { - let calendar_name = self.path.as_ref()?.parent()?.file_name()?; - Some(calendar_name.to_string_lossy().into_owned()) - } - - pub fn events_iter(&self) -> IcalEventIter { - IcalEventIter::from_vcalendar(self) - } - - pub fn get_first_event(&self) -> IcalVEvent { - let event = unsafe { - ical::icalcomponent_get_first_component( - self.get_ptr(), - ical::icalcomponent_kind_ICAL_VEVENT_COMPONENT, - ) - }; - if self.events_iter().unique_uid_count() > 1 { - warn!( - "More than one event in file: {}", - self.get_path_as_string().unwrap_or_else(|| "".to_string()) - ) - } - IcalVEvent::from_ptr_with_parent(event, self) - } - - pub(in crate::icalwrap) fn get_principal_event(&self) -> IcalVEvent { - let mut event = self.get_first_event(); - if let Some(ref timestamp) = self.instance_timestamp { - event = event.with_internal_timestamp(timestamp) - } - event - } - - pub fn get_principal_khevent(&self) -> KhEvent { - KhEvent::from_event_with_timestamp(self.get_first_event(), self.instance_timestamp.clone()) - } - - pub fn check_for_errors(&self) -> Option<Vec<String>> { - unsafe { IcalVCalendar::check_icalcomponent(self.get_ptr()) } - } - - /// to be used after parsing, parser adds X-LIC-ERROR properties for any error - /// ical::icalrestriction_check() checks if the specification is violated and adds X-LIC-ERRORs accordingly - /// ical::icalcomponent_count_errors() counts all X-LIC-ERROR properties - unsafe fn check_icalcomponent(comp: *mut ical::icalcomponent) -> Option<Vec<String>> { - ical::icalrestriction_check(comp); - let error_count = ical::icalcomponent_count_errors(comp); - let mut output: Vec<String> = Vec::new(); - if error_count > 0 { - output.append(&mut IcalVCalendar::get_errors(comp)); - - let mut inner_comp = - ical::icalcomponent_get_first_component(comp, ical::icalcomponent_kind_ICAL_ANY_COMPONENT); - while !inner_comp.is_null() { - output.append(&mut IcalVCalendar::get_errors(inner_comp)); - inner_comp = - ical::icalcomponent_get_next_component(comp, ical::icalcomponent_kind_ICAL_ANY_COMPONENT) - } - } - if let Some(errstring) = IcalVCalendar::check_uid(comp) { - output.push(errstring); - } - if output.is_empty() { - None - } else { - Some(output) - } - } - - unsafe fn check_uid(comp: *mut ical::icalcomponent) -> Option<String> { - let uid = ical::icalcomponent_get_uid(comp); - if uid.is_null() { - Some("missing required property: UID".to_string()) - } else { - None - } - } - - unsafe fn get_errors(comp: *mut ical::icalcomponent) -> Vec<String> { - let mut prop = - ical::icalcomponent_get_first_property(comp, ical::icalproperty_kind_ICAL_XLICERROR_PROPERTY); - let mut output: Vec<String> = Vec::new(); - while !prop.is_null() { - let error_cstr = CStr::from_ptr(ical::icalproperty_get_xlicerror(prop)) - .to_str() - .unwrap(); - output.push(error_cstr.to_owned()); - prop = ical::icalcomponent_get_next_property( - comp, - ical::icalproperty_kind_ICAL_XLICERROR_PROPERTY, - ); - } - output - } -} - -impl<'a> IcalEventIter<'a> { - fn from_vcalendar(cal: &'a IcalVCalendar) -> Self { - let vevent_kind = ical::icalcomponent_kind_ICAL_VEVENT_COMPONENT; - let iter = unsafe { ical::icalcomponent_begin_component(cal.get_ptr(), vevent_kind) }; - IcalEventIter { iter, parent: &cal } - } - - fn unique_uid_count(self) -> usize { - let mut uids = self.map(|event| event.get_uid()).collect::<Vec<String>>(); - uids.sort_unstable(); - uids.dedup(); - uids.len() - } -} - -impl<'a> Iterator for IcalEventIter<'a> { - type Item = IcalVEvent; - - fn next(&mut self) -> Option<Self::Item> { - unsafe { - let ptr = ical::icalcompiter_deref(&mut self.iter); - if ptr.is_null() { - None - } else { - ical::icalcompiter_next(&mut self.iter); - let vevent = IcalVEvent::from_ptr_with_parent(ptr, self.parent); - Some(vevent) - } - } - } -} - -struct IcalComponentOwner { - ptr: *mut ical::icalcomponent, -} - -impl Drop for IcalComponentOwner { - fn drop(&mut self) { - unsafe { - // println!("free"); - ical::icalcomponent_free(self.ptr); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::testdata; - use chrono::{Local, TimeZone}; - - #[test] - fn test_from_str_empty() { - assert!(IcalVCalendar::from_str("", None).is_err()); - } - - #[test] - fn test_from_str_event() { - assert!(IcalVCalendar::from_str(testdata::TEST_BARE_EVENT, None).is_err()); - } - - #[test] - fn event_iterator_element_count() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - assert_eq!(cal.events_iter().count(), 1) - } - - #[test] - fn event_iterator_element_count_with_other() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_WITH_TIMEZONE_COMPONENT, None).unwrap(); - assert_eq!(cal.events_iter().count(), 1) - } - - #[test] - fn load_serialize() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - let back = cal.to_string().replace("\r\n", "\n"); - assert_eq!(back.trim(), testdata::TEST_EVENT_MULTIDAY) - } - - #[test] - fn load_serialize_with_error() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_WITH_X_LIC_ERROR, None).unwrap(); - let back = cal.to_string().replace("\r\n", "\n"); - assert_eq!(back.trim(), testdata::TEST_EVENT_WITH_X_LIC_ERROR) - } - - #[test] - fn with_dtstamp_test() { - let mut cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - cal = cal.with_dtstamp_now(); - let event = cal.get_principal_event(); - - let dtstamp_prop = ical::icalproperty_kind_ICAL_DTSTAMP_PROPERTY; - let now_dtstamp = event.get_property(dtstamp_prop).unwrap().get_value(); - assert_eq!(now_dtstamp, "20130101T010203Z") - } - - #[test] - fn get_calendar_name_test() { - let path = PathBuf::from("calname/event"); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY_ALLDAY, Some(&path)).unwrap(); - assert_eq!("calname".to_string(), cal.get_calendar_name().unwrap()) - } - - #[test] - fn test_get_all_properties_cal() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - - let props = cal.get_properties_all(); - assert_eq!(2, props.len()); - } - - #[test] - fn parse_checker_test_empty_summary() { - let c_str = CString::new(testdata::TEST_EVENT_EMPTY_SUMMARY).unwrap(); - unsafe { - let parsed_cal = ical::icalparser_parse_string(c_str.as_ptr()); - assert!(IcalVCalendar::check_icalcomponent(parsed_cal).is_some()) - } - } - - #[test] - fn parse_checker_test_no_uid() { - let c_str = CString::new(testdata::TEST_EVENT_NO_UID).unwrap(); - unsafe { - let parsed_cal = ical::icalparser_parse_string(c_str.as_ptr()); - assert!(IcalVCalendar::check_icalcomponent(parsed_cal).is_some()) - } - } - - #[test] - fn parse_checker_test_no_prodid() { - let c_str = CString::new(testdata::TEST_EVENT_NO_PRODID).unwrap(); - unsafe { - let parsed_cal = ical::icalparser_parse_string(c_str.as_ptr()); - assert!(IcalVCalendar::check_icalcomponent(parsed_cal).is_some()) - } - } - - #[test] - fn test_with_last_modified_now() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY_LASTMODIFIED, None).unwrap(); - - let new_cal = cal.with_last_modified_now(); - let event = new_cal.get_principal_event(); - - let last_modified_kind = ical::icalproperty_kind_ICAL_LASTMODIFIED_PROPERTY; - assert_eq!( - "20130101T010203Z", - event.get_property(last_modified_kind).unwrap().get_value() - ); - } - - #[test] - fn test_with_last_modified_now_added() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - - let new_cal = cal.with_last_modified_now(); - let event = new_cal.get_principal_event(); - - let last_modified_kind = ical::icalproperty_kind_ICAL_LASTMODIFIED_PROPERTY; - assert_eq!( - "20130101T010203Z", - event.get_property(last_modified_kind).unwrap().get_value() - ); - } - - #[test] - fn test_with_location() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - - let location = "test"; - let new_cal = cal.with_location(location); - - let event = new_cal.get_principal_event(); - assert_eq!(location, event.get_location().unwrap()) - } - - #[test] - fn test_with_summary() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - - let summary = "test"; - let new_cal = cal.with_summary(summary); - - let event = new_cal.get_principal_khevent(); - assert_eq!(summary, event.get_summary().unwrap()) - } - - #[test] - fn test_with_dtend() { - testdata::setup(); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - - let timestamp = IcalTime::floating_ymd(2018, 1, 1).and_hms(11, 30, 20); - let new_cal = cal.with_dtend(×tamp); - - let event = new_cal.get_principal_khevent(); - assert_eq!(timestamp, event.get_end().unwrap()) - } - - #[test] - fn test_with_dtstart() { - testdata::setup(); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - - let timestamp = IcalTime::floating_ymd(2018, 1, 1).and_hms(11, 30, 20); - let new_cal = cal.with_dtstart(×tamp); - - let event = new_cal.get_principal_khevent(); - assert_eq!(timestamp, event.get_start().unwrap()) - } - - #[test] - fn test_with_dtstart_timezone() { - testdata::setup(); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - - let local_date = Local.ymd(2018, 1, 1).and_hms(11, 30, 20); - let timestamp = IcalTime::from(local_date); - - let new_cal = cal.with_dtstart(×tamp); - - let event = new_cal.get_principal_khevent(); - assert_eq!(timestamp, event.get_start().unwrap()); - assert_eq!( - "Europe/Berlin", - event - .get_start() - .unwrap() - .get_timezone() - .unwrap() - .get_name() - ); - } - - //#[test] - //fn test_with_internal_timestamp() { - //let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - // - //let timestamp = IcalTime::floating_ymd(2018, 1, 1).and_hms(11, 30, 20); - //let new_cal = cal.with_internal_timestamp(×tamp); - // - //let event = new_cal.get_principal_event(); - //assert_eq!(timestamp, event.get_dtstart().unwrap()); - //} - - #[test] - fn with_uid_test() { - let path = PathBuf::from("test/path"); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, Some(&path)).unwrap(); - - let uid = "my_new_uid"; - let new_cal = cal.with_uid(uid).unwrap(); - - for event in new_cal.events_iter() { - assert_eq!(uid, event.get_uid()); - } - assert_eq!( - Some(path.with_file_name(uid.to_owned() + ".ics")), - new_cal.path - ); - } - - #[test] - fn with_uid_multiple_test() { - let path = PathBuf::from("test/path"); - let cal = IcalVCalendar::from_str(testdata::TEST_MULTIPLE_EVENTS, Some(&path)).unwrap(); - - let uid = "my_new_uid"; - let new_cal = cal.with_uid(uid); - - assert!(new_cal.is_err()); - } - - #[test] - fn with_keep_uid_test() { - let path = PathBuf::from("test/path"); - let cal = IcalVCalendar::from_str(testdata::TEST_MULTIPLE_EVENTS, Some(&path)).unwrap(); - - for uid in &["uid1", "uid2"] { - let new_cal = cal.clone().with_keep_uid(uid); - - assert_eq!(1, new_cal.events_iter().count()); - assert_eq!(*uid, new_cal.get_uid()); - assert_eq!(*uid, new_cal.get_principal_event().get_uid()); - } - } - - #[test] - fn clone_test() { - let path = PathBuf::from("test/path"); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, Some(&path)).unwrap(); - let cal2 = cal.clone().with_uid("my_new_uid").unwrap(); - - assert_ne!(cal.get_uid(), cal2.get_uid()); - } - - #[test] - fn parse_checker_test_negative() { - let c_str = CString::new(testdata::TEST_EVENT_NO_PRODID).unwrap(); - unsafe { - let parsed_cal = ical::icalparser_parse_string(c_str.as_ptr()); - assert!(IcalVCalendar::check_icalcomponent(parsed_cal).is_some()) - } - } - - #[test] - fn parse_checker_test() { - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY, None).unwrap(); - assert!(cal.check_for_errors().is_none()); - } -} |