feat: pre-cache PDF files

This commit is contained in:
sxyazi 2023-08-03 22:04:50 +08:00
parent 0485259302
commit ad554bab89
No known key found for this signature in database
10 changed files with 90 additions and 49 deletions

View file

@ -26,16 +26,10 @@ impl Image {
}) })
}); });
Ok(img.await??) img.await?
} }
pub async fn precache(path: &Path) -> Result<()> { pub async fn precache(img: Vec<u8>, cache: PathBuf) -> Result<()> {
let cache = Self::cache(path);
if fs::metadata(&cache).await.is_ok() {
return Ok(());
}
let img = fs::read(&path).await?;
let result = tokio::task::spawn_blocking(move || { let result = tokio::task::spawn_blocking(move || {
let img = image::load_from_memory(&img)?; let img = image::load_from_memory(&img)?;
let (w, h) = (PREVIEW.max_width, PREVIEW.max_height); let (w, h) = (PREVIEW.max_width, PREVIEW.max_height);
@ -46,7 +40,7 @@ impl Image {
Ok::<(), Error>(()) Ok::<(), Error>(())
}); });
Ok(result.await??) result.await?
} }
#[inline] #[inline]

View file

@ -4,10 +4,10 @@ use anyhow::{bail, Result};
use config::PREVIEW; use config::PREVIEW;
use tokio::process::Command; 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") let output = Command::new("ffmpegthumbnailer")
.arg("-i") .arg("-i")
.arg(path) .arg(src)
.arg("-o") .arg("-o")
.arg(dest) .arg(dest)
.args(["-q", "6", "-c", "jpeg", "-s", &PREVIEW.max_width.to_string()]) .args(["-q", "6", "-c", "jpeg", "-s", &PREVIEW.max_width.to_string()])

View file

@ -1,22 +1,19 @@
use std::path::Path; use std::path::Path;
use adaptor::Image;
use anyhow::{bail, Result}; use anyhow::{bail, Result};
use tokio::process::Command; use tokio::process::Command;
pub async fn pdftoppm(path: &Path, dest: &Path) -> Result<()> { pub async fn pdftoppm(src: &Path, dest: &Path) -> Result<()> {
let output = Command::new("pdftoppm") let output = Command::new("pdftoppm")
.args(["-png", "-singlefile"]) .args(["-singlefile", "-jpeg", "-jpegopt", "quality=75"])
.arg(path) .arg(src)
.arg(dest) .kill_on_drop(true)
.kill_on_drop(true) .output()
.output() .await?;
.await?;
if !output.status.success() { if !output.status.success() {
bail!( bail!("failed to generate PDF thumbnail: {}", String::from_utf8_lossy(&output.stderr));
"failed to get pdf: {}", }
String::from_utf8_lossy(&output.stderr) Ok(Image::precache(output.stdout, dest.to_path_buf()).await?)
);
}
Ok(())
} }

View file

@ -304,6 +304,7 @@ impl Manager {
tasks.precache_image(&mimes); tasks.precache_image(&mimes);
tasks.precache_video(&mimes); tasks.precache_video(&mimes);
tasks.precache_pdf(&mimes);
self.mimetype.extend(mimes); self.mimetype.extend(mimes);
true true

View file

@ -59,13 +59,13 @@ impl Preview {
let (path, mime) = (path.to_path_buf(), mime.to_owned()); let (path, mime) = (path.to_path_buf(), mime.to_owned());
self.handle = Some(tokio::spawn(async move { self.handle = Some(tokio::spawn(async move {
let result = match kind { 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::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}")), MimeKind::Others => Err(anyhow!("Unsupported mimetype: {mime}")),
}; };
@ -118,17 +118,13 @@ impl Preview {
Self::image(&cache).await Self::image(&cache).await
} }
pub async fn pdf(path: &Path) -> Result<PreviewData> { pub async fn pdf(path: &Path) -> Result<PreviewData> {
let cache = Image::cache(path); let cache = Image::cache(path);
if fs::metadata(&cache).await.is_err() { if fs::metadata(&cache).await.is_err() {
external::pdftoppm(path, &cache).await?; external::pdftoppm(path, &cache).await?;
} }
let mut dest = cache.into_os_string(); Self::image(&cache).await
dest.push(".png");
let dest = Path::new(&dest);
Self::image(dest).await
} }
pub async fn json(path: &Path) -> Result<String> { pub async fn json(path: &Path) -> Result<String> {

View file

@ -375,4 +375,11 @@ impl Scheduler {
self.precache.video(id, targets).ok(); self.precache.video(id, targets).ok();
} }
pub(super) fn precache_pdf(&self, targets: Vec<PathBuf>) {
let name = format!("Precache of {} PDF files", targets.len());
let id = self.running.write().add(name);
self.precache.pdf(id, targets).ok();
}
} }

View file

@ -265,6 +265,19 @@ impl Tasks {
} }
false false
} }
pub fn precache_pdf(&self, mimetype: &BTreeMap<PathBuf, String>) -> bool {
let targets = mimetype
.into_iter()
.filter(|(_, m)| MimeKind::new(m) == MimeKind::PDF)
.map(|(p, _)| p.clone())
.collect::<Vec<_>>();
if !targets.is_empty() {
self.scheduler.precache_pdf(targets);
}
false
}
} }
impl Tasks { impl Tasks {

View file

@ -21,6 +21,7 @@ pub(crate) struct Precache {
pub(crate) enum PrecacheOp { pub(crate) enum PrecacheOp {
Image(PrecacheOpImage), Image(PrecacheOpImage),
Video(PrecacheOpVideo), Video(PrecacheOpVideo),
PDF(PrecacheOpPDF),
} }
#[derive(Debug)] #[derive(Debug)]
@ -48,6 +49,12 @@ pub(crate) struct PrecacheOpVideo {
pub target: PathBuf, pub target: PathBuf,
} }
#[derive(Debug)]
pub(crate) struct PrecacheOpPDF {
pub id: usize,
pub target: PathBuf,
}
impl Precache { impl Precache {
pub(crate) fn new(sch: mpsc::UnboundedSender<TaskOp>) -> Self { pub(crate) fn new(sch: mpsc::UnboundedSender<TaskOp>) -> Self {
let (tx, rx) = async_channel::unbounded(); let (tx, rx) = async_channel::unbounded();
@ -59,13 +66,20 @@ impl Precache {
Ok(match self.rx.recv().await? { Ok(match self.rx.recv().await? {
PrecacheOp::Image(t) => (t.id, PrecacheOp::Image(t)), PrecacheOp::Image(t) => (t.id, PrecacheOp::Image(t)),
PrecacheOp::Video(t) => (t.id, PrecacheOp::Video(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<()> { pub(crate) async fn work(&self, task: &mut PrecacheOp) -> Result<()> {
match task { match task {
PrecacheOp::Image(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))?; self.sch.send(TaskOp::Adv(task.id, 1, 0))?;
} }
PrecacheOp::Video(task) => { PrecacheOp::Video(task) => {
@ -77,6 +91,15 @@ impl Precache {
external::ffmpegthumbnailer(&task.target, &cache).await.ok(); external::ffmpegthumbnailer(&task.target, &cache).await.ok();
self.sch.send(TaskOp::Adv(task.id, 1, 0))?; 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(()) Ok(())
} }
@ -132,4 +155,12 @@ impl Precache {
} }
self.done(id) self.done(id)
} }
pub(crate) fn pdf(&self, id: usize, targets: Vec<PathBuf>) -> Result<()> {
for target in targets {
self.sch.send(TaskOp::New(id, 0))?;
self.tx.send_blocking(PrecacheOp::PDF(PrecacheOpPDF { id, target }))?;
}
self.done(id)
}
} }

View file

@ -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":[]}

View file

@ -2,13 +2,16 @@ pub const MIME_DIR: &str = "inode/directory";
#[derive(Clone, Copy, PartialEq, Eq)] #[derive(Clone, Copy, PartialEq, Eq)]
pub enum MimeKind { pub enum MimeKind {
Archive,
Dir, Dir,
JSON,
Text,
Image, Image,
Video, Video,
Pdf,
Archive, JSON,
PDF,
Text,
Others, Others,
} }
@ -28,7 +31,6 @@ impl MimeKind {
"message" => true, "message" => true,
"model" => true, "model" => true,
"multipart" => true, "multipart" => true,
"pdf" => true,
"text" => true, "text" => true,
"video" => true, "video" => true,
_ => false, _ => false,
@ -39,16 +41,16 @@ impl MimeKind {
pub fn new(s: &str) -> Self { pub fn new(s: &str) -> Self {
if s == MIME_DIR { if s == MIME_DIR {
Self::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") { } else if s.starts_with("text/") || s.ends_with("/xml") {
Self::Text Self::Text
} else if s.starts_with("image/") { } else if s.starts_with("image/") {
Self::Image Self::Image
} else if s.starts_with("video/") { } else if s.starts_with("video/") {
Self::Video Self::Video
} else if s == "application/json" {
Self::JSON
} else if s == "application/pdf" {
Self::PDF
} else if s == "application/x-bzip" } else if s == "application/x-bzip"
|| s == "application/x-bzip2" || s == "application/x-bzip2"
|| s == "application/gzip" || s == "application/gzip"