127 lines
4.1 KiB
Rust
127 lines
4.1 KiB
Rust
use crate::database::stores::UpStore;
|
|
use crate::util::hash::b58_encode;
|
|
use crate::util::hash::Hash;
|
|
use crate::util::jobs::{JobContainer, JobState};
|
|
use anyhow::{anyhow, Result};
|
|
use log::{debug, trace};
|
|
|
|
use std::{
|
|
collections::HashMap,
|
|
fs::File,
|
|
io::Write,
|
|
path::{Path, PathBuf},
|
|
sync::{Arc, Mutex},
|
|
};
|
|
|
|
use self::audio::AudioPath;
|
|
use self::image::ImagePath;
|
|
use self::text::TextPath;
|
|
use self::video::VideoPath;
|
|
|
|
pub mod audio;
|
|
pub mod image;
|
|
pub mod text;
|
|
pub mod video;
|
|
|
|
pub trait Previewable {
|
|
fn get_thumbnail(&self) -> Result<Option<Vec<u8>>>;
|
|
}
|
|
pub struct PreviewStore {
|
|
path: PathBuf,
|
|
store: Arc<Box<dyn UpStore + Send + Sync>>,
|
|
|
|
locks: Mutex<HashMap<Hash, Arc<Mutex<PathBuf>>>>,
|
|
}
|
|
|
|
#[cfg(feature = "previews")]
|
|
impl PreviewStore {
|
|
pub fn new<P: AsRef<Path>>(path: P, store: Arc<Box<dyn UpStore + Send + Sync>>) -> Self {
|
|
PreviewStore {
|
|
path: PathBuf::from(path.as_ref()),
|
|
store,
|
|
locks: Mutex::new(HashMap::new()),
|
|
}
|
|
}
|
|
|
|
fn get_path(&self, hash: &Hash) -> Arc<Mutex<PathBuf>> {
|
|
let mut locks = self.locks.lock().unwrap();
|
|
if let Some(path) = locks.get(hash) {
|
|
path.clone()
|
|
} else {
|
|
let thumbpath = self.path.join(b58_encode(hash));
|
|
let path = Arc::new(Mutex::new(thumbpath));
|
|
locks.insert(hash.clone(), path.clone());
|
|
path
|
|
}
|
|
}
|
|
|
|
pub fn get<S>(
|
|
&self,
|
|
hash: Hash,
|
|
mime_type: S,
|
|
mut job_container: JobContainer,
|
|
) -> Result<Option<PathBuf>>
|
|
where
|
|
S: Into<Option<String>>,
|
|
{
|
|
debug!("Preview for {hash:?} requested...");
|
|
let path_mutex = self.get_path(&hash);
|
|
let thumbpath = path_mutex.lock().unwrap();
|
|
if thumbpath.exists() {
|
|
trace!("Preview for {hash:?} already exists, returning {thumbpath:?}");
|
|
Ok(Some(thumbpath.clone()))
|
|
} else {
|
|
trace!("Calculating preview for {hash:?}...");
|
|
let files = self.store.retrieve(&hash)?;
|
|
if let Some(file) = files.get(0) {
|
|
let file_path = file.get_file_path();
|
|
let mut job_handle = job_container.add_job(
|
|
None,
|
|
&format!("Creating preview for {:?}", file_path.file_name().unwrap()),
|
|
)?;
|
|
|
|
let mime_type = mime_type.into();
|
|
|
|
let mime_type: Option<String> = if mime_type.is_some() {
|
|
mime_type
|
|
} else {
|
|
tree_magic_mini::from_filepath(file_path).map(|m| m.into())
|
|
};
|
|
|
|
let preview = match mime_type {
|
|
Some(tm) if tm.starts_with("text") => TextPath(file_path).get_thumbnail(),
|
|
Some(tm) if tm.starts_with("video") || tm == "application/x-matroska" => {
|
|
VideoPath(file_path).get_thumbnail()
|
|
}
|
|
Some(tm) if tm.starts_with("audio") || tm == "application/x-riff" => {
|
|
AudioPath(file_path).get_thumbnail()
|
|
}
|
|
Some(tm) if tm.starts_with("image") => ImagePath(file_path).get_thumbnail(),
|
|
Some(unknown) => Err(anyhow!("No capability for {:?} thumbnails.", unknown)),
|
|
_ => Err(anyhow!("Unknown file type, or file doesn't exist.")),
|
|
};
|
|
|
|
match preview {
|
|
Ok(preview) => {
|
|
trace!("Got preview for {hash:?}.");
|
|
|
|
let _ = job_handle.update_state(JobState::Done);
|
|
|
|
if let Some(data) = preview {
|
|
std::fs::create_dir_all(&self.path)?;
|
|
let mut file = File::create(&*thumbpath)?;
|
|
file.write_all(&data)?;
|
|
Ok(Some(thumbpath.clone()))
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
Err(err) => Err(err),
|
|
}
|
|
} else {
|
|
Err(anyhow!("Object not found, or is not a file."))
|
|
}
|
|
}
|
|
}
|
|
}
|