From 4ebaee95ccb192f0d23e928a0fd279601d8e5d8f Mon Sep 17 00:00:00 2001 From: Nick Pegg Date: Fri, 9 May 2025 08:23:26 -0700 Subject: [PATCH] reorganize --- src/generate.rs | 220 +++++++++++++++++++------------------- src/generate/album_dir.rs | 102 +----------------- src/generate/image.rs | 108 +++++++++++++++++++ 3 files changed, 219 insertions(+), 211 deletions(-) create mode 100644 src/generate/image.rs diff --git a/src/generate.rs b/src/generate.rs index 62fdbbe..2fc6c73 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -1,9 +1,10 @@ pub mod album_dir; +mod image; use crate::config::Config; -use album_dir::{AlbumDir, Image}; -use anyhow::{Context, anyhow}; -use image::imageops::FilterType; +use crate::generate::image::Image; +use album_dir::AlbumDir; +use anyhow::{anyhow, Context}; use indicatif::ProgressBar; use rayon::prelude::*; use serde::Serialize; @@ -16,112 +17,7 @@ use std::path::{Path, PathBuf}; use std::sync::Mutex; use tera::Tera; -const IMG_RESIZE_FILTER: FilterType = FilterType::Lanczos3; - -#[derive(Serialize, Debug)] -struct Breadcrumb { - path: PathBuf, - name: String, -} - -/// A Tera context for album pages -#[derive(Serialize, Debug)] -struct AlbumContext { - // Path required to get back to the root album - root_path: PathBuf, - - name: String, - description: String, - - images: Vec, - - // Path to the cover image thumbnail within /slides/, relative to the album dir. Used when - // linking to an album from a parent album - cover_thumbnail_path: PathBuf, - - // list of: - // - relative dir to walk up to root, e.g. "../../.." - // - dir name - breadcrumbs: Vec, - - // Immediate children of this album - children: Vec, -} - -impl TryFrom<&AlbumDir> for AlbumContext { - type Error = anyhow::Error; - - fn try_from(album: &AlbumDir) -> anyhow::Result { - let name: String = match album.path.file_name() { - Some(n) => n.to_string_lossy().to_string(), - None => String::new(), - }; - - // Build breadcrumbs - let mut breadcrumbs: Vec = Vec::new(); - { - let mut album_path = album.path.clone(); - let mut relpath: PathBuf = PathBuf::new(); - while album_path.pop() { - let filename: &OsStr = album_path.file_name().unwrap_or(OsStr::new("Home")); - relpath.push(".."); - breadcrumbs.push(Breadcrumb { - path: relpath.clone(), - name: filename.to_string_lossy().to_string(), - }); - } - } - breadcrumbs.reverse(); - log::debug!("Crumbs for {}: {breadcrumbs:?}", album.path.display()); - - // The first breadcrumb path is the relative path to the root album - let root_path = if !breadcrumbs.is_empty() { - breadcrumbs[0].path.clone() - } else { - PathBuf::new() - }; - - // Make the path to the thumbnail relative to the album that we're in - let cover_thumbnail_path: PathBuf; - if let Some(parent) = album.path.parent() { - cover_thumbnail_path = album.cover.thumb_path.strip_prefix(parent)?.to_path_buf() - } else { - cover_thumbnail_path = album - .cover - .thumb_path - .strip_prefix(&album.path)? - .to_path_buf() - }; - - let children: Vec = album - .children - .iter() - .map(AlbumContext::try_from) - .collect::>>()?; - let images: Vec = album.images.clone(); - - Ok(AlbumContext { - root_path, - name, - description: album.description.clone(), - breadcrumbs, - images, - children, - cover_thumbnail_path, - }) - } -} - -/// A Tera context for slide (individual image) pages -#[derive(Serialize, Debug)] -struct SlideContext { - // Path required to get back to the root album - root_path: PathBuf, - image: Image, - prev_image: Option, - next_image: Option, -} - +const IMG_RESIZE_FILTER: ::image::imageops::FilterType = ::image::imageops::FilterType::Lanczos3; /// Generate an album /// /// `root_path` is a path to the root directory of the album. `full` if true will regenerate @@ -202,7 +98,7 @@ fn generate_images(config: &Config, album: &AlbumDir, full: bool) -> anyhow::Res fs::hard_link(&img.path, &full_size_path) .with_context(|| format!("Error creating hard link at {}", full_size_path.display()))?; - let orig_image = image::open(&img.path)?; + let orig_image = ::image::open(&img.path)?; let thumb_path = output_path.join(&img.thumb_path); log::info!( "Resizing {} -> {}", @@ -301,6 +197,110 @@ fn generate_html(config: &Config, album: &AlbumDir) -> anyhow::Result<()> { Ok(()) } +#[derive(Serialize, Debug)] +struct Breadcrumb { + path: PathBuf, + name: String, +} + +/// A Tera context for album pages +#[derive(Serialize, Debug)] +struct AlbumContext { + /// Path required to get back to the root album + root_path: PathBuf, + + name: String, + description: String, + + images: Vec, + + /// Path to the cover image thumbnail within /slides/, relative to the album dir. Used when + /// linking to an album from a parent album + cover_thumbnail_path: PathBuf, + + /// list of: + /// - relative dir to walk up to root, e.g. "../../.." + /// - dir name + breadcrumbs: Vec, + + /// Immediate children of this album + children: Vec, +} + +impl TryFrom<&AlbumDir> for AlbumContext { + type Error = anyhow::Error; + + fn try_from(album: &AlbumDir) -> anyhow::Result { + let name: String = match album.path.file_name() { + Some(n) => n.to_string_lossy().to_string(), + None => String::new(), + }; + + // Build breadcrumbs + let mut breadcrumbs: Vec = Vec::new(); + { + let mut album_path = album.path.clone(); + let mut relpath: PathBuf = PathBuf::new(); + while album_path.pop() { + let filename: &OsStr = album_path.file_name().unwrap_or(OsStr::new("Home")); + relpath.push(".."); + breadcrumbs.push(Breadcrumb { + path: relpath.clone(), + name: filename.to_string_lossy().to_string(), + }); + } + } + breadcrumbs.reverse(); + log::debug!("Crumbs for {}: {breadcrumbs:?}", album.path.display()); + + // The first breadcrumb path is the relative path to the root album + let root_path = if !breadcrumbs.is_empty() { + breadcrumbs[0].path.clone() + } else { + PathBuf::new() + }; + + // Make the path to the thumbnail relative to the album that we're in + let cover_thumbnail_path: PathBuf; + if let Some(parent) = album.path.parent() { + cover_thumbnail_path = album.cover.thumb_path.strip_prefix(parent)?.to_path_buf() + } else { + cover_thumbnail_path = album + .cover + .thumb_path + .strip_prefix(&album.path)? + .to_path_buf() + }; + + let children: Vec = album + .children + .iter() + .map(AlbumContext::try_from) + .collect::>>()?; + let images: Vec = album.images.clone(); + + Ok(AlbumContext { + root_path, + name, + description: album.description.clone(), + breadcrumbs, + images, + children, + cover_thumbnail_path, + }) + } +} + +/// A Tera context for slide (individual image) pages +#[derive(Serialize, Debug)] +struct SlideContext { + // Path required to get back to the root album + root_path: PathBuf, + image: Image, + prev_image: Option, + next_image: Option, +} + #[cfg(test)] mod tests { use super::generate; diff --git a/src/generate/album_dir.rs b/src/generate/album_dir.rs index a941f12..461debb 100644 --- a/src/generate/album_dir.rs +++ b/src/generate/album_dir.rs @@ -1,7 +1,7 @@ +use crate::generate::image::Image; use anyhow::anyhow; use image::ImageReader; use serde::Serialize; -use std::ffi::OsString; use std::fs; use std::path::{Path, PathBuf}; use std::slice::Iter; @@ -169,93 +169,6 @@ impl<'a> Iterator for AlbumImageIter<'a> { } } -#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)] -pub struct Image { - /// Path to the image, relative to the root album - pub path: PathBuf, - pub filename: String, - - /// Text description of the image which is displayed below it on the HTML page - pub description: String, - - pub thumb_filename: String, - pub thumb_path: PathBuf, - pub screen_filename: String, - pub screen_path: PathBuf, - pub html_filename: String, - pub html_path: PathBuf, -} - -impl Image { - pub fn new(path: PathBuf, description: String) -> anyhow::Result { - let filename = path - .file_name() - .ok_or(anyhow!( - "Image path {} is missing a filename", - path.display() - ))? - .to_str() - .ok_or(anyhow!("Cannot convert {} to a string", path.display()))? - .to_string(); - let thumb_filename = Self::slide_filename(&path, "thumb", true)?; - let thumb_path = Self::slide_path(&path, &thumb_filename); - let screen_filename = Self::slide_filename(&path, "screen", true)?; - let screen_path = Self::slide_path(&path, &screen_filename); - let html_filename = Self::slide_filename(&path, "html", false)?; - let html_path = Self::slide_path(&path, &html_filename); - - Ok(Image { - path, - description, - filename, - thumb_filename, - thumb_path, - screen_filename, - screen_path, - html_filename, - html_path, - }) - } - - /// Returns the filename for a given slide type. For example if ext = "thumb" and the current - /// filename is "blah.jpg" this will return "blah.thumb.jpg". If keep_ext if false, it would - /// return "blah.thumb" - fn slide_filename(path: &Path, ext: &str, keep_ext: bool) -> anyhow::Result { - let mut new_ext: OsString = ext.into(); - if keep_ext { - if let Some(e) = path.extension() { - new_ext = OsString::from( - ext.to_string() - + "." - + e.to_str().ok_or(anyhow!( - "Image {} extension is not valid UTF-8", - path.display() - ))?, - ) - } - } - - let new_path = path.with_extension(new_ext); - let new_name = new_path - .file_name() - .ok_or(anyhow!("Image {} missing a file name", path.display()))? - .to_str() - .ok_or(anyhow!("Unable to convert {} to a string", path.display()))? - .to_string(); - - Ok(new_name) - } - - /// Returns the path to the file in the slides dir given the path to the original image - fn slide_path(path: &Path, file_name: &str) -> PathBuf { - let mut new_path = path.to_path_buf(); - new_path.pop(); - new_path.push("slides"); - new_path.push(file_name); - new_path - } -} - #[cfg(test)] mod tests { use super::*; @@ -314,17 +227,4 @@ mod tests { ]); assert_eq!(imgs, expected); } - - #[test] - fn image_paths() { - let img = Image::new(PathBuf::from("foo/bar/image.jpg"), String::new()).unwrap(); - assert_eq!( - img.thumb_path, - PathBuf::from("foo/bar/slides/image.thumb.jpg") - ); - assert_eq!( - img.screen_path, - PathBuf::from("foo/bar/slides/image.screen.jpg") - ); - } } diff --git a/src/generate/image.rs b/src/generate/image.rs new file mode 100644 index 0000000..1053d3f --- /dev/null +++ b/src/generate/image.rs @@ -0,0 +1,108 @@ +use anyhow::anyhow; +use serde::Serialize; +use std::ffi::OsString; +use std::path::{Path, PathBuf}; + +#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)] +pub struct Image { + /// Path to the image, relative to the root album + pub path: PathBuf, + pub filename: String, + + /// Text description of the image which is displayed below it on the HTML page + pub description: String, + + pub thumb_filename: String, + pub thumb_path: PathBuf, + pub screen_filename: String, + pub screen_path: PathBuf, + pub html_filename: String, + pub html_path: PathBuf, +} +impl Image { + pub fn new(path: PathBuf, description: String) -> anyhow::Result { + let filename = path + .file_name() + .ok_or(anyhow!( + "Image path {} is missing a filename", + path.display() + ))? + .to_str() + .ok_or(anyhow!("Cannot convert {} to a string", path.display()))? + .to_string(); + let thumb_filename = Self::slide_filename(&path, "thumb", true)?; + let thumb_path = Self::slide_path(&path, &thumb_filename); + let screen_filename = Self::slide_filename(&path, "screen", true)?; + let screen_path = Self::slide_path(&path, &screen_filename); + let html_filename = Self::slide_filename(&path, "html", false)?; + let html_path = Self::slide_path(&path, &html_filename); + + Ok(Image { + path, + description, + filename, + thumb_filename, + thumb_path, + screen_filename, + screen_path, + html_filename, + html_path, + }) + } + + /// Returns the filename for a given slide type. For example if ext = "thumb" and the current + /// filename is "blah.jpg" this will return "blah.thumb.jpg". If keep_ext if false, it would + /// return "blah.thumb" + fn slide_filename(path: &Path, ext: &str, keep_ext: bool) -> anyhow::Result { + let mut new_ext: OsString = ext.into(); + if keep_ext { + if let Some(e) = path.extension() { + new_ext = OsString::from( + ext.to_string() + + "." + + e.to_str().ok_or(anyhow!( + "Image {} extension is not valid UTF-8", + path.display() + ))?, + ) + } + } + + let new_path = path.with_extension(new_ext); + let new_name = new_path + .file_name() + .ok_or(anyhow!("Image {} missing a file name", path.display()))? + .to_str() + .ok_or(anyhow!("Unable to convert {} to a string", path.display()))? + .to_string(); + + Ok(new_name) + } + + /// Returns the path to the file in the slides dir given the path to the original image + fn slide_path(path: &Path, file_name: &str) -> PathBuf { + let mut new_path = path.to_path_buf(); + new_path.pop(); + new_path.push("slides"); + new_path.push(file_name); + new_path + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn image_paths() { + let img = Image::new(PathBuf::from("foo/bar/image.jpg"), String::new()).unwrap(); + assert_eq!( + img.thumb_path, + PathBuf::from("foo/bar/slides/image.thumb.jpg") + ); + assert_eq!( + img.screen_path, + PathBuf::from("foo/bar/slides/image.screen.jpg") + ); + } +}