From ad554bab89a3ba86de5447be1a2b341e06424546 Mon Sep 17 00:00:00 2001 From: sxyazi Date: Thu, 3 Aug 2023 22:04:50 +0800 Subject: [PATCH] feat: pre-cache PDF files --- adaptor/src/image.rs | 12 +++------- core/src/external/ffmpegthumbnailer.rs | 4 ++-- core/src/external/pdftoppm.rs | 27 ++++++++++----------- core/src/manager/manager.rs | 1 + core/src/manager/preview.rs | 20 +++++++--------- core/src/tasks/scheduler.rs | 7 ++++++ core/src/tasks/tasks.rs | 13 ++++++++++ core/src/tasks/workers/precache.rs | 33 +++++++++++++++++++++++++- cspell.json | 2 +- shared/src/mime.rs | 20 +++++++++------- 10 files changed, 90 insertions(+), 49 deletions(-) diff --git a/adaptor/src/image.rs b/adaptor/src/image.rs index cc645bdc..38d8533d 100644 --- a/adaptor/src/image.rs +++ b/adaptor/src/image.rs @@ -26,16 +26,10 @@ impl Image { }) }); - Ok(img.await??) + img.await? } - pub async fn precache(path: &Path) -> Result<()> { - let cache = Self::cache(path); - if fs::metadata(&cache).await.is_ok() { - return Ok(()); - } - - let img = fs::read(&path).await?; + pub async fn precache(img: Vec, cache: PathBuf) -> Result<()> { let result = tokio::task::spawn_blocking(move || { let img = image::load_from_memory(&img)?; let (w, h) = (PREVIEW.max_width, PREVIEW.max_height); @@ -46,7 +40,7 @@ impl Image { Ok::<(), Error>(()) }); - Ok(result.await??) + result.await? } #[inline] diff --git a/core/src/external/ffmpegthumbnailer.rs b/core/src/external/ffmpegthumbnailer.rs index 7a909dcb..21d48979 100644 --- a/core/src/external/ffmpegthumbnailer.rs +++ b/core/src/external/ffmpegthumbnailer.rs @@ -4,10 +4,10 @@ use anyhow::{bail, Result}; use config::PREVIEW; use tokio::process::Command; -pub async fn ffmpegthumbnailer(path: &Path, dest: &Path) -> Result<()> { +pub async fn ffmpegthumbnailer(src: &Path, dest: &Path) -> Result<()> { let output = Command::new("ffmpegthumbnailer") .arg("-i") - .arg(path) + .arg(src) .arg("-o") .arg(dest) .args(["-q", "6", "-c", "jpeg", "-s", &PREVIEW.max_width.to_string()]) diff --git a/core/src/external/pdftoppm.rs b/core/src/external/pdftoppm.rs index ba92a415..b9eb9de4 100644 --- a/core/src/external/pdftoppm.rs +++ b/core/src/external/pdftoppm.rs @@ -1,22 +1,19 @@ use std::path::Path; +use adaptor::Image; use anyhow::{bail, Result}; use tokio::process::Command; -pub async fn pdftoppm(path: &Path, dest: &Path) -> Result<()> { - let output = Command::new("pdftoppm") - .args(["-png", "-singlefile"]) - .arg(path) - .arg(dest) - .kill_on_drop(true) - .output() - .await?; +pub async fn pdftoppm(src: &Path, dest: &Path) -> Result<()> { + let output = Command::new("pdftoppm") + .args(["-singlefile", "-jpeg", "-jpegopt", "quality=75"]) + .arg(src) + .kill_on_drop(true) + .output() + .await?; - if !output.status.success() { - bail!( - "failed to get pdf: {}", - String::from_utf8_lossy(&output.stderr) - ); - } - Ok(()) + if !output.status.success() { + bail!("failed to generate PDF thumbnail: {}", String::from_utf8_lossy(&output.stderr)); + } + Ok(Image::precache(output.stdout, dest.to_path_buf()).await?) } diff --git a/core/src/manager/manager.rs b/core/src/manager/manager.rs index 6b76dc3b..d3aec58c 100644 --- a/core/src/manager/manager.rs +++ b/core/src/manager/manager.rs @@ -304,6 +304,7 @@ impl Manager { tasks.precache_image(&mimes); tasks.precache_video(&mimes); + tasks.precache_pdf(&mimes); self.mimetype.extend(mimes); true diff --git a/core/src/manager/preview.rs b/core/src/manager/preview.rs index 4fff95de..28e35647 100644 --- a/core/src/manager/preview.rs +++ b/core/src/manager/preview.rs @@ -59,13 +59,13 @@ impl Preview { let (path, mime) = (path.to_path_buf(), mime.to_owned()); self.handle = Some(tokio::spawn(async move { let result = match kind { - MimeKind::Dir => Self::folder(&path).await, - MimeKind::JSON => Self::json(&path).await.map(PreviewData::Text), - MimeKind::Text => Self::highlight(&path).await.map(PreviewData::Text), - MimeKind::Image => Self::image(&path).await, - MimeKind::Pdf => Self::pdf(&path).await, - MimeKind::Video => Self::video(&path).await, MimeKind::Archive => Self::archive(&path).await.map(PreviewData::Text), + MimeKind::Dir => Self::folder(&path).await, + MimeKind::Image => Self::image(&path).await, + MimeKind::Video => Self::video(&path).await, + MimeKind::JSON => Self::json(&path).await.map(PreviewData::Text), + MimeKind::PDF => Self::pdf(&path).await, + MimeKind::Text => Self::highlight(&path).await.map(PreviewData::Text), MimeKind::Others => Err(anyhow!("Unsupported mimetype: {mime}")), }; @@ -118,17 +118,13 @@ impl Preview { Self::image(&cache).await } - pub async fn pdf(path: &Path) -> Result { + pub async fn pdf(path: &Path) -> Result { let cache = Image::cache(path); if fs::metadata(&cache).await.is_err() { external::pdftoppm(path, &cache).await?; } - let mut dest = cache.into_os_string(); - dest.push(".png"); - - let dest = Path::new(&dest); - Self::image(dest).await + Self::image(&cache).await } pub async fn json(path: &Path) -> Result { diff --git a/core/src/tasks/scheduler.rs b/core/src/tasks/scheduler.rs index 9deeca50..d8bde035 100644 --- a/core/src/tasks/scheduler.rs +++ b/core/src/tasks/scheduler.rs @@ -375,4 +375,11 @@ impl Scheduler { self.precache.video(id, targets).ok(); } + + pub(super) fn precache_pdf(&self, targets: Vec) { + let name = format!("Precache of {} PDF files", targets.len()); + let id = self.running.write().add(name); + + self.precache.pdf(id, targets).ok(); + } } diff --git a/core/src/tasks/tasks.rs b/core/src/tasks/tasks.rs index 0fd10e6c..36a78404 100644 --- a/core/src/tasks/tasks.rs +++ b/core/src/tasks/tasks.rs @@ -265,6 +265,19 @@ impl Tasks { } false } + + pub fn precache_pdf(&self, mimetype: &BTreeMap) -> bool { + let targets = mimetype + .into_iter() + .filter(|(_, m)| MimeKind::new(m) == MimeKind::PDF) + .map(|(p, _)| p.clone()) + .collect::>(); + + if !targets.is_empty() { + self.scheduler.precache_pdf(targets); + } + false + } } impl Tasks { diff --git a/core/src/tasks/workers/precache.rs b/core/src/tasks/workers/precache.rs index 057666bb..744ef895 100644 --- a/core/src/tasks/workers/precache.rs +++ b/core/src/tasks/workers/precache.rs @@ -21,6 +21,7 @@ pub(crate) struct Precache { pub(crate) enum PrecacheOp { Image(PrecacheOpImage), Video(PrecacheOpVideo), + PDF(PrecacheOpPDF), } #[derive(Debug)] @@ -48,6 +49,12 @@ pub(crate) struct PrecacheOpVideo { pub target: PathBuf, } +#[derive(Debug)] +pub(crate) struct PrecacheOpPDF { + pub id: usize, + pub target: PathBuf, +} + impl Precache { pub(crate) fn new(sch: mpsc::UnboundedSender) -> Self { let (tx, rx) = async_channel::unbounded(); @@ -59,13 +66,20 @@ impl Precache { Ok(match self.rx.recv().await? { PrecacheOp::Image(t) => (t.id, PrecacheOp::Image(t)), PrecacheOp::Video(t) => (t.id, PrecacheOp::Video(t)), + PrecacheOp::PDF(t) => (t.id, PrecacheOp::PDF(t)), }) } pub(crate) async fn work(&self, task: &mut PrecacheOp) -> Result<()> { match task { PrecacheOp::Image(task) => { - Image::precache(&task.target).await.ok(); + let cache = Image::cache(&task.target); + if fs::metadata(&cache).await.is_ok() { + return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?); + } + if let Ok(img) = fs::read(&task.target).await { + Image::precache(img, cache).await.ok(); + } self.sch.send(TaskOp::Adv(task.id, 1, 0))?; } PrecacheOp::Video(task) => { @@ -77,6 +91,15 @@ impl Precache { external::ffmpegthumbnailer(&task.target, &cache).await.ok(); self.sch.send(TaskOp::Adv(task.id, 1, 0))?; } + PrecacheOp::PDF(task) => { + let cache = Image::cache(&task.target); + if fs::metadata(&cache).await.is_ok() { + return Ok(self.sch.send(TaskOp::Adv(task.id, 1, 0))?); + } + + external::pdftoppm(&task.target, &cache).await.ok(); + self.sch.send(TaskOp::Adv(task.id, 1, 0))?; + } } Ok(()) } @@ -132,4 +155,12 @@ impl Precache { } self.done(id) } + + pub(crate) fn pdf(&self, id: usize, targets: Vec) -> Result<()> { + for target in targets { + self.sch.send(TaskOp::New(id, 0))?; + self.tx.send_blocking(PrecacheOp::PDF(PrecacheOpPDF { id, target }))?; + } + self.done(id) + } } diff --git a/cspell.json b/cspell.json index 48fee7e4..84f7227c 100644 --- a/cspell.json +++ b/cspell.json @@ -1 +1 @@ -{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver"],"version":"0.2","language":"en","flagWords":[]} +{"words":["Punct","KEYMAP","splitn","crossterm","YAZI","unar","peekable","ratatui","syntect","pbpaste","pbcopy","ffmpegthumbnailer","oneshot","Posix","Lsar","XADDOS","zoxide","cands","Deque","precache","imageops","IFBLK","IFCHR","IFDIR","IFIFO","IFLNK","IFMT","IFSOCK","IRGRP","IROTH","IRUSR","ISGID","ISUID","ISVTX","IWGRP","IWOTH","IWUSR","IXGRP","IXOTH","IXUSR","libc","winsize","TIOCGWINSZ","xpixel","ypixel","ioerr","appender","Catppuccin","macchiato","gitmodules","Dotfiles","bashprofile","vimrc","flac","webp","exiftool","mediainfo","ripgrep","nvim","indexmap","indexmap","unwatch","canonicalize","serde","fsevent","Ueberzug","iterm","wezterm","sixel","chafa","ueberzugpp","️ Überzug","️ Überzug","Konsole","Alacritty","Überzug","pkgs","paru","unarchiver","pdftoppm"],"version":"0.2","language":"en","flagWords":[]} diff --git a/shared/src/mime.rs b/shared/src/mime.rs index 33a13b2f..be0af996 100644 --- a/shared/src/mime.rs +++ b/shared/src/mime.rs @@ -2,13 +2,16 @@ pub const MIME_DIR: &str = "inode/directory"; #[derive(Clone, Copy, PartialEq, Eq)] pub enum MimeKind { + Archive, Dir, - JSON, - Text, + Image, Video, - Pdf, - Archive, + + JSON, + PDF, + Text, + Others, } @@ -28,7 +31,6 @@ impl MimeKind { "message" => true, "model" => true, "multipart" => true, - "pdf" => true, "text" => true, "video" => true, _ => false, @@ -39,16 +41,16 @@ impl MimeKind { pub fn new(s: &str) -> Self { if s == MIME_DIR { Self::Dir - } else if s == "application/json" { - Self::JSON - } else if s == "application/pdf" { - Self::Pdf } else if s.starts_with("text/") || s.ends_with("/xml") { Self::Text } else if s.starts_with("image/") { Self::Image } else if s.starts_with("video/") { Self::Video + } else if s == "application/json" { + Self::JSON + } else if s == "application/pdf" { + Self::PDF } else if s == "application/x-bzip" || s == "application/x-bzip2" || s == "application/gzip"