// // imag - the personal information management suite for the commandline // Copyright (C) 2015, 2016 Matthias Beyer 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::datetime::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; fn set_date(&mut self, d: NaiveDateTime) -> Result>>; fn delete_date_range(&mut self) -> Result<()>; fn read_date_range(&self) -> Result; fn set_date_range(&mut self, start: NaiveDateTime, end: NaiveDateTime) -> Result>>; } lazy_static! { static ref DATE_HEADER_LOCATION : &'static str = "date.value"; static ref DATE_RANGE_START_HEADER_LOCATION : &'static str = "date.range.start"; static ref DATE_RANGE_END_HEADER_LOCATION : &'static str = "date.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 { self.get_header() .read(&DATE_HEADER_LOCATION) .map_err_into(DEK::ReadDateError) .and_then(|v| { match v { &Value::String(ref s) => s.parse::() .map_err_into(DEK::DateTimeParsingError), _ => Err(DEK::DateHeaderFieldTypeError.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>> { 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::() .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 { let start = try!(self .get_header() .read(&DATE_RANGE_START_HEADER_LOCATION) .map_err_into(DEK::ReadDateTimeRangeError) .and_then(|v| { match v { &Value::String(ref s) => s.parse::() .map_err_into(DEK::DateTimeParsingError), _ => Err(DEK::DateHeaderFieldTypeError.into_error()), } })); let end = try!(self .get_header() .read(&DATE_RANGE_START_HEADER_LOCATION) .map_err_into(DEK::ReadDateTimeRangeError) .and_then(|v| { match v { &Value::String(ref s) => s.parse::() .map_err_into(DEK::DateTimeParsingError), _ => Err(DEK::DateHeaderFieldTypeError.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>> { 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::() .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::() .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::datetime::NaiveDateTime; use chrono::naive::date::NaiveDate; use chrono::naive::time::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(); match *hdr_field { Value::String(ref s) => assert_eq!("2000-01-02T03:04:05", s), _ => assert!(false, "Wrong header type"), } } }