diff options
Diffstat (limited to 'src/actions/index')
-rw-r--r-- | src/actions/index/action.rs | 182 | ||||
-rw-r--r-- | src/actions/index/bucketable.rs | 162 | ||||
-rw-r--r-- | src/actions/index/indextime.rs | 38 | ||||
-rw-r--r-- | src/actions/index/mod.rs | 18 |
4 files changed, 0 insertions, 400 deletions
diff --git a/src/actions/index/action.rs b/src/actions/index/action.rs deleted file mode 100644 index b32a248..0000000 --- a/src/actions/index/action.rs +++ /dev/null @@ -1,182 +0,0 @@ -use chrono::prelude::*; -use crate::icalwrap::*; -use std::collections::HashMap; -use std::fs; -use std::path::{Path,PathBuf}; -use std::time::SystemTime; -use walkdir::DirEntry; - -use crate::defaults::*; -use super::{IndexArgs, indextime}; -use crate::utils::fileutil; -use crate::utils::lock; -use crate::utils::misc; -use crate::KhResult; - -pub fn action_index(args: &IndexArgs) -> KhResult<()> { - let reindex = args.reindex; - let indexpath = match &args.path { - Some(path) => path.clone(), - None => get_caldir(), - }; - - index_dir(&indexpath, reindex) -} - -fn add_buckets_for_calendar(buckets: &mut HashMap<String, Vec<String>>, cal: &IcalVCalendar) { - use super::bucketable::Bucketable; - use super::bucketable::Merge; - - match cal.get_buckets() { - Ok(cal_buckets) => buckets.merge(cal_buckets), - Err(error) => { - warn!("{}", error) - } - } -} - -fn index_dir(dir: &Path, reindex: bool) -> KhResult<()> { - use std::time::Instant; - - let _lock = lock::lock_file_exclusive(&get_indexlockfile())?; - - info!("Recursively indexing '.ics' files in directory: {}", dir.to_string_lossy()); - if !dir.exists() { - Err(format!("Directory doesn't exist: {}", dir.to_string_lossy()))?; - } - - let now = Instant::now(); - let start_time = Utc::now(); - - let last_index_time = if reindex { - debug!("Forced reindex, indexing all files"); - None - } else { - let last_index_time = indextime::get_index_time(); - match last_index_time { - Some(time) => debug!("Previously indexed {}, indexing newer files only", time.with_timezone(&Local)), - None => debug!("No previous index time, indexing all files"), - } - last_index_time - }; - - let modified_since = last_index_time.map(|time| time.timestamp()).unwrap_or(0); - let ics_files = get_ics_files(dir, modified_since); - - let buckets = read_buckets(ics_files); - - let indexdir = get_indexdir(); - let clear_index_dir = last_index_time.is_none(); - prepare_index_dir(&indexdir, clear_index_dir)?; - - write_index(&indexdir, &buckets); - info!("Index written in {}ms", misc::format_duration(&now.elapsed())); - - indextime::write_index_time(&start_time); - - Ok(()) -} - -pub fn get_ics_files(dir: &Path, modified_since: i64) -> impl Iterator<Item = PathBuf> { - use walkdir::WalkDir; - - WalkDir::new(dir) - .follow_links(true) - .into_iter() - .filter_entry(move |entry| accept_entry(entry, modified_since)) - .filter_map(|e| e.ok()) - .filter(|e| e.file_type().is_file()) - .filter(|e| e.path().extension().map_or(false, |extension| extension == "ics")) - .map(|entry| entry.into_path()) -} - -fn accept_entry(dir_entry: &DirEntry, modified_since: i64) -> bool { - if dir_entry.path().is_dir() { - return true; - } - dir_entry.metadata() - .map_err(|err| err.into()) // transform to io::Error - .and_then(|metadata| metadata.modified()) - .map(|modified| modified.duration_since(SystemTime::UNIX_EPOCH).unwrap()) - .map(|modified| modified.as_secs() as i64) - .map(|modified| modified > modified_since) - .unwrap_or(false) -} - -fn read_buckets(ics_files: impl Iterator<Item = PathBuf>) -> HashMap<String, Vec<String>> { - let mut buckets: HashMap<String, Vec<String>> = HashMap::new(); - - let mut total_files = 0; - for file in ics_files { - debug!("Indexing file: {:?}", file); - match fileutil::read_file_to_string(&file) { - Ok(content) => { - total_files += 1; - match IcalVCalendar::from_str(&content, Some(&file)) { - Ok(cal) => add_buckets_for_calendar(&mut buckets, &cal), - Err(error) => error!("{:?}: {}", file, error) - } - } - Err(error) => error!("{}", error), - } - } - - info!("Loaded {} files into {} buckets", total_files, buckets.len()); - buckets -} - -fn write_index(index_dir: &Path, buckets: &HashMap<String, Vec<String>>) { - for (key, val) in buckets.iter() { - let bucketfile = bucket_file(index_dir, key); - trace!("Writing bucket: {}", key); - let content = &[&val.join("\n"), "\n"].concat(); - if let Err(error) = fileutil::append_file(&bucketfile, content) { - error!("{}", error); - return; - } - } -} - -fn bucket_file(index_dir: &Path, key: &str) -> PathBuf { - let mut result = PathBuf::from(index_dir); - result.push(key); - result -} - -fn prepare_index_dir(indexdir: &Path, clear_index_dir: bool) -> Result<(), std::io::Error> { - if indexdir.exists() && clear_index_dir { - info!("Clearing index directory: {}", indexdir.to_string_lossy()); - fs::remove_dir_all(&indexdir)? - } - - if !indexdir.exists() { - info!("Creating index directory: {}", indexdir.to_string_lossy()); - fs::create_dir(&indexdir)?; - } - - Ok(()) -} - -#[cfg(test)] -mod integration { - use super::*; - - use crate::testutils::prepare_testdir; - use assert_fs::prelude::*; - use crate::cli::CommandLine; - use crate::cli::Command::Index; - use structopt::StructOpt; - - #[test] - fn test_index() { - let testdir = prepare_testdir("testdir"); - - let args = CommandLine::from_iter(&["khaleesi", "index"]); - if let Index(x) = args.cmd { - action_index(&x).unwrap(); - } - - testdir.child(".khaleesi/index/2018-W50").assert("1544740200 twodaysacrossbuckets.ics\n"); - testdir.child(".khaleesi/index/2018-W51").assert("1544740200 twodaysacrossbuckets.ics\n"); - } -} diff --git a/src/actions/index/bucketable.rs b/src/actions/index/bucketable.rs deleted file mode 100644 index 8644c1e..0000000 --- a/src/actions/index/bucketable.rs +++ /dev/null @@ -1,162 +0,0 @@ -use chrono::{Local, Date, Datelike, Duration}; -use std::collections::HashMap; -use std::{hash, cmp}; - -use crate::icalwrap::IcalVCalendar; -//use crate::icalwrap::IcalVEvent; -use crate::utils::misc; -use crate::khline::KhLine; -use crate::khevent::KhEvent; - -pub trait Bucketable { - fn get_buckets(&self) -> Result<HashMap<String, Vec<String>>, String>; - - fn buckets_for_interval(mut start: Date<Local>, end: Date<Local>) -> Vec<String> { - let mut buckets = Vec::new(); - - while start.iso_week() <= end.iso_week() { - let bucket = misc::get_bucket_for_date(start); - buckets.push(bucket); - start = start.checked_add_signed(Duration::days(7)).unwrap(); - } - buckets - } -} - -impl Bucketable for KhEvent { - fn get_buckets(&self) -> Result<HashMap<String, Vec<String>>, String> { - let mut result: HashMap<String, Vec<String>> = HashMap::new(); - - let start_date: Date<Local> = self.get_start().ok_or_else(|| format!("Invalid DTSTART in {}", self.get_uid()))?.into(); - //TODO - //let mut end_date: Date<Local> = self.get_end().map(|date| date.into()).unwrap_or(start_date); - - let end_date = self.get_last_relevant_date().map(|date| date.into()).unwrap_or(start_date); - // end-dtimes are non-inclusive - // so in case of date-only events, the last day of the event is dtend-1 - //if self.is_allday() { - //end_date = end_date.pred(); - //} - - let buckets = Self::buckets_for_interval(start_date, end_date); - let khline = KhLine::from(self); - for bucketid in buckets { - result - .entry(bucketid) - .and_modify(|items| items.push(khline.to_string())) - .or_insert_with(|| vec!(khline.to_string())); - } - - if self.is_recur_master() { - for instance in self.get_recur_instances() { - let recur_buckets = instance.get_buckets()?; - result.merge(recur_buckets) - } - } - - for vec in result.values_mut() { - vec.dedup() - } - Ok(result) - } -} - -impl Bucketable for IcalVCalendar { - fn get_buckets(&self) -> Result<HashMap<String, Vec<String>>, String> { - let mut result: HashMap<String, Vec<String>> = HashMap::new(); - for event in self.events_iter() { - let event = KhEvent::from_event(event); - //let recur_buckets = event.get_buckets()?; - let recur_buckets = event.get_buckets()?; - result.merge(recur_buckets); - } - Ok(result) - } -} - -pub trait Merge<K> -where K: cmp::Eq + hash::Hash -{ - fn merge(&mut self, other: HashMap<K, Vec<String>>); -} - -impl<K, S> Merge<K> for HashMap<K, Vec<String>, S> -where K: cmp::Eq + hash::Hash, - S: std::hash::BuildHasher -{ - fn merge(&mut self, other: HashMap<K, Vec<String>>) { - for (key, mut lines) in other.into_iter() { - self - .entry(key) - .and_modify(|items| items.append(&mut lines)) - .or_insert(lines); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn merge_test() { - let mut map_a: HashMap<&str, Vec<String>> = HashMap::new(); - let mut map_b: HashMap<&str, Vec<String>> = HashMap::new(); - - let key = "key"; - map_a.insert(&key, vec!["a".to_string(), "b".to_string()]); - map_b.insert(&key, vec!["c".to_string(), "d".to_string()]); - - map_a.merge(map_b); - assert_eq!(map_a.get(&key).unwrap(), &vec!["a".to_string(), "b".to_string(), "c".to_string(), "d".to_string()]); - } - - #[test] - fn buckets_multi_day_allday() { - use crate::testdata; - use std::path::PathBuf; - - let path = PathBuf::from("test/path"); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_MULTIDAY_ALLDAY, Some(&path)).unwrap(); - - let event_buckets = cal.get_principal_khevent().get_buckets().unwrap(); - - assert_eq!(2, event_buckets.len()); - - let mut bucket_names = event_buckets.keys().collect::<Vec<&String>>(); - bucket_names.sort_unstable(); - assert_eq!(vec!("2007-W26", "2007-W27"), bucket_names); - - let cal_buckets = cal.get_buckets().unwrap(); - assert_eq!(event_buckets, cal_buckets); - } - - #[test] - fn buckets_single_event() { - use crate::testdata; - use std::path::PathBuf; - - let path = PathBuf::from("test/path"); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_ONE_MEETING, Some(&path)).unwrap(); - - let comp_buckets = cal.get_buckets().unwrap(); - assert_eq!(vec!("1997-W13"), comp_buckets.keys().collect::<Vec<&String>>()); - } - - #[test] - fn buckets_simple_recurring_event() { - use crate::testdata; - use std::path::PathBuf; - - let path = PathBuf::from("test/path"); - let cal = IcalVCalendar::from_str(testdata::TEST_EVENT_RECUR, Some(&path)).unwrap(); - - let event = cal.get_principal_khevent(); - let event_buckets = event.get_buckets().unwrap(); - let cal_buckets = cal.get_buckets().unwrap(); - assert_eq!(event_buckets, cal_buckets); - let mut cal_bucket_names = cal_buckets.keys().collect::<Vec<&String>>(); - cal_bucket_names.sort_unstable(); - assert_eq!(vec!("2018-W41", "2018-W42", "2018-W43", "2018-W44", "2018-W45", "2018-W46", "2018-W47", "2018-W48", "2018-W49", "2018-W50"), cal_bucket_names); - } -} diff --git a/src/actions/index/indextime.rs b/src/actions/index/indextime.rs deleted file mode 100644 index 6d56688..0000000 --- a/src/actions/index/indextime.rs +++ /dev/null @@ -1,38 +0,0 @@ -use std::fs; -use std::io::{Read,Write}; -use chrono::prelude::*; - -use crate::defaults::*; - -pub fn write_index_time(index_time: &DateTime<Utc>) { - let mut timefile = fs::File::create(get_indextimefile()).unwrap(); - timefile.write_all(format!("{}\n", index_time.timestamp()).as_bytes()).unwrap(); -} - -pub fn get_index_time() -> Option<DateTime<Utc>> { - let mut timefile = fs::File::open(get_indextimefile()).ok()?; - let mut timestamp_str = String::new(); - timefile.read_to_string(&mut timestamp_str).ok()?; - let timestamp = timestamp_str.trim().parse::<i64>().ok()?; - Some(Utc.timestamp(timestamp, 0)) -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::testutils; - use assert_fs::prelude::*; - - #[test] - fn test_write_read() { - let testdir = testutils::prepare_testdir("testdir"); - - let timestamp = Utc.ymd(1990,01,01).and_hms(1, 1, 0); - write_index_time(×tamp); - testdir.child(".khaleesi/index-time").assert("631155660\n"); - - let indextime = get_index_time(); - assert_eq!(Some(timestamp), indextime); - } -} diff --git a/src/actions/index/mod.rs b/src/actions/index/mod.rs deleted file mode 100644 index cc4c0ae..0000000 --- a/src/actions/index/mod.rs +++ /dev/null @@ -1,18 +0,0 @@ -pub mod action; -mod indextime; -mod bucketable; - -pub use self::action::action_index; - -use structopt::StructOpt; -use std::path::PathBuf; - -#[derive(Debug, StructOpt)] -pub struct IndexArgs { - /// Rebuild index - #[structopt(short = "r", long = "reindex")] - pub reindex: bool, - /// index path - #[structopt(name = "path", parse(from_os_str))] - pub path: Option<PathBuf>, -} |