packler/pipelines/assets/
images.rs

1use super::AssetMetadata;
2use crate::PacklerConfig;
3use log::{debug, info, trace, warn};
4use serde::{Deserialize, Serialize};
5use std::path::PathBuf;
6use walkdir::WalkDir;
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ImageProcessOutput {
10    pub generated_at: u64,
11    pub source_dir: PathBuf,
12    pub files: Vec<AssetMetadata>,
13}
14
15pub fn process(config: &PacklerConfig) -> Result<Vec<AssetMetadata>, Box<dyn std::error::Error>> {
16    let images_dir = config.source_image_dir();
17
18    info!("IMG: Collecting all images metadata");
19    let images: Vec<AssetMetadata> = WalkDir::new(&images_dir)
20        .into_iter()
21        .filter_map(|entry| {
22            match entry {
23                Ok(entry) => {
24                    if entry.path().is_file() {
25                        let relative_path = entry
26                            .path()
27                            .strip_prefix(&config.assets_source_dir)
28                            .unwrap();
29
30                        debug!(
31                            "IMG: {} (relative: {})",
32                            entry.path().display(),
33                            relative_path.display()
34                        );
35
36                        let image_content = std::fs::read(entry.path()).unwrap();
37                        let hash = seahash::hash(&image_content);
38
39                        // file_stem() instead of file_prefix() otherwise we would
40                        // lose a component if there are two '.' in the filename.
41                        let hashed_name = format!(
42                            "{}-{:x}.{}",
43                            relative_path.file_stem().unwrap().to_string_lossy(),
44                            hash,
45                            relative_path.extension().unwrap().to_string_lossy()
46                        );
47
48                        Some(AssetMetadata {
49                            source_path: entry.path().to_owned(),
50                            logical_path: relative_path.to_owned(),
51                            processed_relative_path: relative_path.with_file_name(hashed_name),
52                            hash,
53                        })
54                    } else {
55                        trace!("{} is not a file. Skip", entry.path().display());
56                        None
57                    }
58                }
59                Err(e) => {
60                    warn!("Could not walk into images: {e}");
61                    None
62                }
63            }
64        })
65        .collect();
66
67    info!("IMG: Cleaning destination directory");
68    clean_dist_dir(config);
69
70    // Actual file copy
71    for image in images.iter() {
72        let dest_path = config.dist_dir.join(&image.processed_relative_path);
73
74        if let Some(dir) = dest_path
75            .parent() { std::fs::create_dir_all(dir).expect("Could not create final directory") }
76
77        std::fs::copy(&image.source_path, &dest_path).unwrap();
78    }
79
80    Ok(images)
81}
82
83pub fn clean_dist_dir(cfg: &PacklerConfig) {
84    let images_dir = cfg.dist_image_dir();
85
86    if images_dir.exists() {
87        std::fs::remove_dir_all(&images_dir)
88            .unwrap_or_else(|_| panic!("Could not remove '{}'", images_dir.display()))
89    }
90}