diff options
Diffstat (limited to 'lib/entry/libimagentrydatetime/src/datetime.rs')
-rw-r--r-- | lib/entry/libimagentrydatetime/src/datetime.rs | 330 |
1 files changed, 330 insertions, 0 deletions
diff --git a/lib/entry/libimagentrydatetime/src/datetime.rs b/lib/entry/libimagentrydatetime/src/datetime.rs new file mode 100644 index 00000000..73c29622 --- /dev/null +++ b/lib/entry/libimagentrydatetime/src/datetime.rs @@ -0,0 +1,330 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015, 2016 Matthias Beyer <mail@beyermatthias.de> and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use chrono::naive::NaiveDateTime; +use toml_query::delete::TomlValueDeleteExt; +use toml_query::insert::TomlValueInsertExt; +use toml_query::read::TomlValueReadExt; +use toml::Value; + +use libimagstore::store::Entry; +use libimagerror::into::IntoError; + +use error::DateErrorKind as DEK; +use error::*; +use result::Result; +use range::DateTimeRange; + +pub trait EntryDate { + + fn delete_date(&mut self) -> Result<()>; + fn read_date(&self) -> Result<NaiveDateTime>; + fn set_date(&mut self, d: NaiveDateTime) -> Result<Option<Result<NaiveDateTime>>>; + + fn delete_date_range(&mut self) -> Result<()>; + fn read_date_range(&self) -> Result<DateTimeRange>; + fn set_date_range(&mut self, start: NaiveDateTime, end: NaiveDateTime) -> Result<Option<Result<DateTimeRange>>>; + +} + +lazy_static! { + static ref DATE_HEADER_LOCATION : &'static str = "datetime.value"; + static ref DATE_RANGE_START_HEADER_LOCATION : &'static str = "datetime.range.start"; + static ref DATE_RANGE_END_HEADER_LOCATION : &'static str = "datetime.range.end"; + static ref DATE_FMT : &'static str = "%Y-%m-%dT%H:%M:%S"; +} + +impl EntryDate for Entry { + + fn delete_date(&mut self) -> Result<()> { + self.get_header_mut() + .delete(&DATE_HEADER_LOCATION) + .map(|_| ()) + .map_err_into(DEK::DeleteDateError) + } + + fn read_date(&self) -> Result<NaiveDateTime> { + self.get_header() + .read(&DATE_HEADER_LOCATION) + .map_err_into(DEK::ReadDateError) + .and_then(|v| { + match v { + Some(&Value::String(ref s)) => s.parse::<NaiveDateTime>() + .map_err_into(DEK::DateTimeParsingError), + Some(_) => Err(DEK::DateHeaderFieldTypeError.into_error()), + _ => Err(DEK::ReadDateError.into_error()), + } + }) + } + + /// Set a Date for this entry + /// + /// # Return value + /// + /// This function returns funny things, I know. But I find it more attractive to be explicit + /// what failed when here, instead of beeing nice to the user here. + /// + /// So here's a list how things are returned: + /// + /// - Err(_) if the inserting failed + /// - Ok(None) if the inserting succeeded and _did not replace an existing value_. + /// - Ok(Some(Ok(_))) if the inserting succeeded, but replaced an existing value which then got + /// parsed into a NaiveDateTime object + /// - Ok(Some(Err(_))) if the inserting succeeded, but replaced an existing value which then + /// got parsed into a NaiveDateTime object, where the parsing failed for some reason. + /// + fn set_date(&mut self, d: NaiveDateTime) -> Result<Option<Result<NaiveDateTime>>> { + let date = d.format(&DATE_FMT).to_string(); + + self.get_header_mut() + .insert(&DATE_HEADER_LOCATION, Value::String(date)) + .map(|opt| opt.map(|stri| { + match stri { + Value::String(ref s) => s.parse::<NaiveDateTime>() + .map_err_into(DEK::DateTimeParsingError), + _ => Err(DEK::DateHeaderFieldTypeError.into_error()), + } + })) + .map_err_into(DEK::SetDateError) + } + + + /// Deletes the date range + /// + /// # Warning + /// + /// First deletes the start, then the end. If the first operation fails, this might leave the + /// header in an inconsistent state. + /// + fn delete_date_range(&mut self) -> Result<()> { + let _ = try!(self + .get_header_mut() + .delete(&DATE_RANGE_START_HEADER_LOCATION) + .map(|_| ()) + .map_err_into(DEK::DeleteDateTimeRangeError)); + + self.get_header_mut() + .delete(&DATE_RANGE_END_HEADER_LOCATION) + .map(|_| ()) + .map_err_into(DEK::DeleteDateTimeRangeError) + } + + fn read_date_range(&self) -> Result<DateTimeRange> { + let start = try!(self + .get_header() + .read(&DATE_RANGE_START_HEADER_LOCATION) + .map_err_into(DEK::ReadDateTimeRangeError) + .and_then(|v| { + match v { + Some(&Value::String(ref s)) => s.parse::<NaiveDateTime>() + .map_err_into(DEK::DateTimeParsingError), + Some(_) => Err(DEK::DateHeaderFieldTypeError.into_error()), + _ => Err(DEK::ReadDateError.into_error()), + } + })); + + let end = try!(self + .get_header() + .read(&DATE_RANGE_START_HEADER_LOCATION) + .map_err_into(DEK::ReadDateTimeRangeError) + .and_then(|v| { + match v { + Some(&Value::String(ref s)) => s.parse::<NaiveDateTime>() + .map_err_into(DEK::DateTimeParsingError), + Some(_) => Err(DEK::DateHeaderFieldTypeError.into_error()), + _ => Err(DEK::ReadDateError.into_error()), + } + })); + + DateTimeRange::new(start, end) + .map_err_into(DEK::DateTimeRangeError) + } + + /// Set the date range + /// + /// # Warning + /// + /// This first sets the start, then the end. If the first operation fails, this might leave the + /// header in an inconsistent state. + /// + fn set_date_range(&mut self, start: NaiveDateTime, end: NaiveDateTime) + -> Result<Option<Result<DateTimeRange>>> + { + let start = start.format(&DATE_FMT).to_string(); + let end = end.format(&DATE_FMT).to_string(); + + let opt_old_start = try!(self + .get_header_mut() + .insert(&DATE_RANGE_START_HEADER_LOCATION, Value::String(start)) + .map(|opt| opt.map(|stri| { + match stri { + Value::String(ref s) => s.parse::<NaiveDateTime>() + .map_err_into(DEK::DateTimeParsingError), + _ => Err(DEK::DateHeaderFieldTypeError.into_error()), + } + })) + .map_err_into(DEK::SetDateTimeRangeError)); + + let opt_old_end = try!(self + .get_header_mut() + .insert(&DATE_RANGE_END_HEADER_LOCATION, Value::String(end)) + .map(|opt| opt.map(|stri| { + match stri { + Value::String(ref s) => s.parse::<NaiveDateTime>() + .map_err_into(DEK::DateTimeParsingError), + _ => Err(DEK::DateHeaderFieldTypeError.into_error()), + } + })) + .map_err_into(DEK::SetDateTimeRangeError)); + + match (opt_old_start, opt_old_end) { + (Some(Ok(old_start)), Some(Ok(old_end))) => { + let dr = DateTimeRange::new(old_start, old_end) + .map_err_into(DEK::DateTimeRangeError); + + Ok(Some(dr)) + }, + + (Some(Err(e)), _) => Err(e), + (_, Some(Err(e))) => Err(e), + _ => { + Ok(None) + }, + } + } + +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + + use super::*; + + use libimagstore::store::Store; + + use chrono::naive::NaiveDateTime; + use chrono::naive::NaiveDate; + use chrono::naive::NaiveTime; + + pub fn get_store() -> Store { + Store::new(PathBuf::from("/"), None).unwrap() + } + + #[test] + fn test_set_date() { + let store = get_store(); + + let date = { + let date = NaiveDate::from_ymd(2000, 01, 02); + let time = NaiveTime::from_hms(03, 04, 05); + + NaiveDateTime::new(date, time) + }; + + let mut entry = store.create(PathBuf::from("test")).unwrap(); + let res = entry.set_date(date); + + assert!(res.is_ok(), format!("Error: {:?}", res)); + let res = res.unwrap(); + + assert!(res.is_none()); // There shouldn't be an existing value + + // Check whether the header is set correctly + + let hdr_field = entry.get_header().read(&DATE_HEADER_LOCATION); + + assert!(hdr_field.is_ok()); + let hdr_field = hdr_field.unwrap(); + + assert!(hdr_field.is_some()); + let hdr_field = hdr_field.unwrap(); + + match *hdr_field { + Value::String(ref s) => assert_eq!("2000-01-02T03:04:05", s), + _ => assert!(false, "Wrong header type"), + } + } + + #[test] + fn test_read_date() { + use chrono::Datelike; + use chrono::Timelike; + + let store = get_store(); + + let date = { + let date = NaiveDate::from_ymd(2000, 01, 02); + let time = NaiveTime::from_hms(03, 04, 05); + + NaiveDateTime::new(date, time) + }; + + let mut entry = store.create(PathBuf::from("test")).unwrap(); + let res = entry.set_date(date); + + assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res)); + let res = res.unwrap(); + + assert!(res.is_none()); // There shouldn't be an existing value + + // same as the test above ... + + let d = entry.read_date(); + + assert!(d.is_ok(), format!("Expected Ok(_), got: {:?}", d)); + let d = d.unwrap(); + + assert_eq!(d.date().year() , 2000); + assert_eq!(d.date().month() , 01); + assert_eq!(d.date().day() , 02); + assert_eq!(d.time().hour() , 03); + assert_eq!(d.time().minute() , 04); + assert_eq!(d.time().second() , 05); + } + + #[test] + fn test_delete_date() { + let store = get_store(); + + let date = { + let date = NaiveDate::from_ymd(2000, 01, 02); + let time = NaiveTime::from_hms(03, 04, 05); + + NaiveDateTime::new(date, time) + }; + + let mut entry = store.create(PathBuf::from("test")).unwrap(); + let res = entry.set_date(date); + + assert!(res.is_ok(), format!("Expected Ok(_), got: {:?}", res)); + let res = res.unwrap(); + assert!(res.is_none()); // There shouldn't be an existing value + + assert!(entry.delete_date().is_ok()); + + let hdr_field = entry.get_header().read(&DATE_HEADER_LOCATION); + + assert!(hdr_field.is_ok()); + let hdr_field = hdr_field.unwrap(); + + assert!(hdr_field.is_none()); + } +} + |