use log::{debug, info}; use std::{
fs::File,
io::{BufReader, BufWriter},
path::{Path, PathBuf},
time::UNIX_EPOCH
};
use image::{DynamicImage, Rgba, RgbaImage};
use png::{Decoder, Encoder};
use url::Url;
use crate::{error::ThumbnailError, sizes::ThumbnailSize};
fn get_base_cache_dir() -> PathBuf {
let dir = dirs::cache_dir().unwrap_or_else(|| {
let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
PathBuf::from(home).join(".cache")
});
debug!("Using base cache directory: {:?}", dir);
dir
}
pub fn get_thumbnail_hash_output(hash: &str, size: ThumbnailSize) -> PathBuf {
let base_dir = get_base_cache_dir().join("thumbnails");
let output_dir = base_dir.join(size.to_string());
let output_file = format!("{}.png", hash);
let path = output_dir.join(output_file);
debug!(
"Constructed thumbnail hash output path for hash={} size={:?}: {:?}",
hash, size, path
);
path
}
pub fn get_failed_thumbnail_output(hash: &str) -> PathBuf {
let fail_dir = get_base_cache_dir().join("thumbnails").join("fail").join("thumbnailify");
let output_file = format!("{}.png", hash);
let path = fail_dir.join(output_file);
debug!("Constructed fail thumbnail path for hash={}: {:?}", hash, path);
path
}
pub fn write_failed_thumbnail(fail_path: &Path, source_path: &Path) -> Result<(), ThumbnailError> {
info!(
"Writing failed thumbnail marker at {:?} for source {:?}",
fail_path, source_path
);
let failed_img: DynamicImage = DynamicImage::ImageRgba8(
RgbaImage::from_pixel(1, 1, Rgba([0, 0, 0, 0]))
);
write_out_thumbnail(fail_path, failed_img, source_path)
}
pub fn get_file_uri(input: &Path) -> Result<String, ThumbnailError> {
debug!("Attempting to get file URI for path: {:?}", input);
let canonical = std::fs::canonicalize(input).unwrap_or_else(|_| {
debug!(
"Failed to canonicalize path: {:?}, using the raw path as fallback",
input
);
PathBuf::from(input)
});
let url = Url::from_file_path(&canonical).map_err(|_| {
std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Failed to convert file path to URL",
)
})?;
debug!("File URI for path {:?} is {}", input, url);
Ok(url.to_string())
}
pub fn write_out_thumbnail(
image_path: &Path,
img: DynamicImage,
source_image_path: &Path,
) -> Result<(), ThumbnailError> {
info!(
"Writing out thumbnail to {:?} from source {:?}",
image_path, source_image_path
);
let file = File::create(image_path)?;
let rgba_image: RgbaImage = img.to_rgba8();
let (width, height) = rgba_image.dimensions();
let buffer = rgba_image.into_raw();
let mut encoder = Encoder::new(BufWriter::new(file), width, height);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header()?;
writer.write_image_data(&buffer)?;
debug!("Successfully wrote thumbnail file to {:?}", image_path);
Ok(())
}
pub fn add_thumbnail_metadata(thumb_path: &Path, source_image_path: &Path) -> Result<(), ThumbnailError> {
debug!("Adding thumbnail metadata to {:?}", thumb_path);
let file_in = File::open(thumb_path)?;
let reader = BufReader::new(file_in);
let decoder = Decoder::new(reader);
let mut reader = decoder.read_info()?;
let info = reader.info();
let existing_text = &info.uncompressed_latin1_text.clone();
let mut buf = vec![0; reader.output_buffer_size()];
reader.next_frame(&mut buf)?;
let file_out = File::create(thumb_path)?;
let w = BufWriter::new(file_out);
let mut encoder = Encoder::new(w, reader.info().width, reader.info().height);
encoder.set_color(reader.info().color_type);
encoder.set_depth(reader.info().bit_depth);
for chunk in existing_text {
encoder.add_text_chunk(chunk.keyword.clone(), chunk.text.clone())?;
}
encoder.add_text_chunk("Software".to_string(), "Thumbnailify".to_string())?;
let uri = get_file_uri(source_image_path)?;
encoder.add_text_chunk("Thumb::URI".to_string(), uri)?;
let metadata = std::fs::metadata(source_image_path)?;
let size = metadata.len();
encoder.add_text_chunk("Thumb::Size".to_string(), size.to_string())?;
let modified_time = metadata.modified()?;
let mtime_unix = modified_time.duration_since(UNIX_EPOCH).unwrap_or_default().as_secs();
encoder.add_text_chunk("Thumb::MTime".to_string(), mtime_unix.to_string())?;
let mut writer = encoder.write_header()?;
writer.write_image_data(&buf)?;
debug!(
"Embedded PNG metadata: URI, Size={}, MTime={}",
size, mtime_unix
);
Ok(())
}