//! This module contains the cache manager. use std::fs::File; use std::io::{stdin, stdout, BufRead, BufReader, Write}; use std::num::Wrapping; use std::path::PathBuf; use colored::*; use walkdir::WalkDir; use crate::git::GCLONE_PATH; use crate::{Error, Result}; /// Flushes the stdout. fn flush_stdout() { stdout().flush().ok(); } /// The cache of gclone. /// /// When running the command `cdg`, if the computer just booted, finding all the directories can be /// quite slow. The cache contains the list of entries in the git directory to avoid re-finding /// them every time. pub struct Cache(Vec); impl Cache { /// Generates the cache by traversing the files in the git directory. pub fn generate() -> Cache { Cache( WalkDir::new(&*GCLONE_PATH) .max_depth(3) .into_iter() .filter_map(|x| x.ok()) .map(|x| x.path().display().to_string()) .collect(), ) } /// Reads the cache file. pub fn read() -> Result { let mut path = PathBuf::from(&*GCLONE_PATH); path.push(".cdgcache"); let file = File::open(&path)?; let file = BufReader::new(file); let mut values = vec![]; for line in file.lines() { if let Ok(l) = line { values.push(l); } } Ok(Cache(values)) } /// Reads the cache file, and if it failed, generate a new cache. pub fn read_or_generate() -> Cache { match Cache::read() { Ok(c) => c, Err(_) => Cache::generate(), } } /// Writes the current content of the cache to the cache file. pub fn write(&self) -> Result<()> { let mut path = PathBuf::from(&*GCLONE_PATH); path.push(".cdgcache"); let mut file = File::create(path)?; for line in &self.0 { writeln!(file, "{}", line)?; } Ok(()) } /// Search a directory in the cache, and returns all matching directories. pub fn find(&self, dirname: &str) -> Vec { let dirname = &format!("/{}", dirname); let mut matches = vec![]; for line in &self.0 { if line.ends_with(dirname) { matches.push(line.clone()); } } matches } /// Searches a directory, if multiple where found, prompt user for answer. pub fn find_interactive(&self, dirname: &str) -> Result { let matches = self.find(dirname); match matches.len() { 0 => Err(Error::NoSuchDirectory), 1 => Ok(matches[0].clone()), len => { info!("{}", "multiple entries found, please select one"); for (i, j) in matches.iter().enumerate() { eprintln!( "{} {}", &format!("[{}]", i + 1).bold().blue(), &format!("{}", j).yellow() ); } eprint!("{}", "Enter your choice: ".bold()); flush_stdout(); let mut string = String::new(); stdin().read_line(&mut string)?; // Pop the trailing \n of the string string.pop(); // Use wrapping to move 0 to std::usize::MAX which is ok. let value = (Wrapping(string.parse::()?) - Wrapping(1)).0; if value >= len { return Err(Error::OutsideRange); } Ok(matches[value].clone()) } } } /// Appends a value to the cache. pub fn append(&mut self, value: String) { self.0.push(value); } }