kickoff 0.6.0

Fast and minimal program launcher
Documentation
extern crate xdg;

use log::*;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::time::SystemTime;
use xdg::BaseDirectories;

use crate::selection::Element;

#[derive(Debug, Serialize, Deserialize)]
pub struct HistoryEntry {
    pub name: String,
    pub value: String,
    pub num_used: usize,
}

#[derive(Debug)]
pub struct History {
    entries: Vec<HistoryEntry>,
    path: PathBuf,
}

impl History {
    pub fn as_vec(&self) -> &Vec<HistoryEntry> {
        &self.entries
    }

    pub fn load(path: Option<PathBuf>, decrease_interval: u64) -> Result<Self, std::io::Error> {
        let history_path = if let Some(path) = path {
            path
        } else {
            let xdg_dirs = BaseDirectories::with_prefix("kickoff")?;
            if let Some(path) = xdg_dirs.find_cache_file("default.csv") {
                path
            } else {
                return Ok(History {
                    entries: Vec::new(),
                    path: xdg_dirs.place_cache_file("default.csv")?,
                });
            }
        };

        let mut res = History {
            entries: Vec::new(),
            path: history_path.clone(),
        };

        if history_path.exists() {
            let last_modified = history_path.metadata()?.modified()?;
            let interval_diff = if decrease_interval > 0 {
                SystemTime::now()
                    .duration_since(SystemTime::UNIX_EPOCH)
                    .unwrap()
                    .as_secs()
                    / (3600 * decrease_interval)
                    - last_modified
                        .duration_since(SystemTime::UNIX_EPOCH)
                        .unwrap()
                        .as_secs()
                        / (3600 * decrease_interval)
            } else {
                0
            };

            let mut rdr = csv::Reader::from_path(history_path).unwrap();
            for result in rdr.deserialize() {
                let mut record: HistoryEntry = result?;
                record.num_used = record.num_used.saturating_sub(interval_diff as usize);
                if record.num_used > 0 {
                    res.entries.push(record);
                }
            }
        } else {
            info!("History file does not exists, will be created on saving");
        }

        Ok(res)
    }

    pub fn inc(&mut self, element: &Element) {
        if let Some(entry) = self.entries.iter_mut().find(|x| x.name == element.name) {
            entry.num_used += 1;
            entry.value = element.value.to_owned();
        } else {
            self.entries.push(HistoryEntry {
                name: element.name.to_owned(),
                value: element.value.to_owned(),
                num_used: 1,
            })
        }
    }

    pub fn save(&self) -> Result<(), std::io::Error> {
        let mut wtr = csv::Writer::from_path(&self.path)?;
        for entry in &self.entries {
            wtr.serialize(entry)?;
        }
        wtr.flush()?;

        Ok(())
    }
}