stapix

Yet another static page generator for photo galleries

Commit
791bf7edf2bb285bc126af9baa3bfdde1a147479
Parent
f026aae21c05709ab539143b1ac34f6b9987da78
Author
Pablo <pablo-escobar@riseup.net>
Date

Parallelized the rendering of the WebP thumbnails

Diffstat

4 files changed, 73 insertions, 15 deletions

Status File Name N° Changes Insertions Deletions
Modified Cargo.lock 27 27 0
Modified Cargo.toml 2 2 0
Modified src/main.rs 28 25 3
Modified src/picture.rs 31 19 12
diff --git a/Cargo.lock b/Cargo.lock
@@ -196,6 +196,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
 
 [[package]]
+name = "hermit-abi"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
+
+[[package]]
 name = "image"
 version = "0.24.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -330,6 +336,16 @@ dependencies = [
 ]
 
 [[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
 name = "png"
 version = "0.17.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -460,8 +476,10 @@ name = "stapix"
 version = "0.1.0"
 dependencies = [
  "image",
+ "num_cpus",
  "serde",
  "serde_yaml",
+ "threadpool",
  "webp",
 ]
 
@@ -477,6 +495,15 @@ dependencies = [
 ]
 
 [[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
+[[package]]
 name = "tiff"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -11,3 +11,5 @@ serde = { version = "1.0", features = ["derive"] }
 serde_yaml = "0.9"
 webp = "0.2"
 image = "0.24.7"
+threadpool = "1.8.1"
+num_cpus = "1.16.0"
diff --git a/src/main.rs b/src/main.rs
@@ -4,7 +4,9 @@ use std::fs::{self, File};
 use std::io::{self, Write};
 use std::path::PathBuf;
 use std::process::exit;
+use std::sync::mpsc;
 use picture::Picture;
+use threadpool::ThreadPool;
 
 mod picture;
 
@@ -76,7 +78,7 @@ fn render_gallery(pics: Vec<Picture>) -> io::Result<()> {
         }
     }
 
-    // Warn the user if a particular path doesn't have an associated alt string
+    // ========================================================================
     for pic in &pics {
         if pic.alt.is_empty() {
             println!(
@@ -86,13 +88,33 @@ fn render_gallery(pics: Vec<Picture>) -> io::Result<()> {
         }
     }
 
-    // TODO: Parallelize this
+    // ========================================================================
     let mut thumb_path = PathBuf::from(TARGET_PATH);
     thumb_path.push(THUMBS_PATH);
+
+    let rendering_pool = ThreadPool::with_name(
+        String::from("thumbnails renderer"),
+        num_cpus::get() + 1
+        );
+    let (sender, reciever) = mpsc::channel();
+
     for pic in &pics {
-        pic.render_thumbnail(&thumb_path)?;
+        let sender = sender.clone();
+        let pic = pic.clone();
+        let thumb_path = thumb_path.clone();
+        rendering_pool.execute(move || {
+            sender.send(pic.render_thumbnail(&thumb_path))
+                .expect("channel should still be alive awaiting for the completion of this task");
+        });
+    }
+
+    for msg in reciever.iter().take(pics.len()) {
+        if !msg {
+            exit(1);
+        }
     }
 
+    // ========================================================================
     render_index(&pics)?;
 
     for pic in pics {
diff --git a/src/picture.rs b/src/picture.rs
@@ -5,9 +5,8 @@ use serde::{
     Deserialize,
 };
 use std::fs::{self, File};
-use std::io::{self, Write};
+use std::io::Write;
 use std::path::{PathBuf, Path};
-use std::process::exit;
 
 /// WebP image quality
 const IMAGE_QUALITY: f32 = 50.0;
@@ -26,15 +25,23 @@ pub struct Picture {
 
 impl Picture {
     // TODO: Render GIF files as mp4 instead
-    pub fn render_thumbnail(&self, target_dir: &Path) -> io::Result<()> {
+    /// Returns `true` if rendering succeded (or was skipped) and `false`
+    /// otherwise. All warinings and error messages should be logged in here.
+    pub fn render_thumbnail(&self, target_dir: &Path) -> bool {
         let thumb_path = target_dir.join(self.file_name.clone() + ".webp");
 
         // Only try to render thumbnail in case the thumbnail file in the machine
         // is older than the source file
         if let (Ok(thumb_meta), Ok(img_meta)) = (fs::metadata(&thumb_path), fs::metadata(&self.path)) {
-            if thumb_meta.modified()? > img_meta.modified()? {
-                // TODO: Report a warning to the user
-                return Ok(());
+            let thumb_mod_date = thumb_meta.modified()
+                .expect("os should support file modification date");
+            let img_mod_date = img_meta.modified()
+                .expect("os should support file modification date");
+
+            if thumb_mod_date > img_mod_date {
+                println!("WARNING: Skipped rendering the WebP thumbnail for {name:?}.\nUpdate {path:?} to overwrite this behaviour",
+                         name = self.file_name, path = self.path);
+                return true;
             }
         }
 
@@ -42,7 +49,7 @@ impl Picture {
             Ok(f)    => f,
             Err(err) => {
                 eprintln!("ERROR: Couldn't open WebP thumbnail file {thumb_path:?}: {err}");
-                exit(1);
+                return false;
             }
         };
 
@@ -53,7 +60,7 @@ impl Picture {
                     "ERROR: Couldn't open file {path:?} to render WebP thumbnail: {err}",
                     path = self.file_name
                 );
-                exit(1);
+                return false;
             }
         };
 
@@ -61,10 +68,10 @@ impl Picture {
             Ok(img)  => img,
             Err(err) => {
                 eprintln!(
-                    "ERROR: Failed to decode image file {name:?}: {err}",
+                    "ERROR: Faileded to decode image file {name:?}: {err}",
                     name = self.file_name
                 );
-                exit(1);
+                return false;
             }
         };
 
@@ -84,10 +91,10 @@ impl Picture {
 
         if let Err(err) = thumb_file.write_all(&mem) {
             eprintln!("ERROR: Couldn't write WebP thumnail to file {thumb_path:?}: {err}");
-            exit(1);
+            return false;
         }
 
-        Ok(())
+        true
     }
 }