diff --git a/src/format/chromium_pdf/mod.rs b/src/format/chromium_pdf/mod.rs index 77f39b5..748d8a1 100644 --- a/src/format/chromium_pdf/mod.rs +++ b/src/format/chromium_pdf/mod.rs @@ -8,7 +8,7 @@ use crate::references::configuration::keys::{ PDF_PAGE_SCALE, PDF_PAGE_WIDTH, }; use crate::references::configuration::Configuration; -use crate::utils::downloads::get_cached_path; +use crate::utils::caching::CacheStorage; use headless_chrome::protocol::page::PrintToPdfOptions; use headless_chrome::{Browser, LaunchOptionsBuilder, Tab}; use std::fs; @@ -22,8 +22,9 @@ pub mod result; /// Renders the document to pdf and returns the resulting bytes pub fn render_to_pdf(document: Document) -> PdfRenderingResult> { + let cache = CacheStorage::new(); let mut file_path = PathBuf::from(format!("tmp-document.html")); - file_path = get_cached_path(file_path).with_extension("html"); + file_path = cache.get_file_path(&file_path); let mut mathjax = false; if let Some(entry) = document.config.get_entry(INCLUDE_MATHJAX) { diff --git a/src/main.rs b/src/main.rs index 0b3db3b..3d49d4c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,16 +6,38 @@ use snekdown::elements::Document; use snekdown::format::html::html_writer::HTMLWriter; use snekdown::format::html::to_html::ToHtml; use snekdown::parser::ParserOptions; +use snekdown::utils::caching::CacheStorage; use snekdown::Parser; use std::fs::{File, OpenOptions}; use std::io::{BufWriter, Write}; use std::path::PathBuf; +use std::process::exit; use std::sync::mpsc::channel; use std::time::{Duration, Instant}; use structopt::StructOpt; #[derive(StructOpt, Debug)] struct Opt { + #[structopt(subcommand)] + sub_command: SubCommand, +} + +#[derive(StructOpt, Debug)] +#[structopt()] +enum SubCommand { + /// Watch the document and its imports and render on change. + Watch(RenderOptions), + + /// Parse and render the document. + Render(RenderOptions), + + /// Clears the cache directory + ClearCache, +} + +#[derive(StructOpt, Debug)] +#[structopt()] +struct RenderOptions { /// Path to the input file #[structopt(parse(from_os_str))] input: PathBuf, @@ -31,19 +53,6 @@ struct Opt { /// Don't use the cache #[structopt(long)] no_cache: bool, - - #[structopt(subcommand)] - sub_command: Option, -} - -#[derive(StructOpt, Debug)] -#[structopt()] -enum SubCommand { - /// Watch the document and its imports and render on change. - Watch, - - /// Default. Parse and render the document. - Render, } fn main() { @@ -68,19 +77,16 @@ fn main() { ) }) .init(); - if !opt.input.exists() { - log::error!( - "The input file {} could not be found", - opt.input.to_str().unwrap() - ); - return; - } match &opt.sub_command { - Some(SubCommand::Render) | None => { + SubCommand::Render(opt) => { let _ = render(&opt); } - Some(SubCommand::Watch) => watch(&opt), + SubCommand::Watch(opt) => watch(&opt), + SubCommand::ClearCache => { + let cache = CacheStorage::new(); + cache.clear().expect("Failed to clear cache"); + } }; } @@ -95,7 +101,7 @@ fn get_level_style(level: Level) -> colored::Color { } /// Watches a file with all of its imports and renders on change -fn watch(opt: &Opt) { +fn watch(opt: &RenderOptions) { let parser = render(opt); let (tx, rx) = channel(); let mut watcher = watcher(tx, Duration::from_millis(250)).unwrap(); @@ -112,7 +118,15 @@ fn watch(opt: &Opt) { } /// Renders the document to the output path -fn render(opt: &Opt) -> Parser { +fn render(opt: &RenderOptions) -> Parser { + if !opt.input.exists() { + log::error!( + "The input file {} could not be found", + opt.input.to_str().unwrap() + ); + + exit(1) + } let start = Instant::now(); let mut parser = Parser::with_defaults( @@ -142,7 +156,7 @@ fn render(opt: &Opt) -> Parser { } #[cfg(not(feature = "pdf"))] -fn render_format(opt: &Opt, document: Document, writer: BufWriter) { +fn render_format(opt: &RenderOptions, document: Document, writer: BufWriter) { match opt.format.as_str() { "html" => render_html(document, writer), _ => log::error!("Unknown format {}", opt.format), @@ -150,7 +164,7 @@ fn render_format(opt: &Opt, document: Document, writer: BufWriter) { } #[cfg(feature = "pdf")] -fn render_format(opt: &Opt, document: Document, writer: BufWriter) { +fn render_format(opt: &RenderOptions, document: Document, writer: BufWriter) { match opt.format.as_str() { "html" => render_html(document, writer), "pdf" => render_pdf(document, writer), diff --git a/src/utils/caching.rs b/src/utils/caching.rs new file mode 100644 index 0000000..ce8fb3e --- /dev/null +++ b/src/utils/caching.rs @@ -0,0 +1,63 @@ +use platform_dirs::{AppDirs, AppUI}; +use std::collections::hash_map::DefaultHasher; +use std::fs; +use std::hash::{Hash, Hasher}; +use std::io; +use std::path::PathBuf; + +#[derive(Clone, Debug)] +pub struct CacheStorage { + location: PathBuf, +} + +impl CacheStorage { + pub fn new() -> Self { + lazy_static::lazy_static! { + static ref APP_DIRS: AppDirs = AppDirs::new(Some("snekdown"), AppUI::CommandLine).unwrap(); + } + + Self { + location: APP_DIRS.cache_dir.clone(), + } + } + + /// Returns the cache path for a given file + pub fn get_file_path(&self, path: &PathBuf) -> PathBuf { + let mut hasher = DefaultHasher::new(); + path.hash(&mut hasher); + let mut file_name = PathBuf::from(format!("{:x}", hasher.finish())); + + if let Some(extension) = path.extension() { + file_name.set_extension(extension); + } + + return self.location.join(PathBuf::from(file_name)); + } + + /// Returns if the given file exists in the cache + pub fn has_file(&self, path: &PathBuf) -> bool { + let cache_path = self.get_file_path(path); + + cache_path.exists() + } + + /// Writes into the corresponding cache file + pub fn read(&self, path: &PathBuf) -> io::Result> { + let cache_path = self.get_file_path(path); + + fs::read(cache_path) + } + + /// Reads the corresponding cache file + pub fn write>(&self, path: &PathBuf, contents: R) -> io::Result<()> { + let cache_path = self.get_file_path(path); + + fs::write(cache_path, contents) + } + + /// Clears the cache directory by deleting and recreating it + pub fn clear(&self) -> io::Result<()> { + fs::remove_dir_all(&self.location)?; + fs::create_dir(&self.location) + } +} diff --git a/src/utils/downloads.rs b/src/utils/downloads.rs index 118f26d..4b0f535 100644 --- a/src/utils/downloads.rs +++ b/src/utils/downloads.rs @@ -1,10 +1,7 @@ +use crate::utils::caching::CacheStorage; use indicatif::{ProgressBar, ProgressStyle}; -use platform_dirs::{AppDirs, AppUI}; use rayon::prelude::*; -use std::collections::hash_map::DefaultHasher; -use std::fs; use std::fs::read; -use std::hash::{Hash, Hasher}; use std::path::PathBuf; use std::sync::{Arc, Mutex}; @@ -60,6 +57,7 @@ pub struct PendingDownload { pub(crate) path: String, pub(crate) data: Option>, pub(crate) use_cache: bool, + cache: CacheStorage, } impl PendingDownload { @@ -68,6 +66,7 @@ impl PendingDownload { path, data: None, use_cache: true, + cache: CacheStorage::new(), } } @@ -98,22 +97,18 @@ impl PendingDownload { /// Stores the data to a cache file to retrieve it later fn store_to_cache(&self, data: &Vec) { if self.use_cache { - let cache_file = get_cached_path(PathBuf::from(&self.path)); - log::debug!("Writing to cache {} -> {:?}", self.path, cache_file); - fs::write(&cache_file, data.clone()).unwrap_or_else(|_| { - log::warn!( - "Failed to write file to cache: {} -> {:?}", - self.path, - cache_file - ) - }); + let path = PathBuf::from(&self.path); + self.cache + .write(&path, data.clone()) + .unwrap_or_else(|_| log::warn!("Failed to write file to cache: {}", self.path)); } } fn read_from_cache(&self) -> Option> { - let cache_path = get_cached_path(PathBuf::from(&self.path)); - if cache_path.exists() && self.use_cache { - read(cache_path).ok() + let path = PathBuf::from(&self.path); + + if self.cache.has_file(&path) && self.use_cache { + self.cache.read(&path).ok() } else { None } @@ -128,19 +123,3 @@ impl PendingDownload { .map(|b| b.to_vec()) } } - -pub fn get_cached_path(path: PathBuf) -> PathBuf { - lazy_static::lazy_static! { - static ref APP_DIRS: AppDirs = AppDirs::new(Some("snekdown"), AppUI::CommandLine).unwrap(); - } - let mut hasher = DefaultHasher::new(); - path.hash(&mut hasher); - let file_name = PathBuf::from(format!("{:x}", hasher.finish())); - - if !APP_DIRS.cache_dir.is_dir() { - fs::create_dir(&APP_DIRS.cache_dir) - .unwrap_or_else(|_| log::warn!("Failed to create cache dir {:?}", APP_DIRS.cache_dir)) - } - - APP_DIRS.cache_dir.join(file_name) -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 20780f3..d1229a1 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,4 @@ +pub mod caching; pub mod downloads; pub mod macros; pub mod parsing;