148 lines
4.9 KiB
Rust
148 lines
4.9 KiB
Rust
use eyre::{eyre, Result};
|
|
use tracing::{debug, trace};
|
|
use upend_base::hash::{b58_encode, UpMultihash};
|
|
use upend_db::jobs::{JobContainer, JobState};
|
|
use upend_db::stores::UpStore;
|
|
|
|
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, options: HashMap<String, String>) -> Result<Option<Vec<u8>>>;
|
|
}
|
|
|
|
type HashWithOptions = (UpMultihash, String);
|
|
pub struct PreviewStore {
|
|
path: PathBuf,
|
|
store: Arc<Box<dyn UpStore + Send + Sync>>,
|
|
|
|
locks: Mutex<HashMap<HashWithOptions, 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: &UpMultihash,
|
|
options: &HashMap<String, String>,
|
|
) -> Arc<Mutex<PathBuf>> {
|
|
let mut locks = self.locks.lock().unwrap();
|
|
let mut options_strs = options
|
|
.iter()
|
|
.map(|(k, v)| format!("{k}{v}"))
|
|
.collect::<Vec<String>>();
|
|
options_strs.sort();
|
|
let options_concat = options_strs.concat();
|
|
if let Some(path) = locks.get(&(hash.clone(), options_concat.clone())) {
|
|
path.clone()
|
|
} else {
|
|
let thumbpath = self.path.join(format!(
|
|
"{}{}",
|
|
b58_encode(hash.to_bytes()),
|
|
if options_concat.is_empty() {
|
|
String::from("")
|
|
} else {
|
|
format!("_{options_concat}")
|
|
}
|
|
));
|
|
|
|
let path = Arc::new(Mutex::new(thumbpath));
|
|
locks.insert((hash.clone(), options_concat), path.clone());
|
|
path
|
|
}
|
|
}
|
|
|
|
pub fn get(
|
|
&self,
|
|
hash: UpMultihash,
|
|
options: HashMap<String, String>,
|
|
mut job_container: JobContainer,
|
|
) -> Result<Option<PathBuf>> {
|
|
debug!("Preview for {hash} requested...");
|
|
let path_mutex = self.get_path(&hash, &options);
|
|
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 = options.get("mime").map(|x| x.to_owned());
|
|
|
|
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(options)
|
|
}
|
|
Some(tm) if tm.starts_with("video") || tm == "application/x-matroska" => {
|
|
VideoPath(file_path).get_thumbnail(options)
|
|
}
|
|
Some(tm) if tm.starts_with("audio") || tm == "application/x-riff" => {
|
|
AudioPath(file_path).get_thumbnail(options)
|
|
}
|
|
Some(tm) if tm.starts_with("image") => {
|
|
ImagePath(file_path).get_thumbnail(options)
|
|
}
|
|
Some(unknown) => Err(eyre!("No capability for {:?} thumbnails.", unknown)),
|
|
_ => Err(eyre!("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(eyre!("Object not found, or is not a file."))
|
|
}
|
|
}
|
|
}
|
|
}
|