Skip to content

Commit b624fcb

Browse files
committed
feat: skipping media that's been already generated
1 parent 4c1dab8 commit b624fcb

2 files changed

Lines changed: 74 additions & 11 deletions

File tree

src/site/website.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::time::{Duration, Instant};
66
use rayon::prelude::*;
77

88
use super::media_library::{MediaLibrary, MediaLibraryError};
9-
use super::website_info::{SITE_TOML, WebsiteInfo, WebsiteInfoError};
9+
use super::website_info::{WebsiteInfo, WebsiteInfoError, SITE_TOML};
1010
use super::website_media::{GenerationResult, WebsiteMedia};
1111

1212
const DEFAULT_BUILD_DIR: &str = "build";
@@ -23,18 +23,24 @@ const TEMPLATE_RSS: &str = include_str!("../../template/rss.xml");
2323

2424
pub struct BuildReport {
2525
pub items_processed: usize,
26+
pub items_skipped: usize,
2627
pub total_media_size: u64,
2728
pub total_thumbs_size: u64,
2829
pub processing_time: Duration,
2930
}
3031

3132
impl BuildReport {
32-
fn from_results(results: Vec<GenerationResult>, processing_time: Duration) -> Self {
33+
fn from_results(
34+
results: Vec<GenerationResult>,
35+
items_skipped: usize,
36+
processing_time: Duration,
37+
) -> Self {
3338
let items_processed = results.len();
3439
let total_media_size = results.iter().map(|r| r.media_size).sum();
3540
let total_thumbs_size = results.iter().map(|r| r.thumb_size).sum();
3641
Self {
3742
items_processed,
43+
items_skipped,
3844
total_media_size,
3945
total_thumbs_size,
4046
processing_time,
@@ -46,6 +52,7 @@ impl std::fmt::Display for BuildReport {
4652
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4753
writeln!(f, "Build report:")?;
4854
writeln!(f, " Items processed: {}", self.items_processed)?;
55+
writeln!(f, " Items skipped (up to date): {}", self.items_skipped)?;
4956
writeln!(
5057
f,
5158
" Total media size: {}",
@@ -174,7 +181,7 @@ impl Website {
174181
library.update_metadata(&source_media_path)?;
175182

176183
// Scan source media directory, copy files, generate thumbnails, and collect data entries
177-
let (clutterlog_data, rss_items, generation_results) =
184+
let (clutterlog_data, rss_items, generation_results, items_skipped) =
178185
self.scan_and_copy_media(&source_media_path, &build_media_path, &library)?;
179186

180187
// Render index.html from template
@@ -213,6 +220,7 @@ impl Website {
213220

214221
Ok(BuildReport::from_results(
215222
generation_results,
223+
items_skipped,
216224
start.elapsed(),
217225
))
218226
}
@@ -222,11 +230,11 @@ impl Website {
222230
source_path: &Path,
223231
dest_path: &Path,
224232
library: &MediaLibrary,
225-
) -> Result<(String, Vec<String>, Vec<GenerationResult>), WebsiteError> {
233+
) -> Result<(String, Vec<String>, Vec<GenerationResult>, usize), WebsiteError> {
226234
let base_url = self.info.url.trim_end_matches('/');
227235

228236
if !source_path.exists() {
229-
return Ok(("[]".to_string(), Vec::new(), Vec::new()));
237+
return Ok(("[]".to_string(), Vec::new(), Vec::new(), 0));
230238
}
231239

232240
let dir_entries = fs::read_dir(source_path)
@@ -243,18 +251,25 @@ impl Website {
243251
})
244252
.collect();
245253

246-
// Process items in parallel: copy files and generate thumbnails
247-
let processed: Vec<Result<(GenerationResult, String, String), WebsiteError>> = items
254+
// Process items in parallel: copy files and generate thumbnails (skipping up-to-date items)
255+
// Each result includes a bool indicating whether the item was skipped.
256+
let processed: Vec<Result<(GenerationResult, String, String, bool), WebsiteError>> = items
248257
.par_iter()
249258
.filter_map(|(path, datetime)| {
250259
let item = WebsiteMedia::from_path(path, datetime.as_deref())?;
251-
let result = item.copy_and_generate_thumb(dest_path);
260+
261+
let (result, skipped) = if item.is_up_to_date(dest_path) {
262+
(item.read_existing_sizes(dest_path), true)
263+
} else {
264+
(item.copy_and_generate_thumb(dest_path), false)
265+
};
266+
252267
let entry = item.to_json_entry(base_url, DEFAULT_MEDIA_DIR);
253268
let rss_item = item.to_rss_item(base_url, DEFAULT_MEDIA_DIR);
254269
let image_url = item.image_url(base_url, DEFAULT_MEDIA_DIR);
255270
Some(result.map(|mut r| {
256271
r.image_url = image_url;
257-
(r, entry, rss_item)
272+
(r, entry, rss_item, skipped)
258273
}))
259274
})
260275
.collect();
@@ -263,8 +278,12 @@ impl Website {
263278
let mut results: Vec<GenerationResult> = Vec::new();
264279
let mut entries: Vec<String> = Vec::new();
265280
let mut rss_items: Vec<String> = Vec::new();
281+
let mut items_skipped: usize = 0;
266282
for item_result in processed {
267-
let (gen_result, entry, rss_item) = item_result?;
283+
let (gen_result, entry, rss_item, skipped) = item_result?;
284+
if skipped {
285+
items_skipped += 1;
286+
}
268287
results.push(gen_result);
269288
entries.push(entry);
270289
rss_items.push(rss_item);
@@ -276,7 +295,7 @@ impl Website {
276295
format!("[\n{}\n ]", entries.join(",\n"))
277296
};
278297

279-
Ok((json, rss_items, results))
298+
Ok((json, rss_items, results, items_skipped))
280299
}
281300
}
282301

src/site/website_media.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,50 @@ impl WebsiteMedia {
108108
}
109109
}
110110

111+
/// Check whether the processed output (copied media + thumbnail) in `dest_media`
112+
/// is already up to date with respect to the source media file.
113+
/// Returns `true` if both output files exist and the thumbnail is newer than the source.
114+
pub fn is_up_to_date(&self, dest_media: &Path) -> bool {
115+
let dest_file = dest_media.join(&self.filename);
116+
let thumb_file = dest_media.join(self.thumb_filename());
117+
118+
let thumb_mtime = match fs::metadata(&thumb_file).and_then(|m| m.modified()) {
119+
Ok(t) => t,
120+
Err(_) => return false,
121+
};
122+
123+
let dest_mtime = match fs::metadata(&dest_file).and_then(|m| m.modified()) {
124+
Ok(t) => t,
125+
Err(_) => return false,
126+
};
127+
128+
let source_mtime = match fs::metadata(&self.source_path).and_then(|m| m.modified()) {
129+
Ok(t) => t,
130+
Err(_) => return false,
131+
};
132+
133+
thumb_mtime >= source_mtime && dest_mtime >= source_mtime
134+
}
135+
136+
/// Read file sizes from already-processed output files without regenerating them.
137+
pub fn read_existing_sizes(&self, dest_media: &Path) -> Result<GenerationResult, WebsiteError> {
138+
let dest_file = dest_media.join(&self.filename);
139+
let thumb_file = dest_media.join(self.thumb_filename());
140+
141+
let media_size = fs::metadata(&dest_file)
142+
.map_err(|e| WebsiteError::Io(dest_file, e))?
143+
.len();
144+
let thumb_size = fs::metadata(&thumb_file)
145+
.map_err(|e| WebsiteError::Io(thumb_file, e))?
146+
.len();
147+
148+
Ok(GenerationResult {
149+
media_size,
150+
thumb_size,
151+
image_url: String::new(),
152+
})
153+
}
154+
111155
pub fn copy_and_generate_thumb(
112156
&self,
113157
dest_media: &Path,

0 commit comments

Comments
 (0)