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<()> {
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<u8>, 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]

View File

@ -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()])

View File

@ -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?)
}

View File

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

View File

@ -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<PreviewData> {
pub async fn pdf(path: &Path) -> Result<PreviewData> {
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<String> {

View File

@ -375,4 +375,11 @@ impl Scheduler {
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
}
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 {

View File

@ -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<TaskOp>) -> 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<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)]
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"