summaryrefslogtreecommitdiffstats
path: root/src/actions/index
diff options
context:
space:
mode:
Diffstat (limited to 'src/actions/index')
-rw-r--r--src/actions/index/action.rs182
-rw-r--r--src/actions/index/bucketable.rs162
-rw-r--r--src/actions/index/indextime.rs38
-rw-r--r--src/actions/index/mod.rs18
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(&timestamp);
- 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>,
-}