From 9ac5b0bcfa6614c0d5656b9b09bf46bf1098d698 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 5 Jun 2026 15:50:24 +0300 Subject: Add fast splash refresh action --- README.md | 22 +++++++---- bin/photoalbum | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- src/photoalbum.sh | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- tests/cli.sh | 86 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 321 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index a488b02..3409a02 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,7 @@ modern `magick` command and falls back to `convert` when needed. ``` photoalbum --init photoalbum --generate [--config PATH] [OPTIONS] +photoalbum --refresh-splash [--config PATH] [OPTIONS] photoalbum --dry-run [--config PATH] [OPTIONS] photoalbum --print-config [--config PATH] [OPTIONS] photoalbum --clean [--config PATH] [OPTIONS] @@ -49,6 +50,7 @@ photoalbum --version * `--init` creates `./photoalbum.conf` in the current working directory from the default config. It refuses to overwrite an existing file. * `--generate` builds the static album. +* `--refresh-splash` rewrites only the generated root splash page. * `--dry-run` loads the config and overrides, validates the planned generation, and prints the effective paths, image count, tarball plan, and generated file plan without writing output or running ImageMagick or tar. @@ -57,12 +59,12 @@ photoalbum --version ImageMagick, running tar, cleaning, or initializing. * `--clean` removes the configured output directory. * `--version` prints the program version. -* `--config PATH` selects the config file for `--generate`, `--dry-run`, - `--print-config`, or `--clean`. +* `--config PATH` selects the config file for `--generate`, + `--refresh-splash`, `--dry-run`, `--print-config`, or `--clean`. When `--config PATH` is not provided, `--generate`, `--dry-run`, -`--print-config`, and `--clean` read `./photoalbum.conf`. If the file is -missing, run `photoalbum --init` first. +`--print-config`, `--refresh-splash`, and `--clean` read `./photoalbum.conf`. +If the file is missing, run `photoalbum --init` first. The config file is a Bash file with assignments such as `INCOMING_DIR`, `DIST_DIR`, `TEMPLATE_DIR`, `TITLE`, `HEIGHT`, `THUMBHEIGHT`, `MAXPREVIEWS`, @@ -123,15 +125,21 @@ By default, the generated root `index.html` is a no-JavaScript splash page using a randomly selected album photo. Set `SPLASH_PAGE=no` or pass `--no-splash` to restore the top-level redirect to `page-1.html`. +To quickly pick a new random splash photo for an already generated album, run +`photoalbum --refresh-splash`. This rewrites only `DIST_DIR/index.html` using +the existing `photos` and `blurs` output, so it avoids reprocessing images and +rerendering album pages. It requires `SPLASH_PAGE=yes`; pass +`--random-seed VALUE` when you need a repeatable pick. + By default, splash and background photos, animation classes, generated timestamps, and `--shuffle` preview order remain non-deterministic. Set `RANDOM_SEED` in the config, or pass `--random-seed VALUE`, to make those choices repeatable for stable tests or reproducible album builds. Use the same seed and inputs to produce the same HTML. -`--dry-run` and `--print-config` accept the same override options as -`--generate`. `--clean` accepts the same override options, but only `--dist` -changes what it removes. +`--dry-run`, `--print-config`, and `--refresh-splash` accept the same override +options as `--generate`. `--clean` accepts the same override options, but only +`--dist` changes what it removes. Output is human-readable by default and reports routine generation progress. Use `--quiet` to suppress routine progress while still writing errors to stderr. diff --git a/bin/photoalbum b/bin/photoalbum index dfef411..653e1a8 100755 --- a/bin/photoalbum +++ b/bin/photoalbum @@ -15,6 +15,7 @@ usage() { cat - <&2 Usage: $0 --generate [--config PATH] [OPTIONS] + $0 --refresh-splash [--config PATH] [OPTIONS] $0 --dry-run [--config PATH] [OPTIONS] $0 --print-config [--config PATH] [OPTIONS] $0 --clean [--config PATH] [OPTIONS] @@ -1266,10 +1267,16 @@ render_album_splash_page() { local -r html_dir="$1"; shift local -r blurs_dir="$1"; shift local -r backhref="$1"; shift + local html='index.html' local photo - photo=$(randomphoto "$photos_dir" splash) - template 'splash' 'index.html' \ + if (( $# > 0 )); then + html="$1" + shift + fi + + photo=$(random_splash_photo "$photos_dir" "$blurs_dir") + template 'splash' "$html" \ html_dir "$html_dir" \ backhref "$backhref" \ blurs_dir "$blurs_dir" \ @@ -1374,6 +1381,43 @@ render_album_pages() { render_album_index "$photos_dir" "$html_dir" "$blurs_dir" "$backhref" } +splash_photo_files() { + local -r photos_dir="$1"; shift + local -r blurs_dir="$1"; shift + local photo + + while IFS= read -r photo; do + if [ -f "$DIST_DIR/$blurs_dir/$photo" ]; then + printf '%s\n' "$photo" + fi + done < <( + find "$DIST_DIR/$photos_dir" -maxdepth 1 -type f -printf '%f\n' \ + | sort + ) +} + +random_splash_photo() { + local -r photos_dir="$1"; shift + local -r blurs_dir="$1"; shift + local -i index + local photo + local -a photos=() + + while IFS= read -r photo; do + photos+=("$photo") + done < <(splash_photo_files "$photos_dir" "$blurs_dir") + + if (( ${#photos[@]} == 0 )); then + printf 'ERROR: No splash photos found in %s with matching blurs in %s\n' \ + "$(_display_path "$DIST_DIR/$photos_dir")" \ + "$(_display_path "$DIST_DIR/$blurs_dir")" >&2 + return 1 + fi + + index=$(random_index "photo:$photos_dir:splash" "${#photos[@]}") + printf '%s\n' "${photos[index]}" +} + randomphoto() { local -r photos_dir="$1"; shift local -r context="${1:-$photos_dir}" @@ -1536,6 +1580,24 @@ generate() { write_generation_metadata "$tarball_name" } +refresh_splash() { + local tmp_html + local tmp_path + + tmp_path=$(mktemp "$DIST_DIR/.index.html.XXXXXX") + tmp_html=$(basename "$tmp_path") + rm -f "$tmp_path" + + if render_album_splash_page 'photos' '.' 'blurs' '.' "$tmp_html"; then + mv "$DIST_DIR/$tmp_html" "$DIST_DIR/index.html" + log_info "Refreshed splash page $(_display_path "$DIST_DIR/index.html")" + return + fi + + rm -f "$DIST_DIR/$tmp_html" + return 1 +} + dry_run() { local -i image_count=0 local -i html_index_count=1 @@ -1994,6 +2056,45 @@ validate_template_dir() { done } +validate_refresh_splash_config() { + local required_var + local -a required_vars=( + TITLE + DIST_DIR + TEMPLATE_DIR + ) + + for required_var in "${required_vars[@]}"; do + require_config_var "$required_var" + done + + validate_yes_no_config_var SPLASH_PAGE + + if [ "${SPLASH_PAGE:-yes}" != yes ]; then + config_error 'SPLASH_PAGE must be yes to refresh the splash page' + fi + + validate_dist_dir + + if [[ ! -d "$TEMPLATE_DIR" || ! -r "$TEMPLATE_DIR" \ + || ! -x "$TEMPLATE_DIR" ]]; then + config_error "TEMPLATE_DIR $TEMPLATE_DIR must be a readable directory" + fi + + if [ ! -r "$TEMPLATE_DIR/splash.tmpl" ]; then + config_error \ + "template file $TEMPLATE_DIR/splash.tmpl must be readable" + fi + + if [ ! -d "$DIST_DIR/photos" ]; then + config_error "DIST_DIR photos directory $DIST_DIR/photos must exist" + fi + + if [ ! -d "$DIST_DIR/blurs" ]; then + config_error "DIST_DIR blurs directory $DIST_DIR/blurs must exist" + fi +} + validate_imagemagick() { if command -v magick >/dev/null 2>&1; then return @@ -2174,7 +2275,8 @@ parse_cli_arguments() { --quiet) PHOTOALBUM_OUTPUT_MODE=quiet ;; - --version|--init|--clean|--generate|--dry-run|--print-config) + --version|--init|--clean|--generate|--refresh-splash|--dry-run|\ + --print-config) set_cli_action "$option" ;; *) @@ -2258,6 +2360,10 @@ run_configured_action() { validate_generation_config generate_staged ;; + --refresh-splash) + validate_refresh_splash_config + refresh_splash + ;; --dry-run) validate_generation_config no dry_run @@ -2274,7 +2380,7 @@ run_action() { --version|--init) run_simple_action ;; - --clean|--generate|--dry-run|--print-config) + --clean|--generate|--refresh-splash|--dry-run|--print-config) run_configured_action ;; *) diff --git a/src/photoalbum.sh b/src/photoalbum.sh index 82353a5..234bf8a 100755 --- a/src/photoalbum.sh +++ b/src/photoalbum.sh @@ -15,6 +15,7 @@ usage() { cat - <&2 Usage: $0 --generate [--config PATH] [OPTIONS] + $0 --refresh-splash [--config PATH] [OPTIONS] $0 --dry-run [--config PATH] [OPTIONS] $0 --print-config [--config PATH] [OPTIONS] $0 --clean [--config PATH] [OPTIONS] @@ -1266,10 +1267,16 @@ render_album_splash_page() { local -r html_dir="$1"; shift local -r blurs_dir="$1"; shift local -r backhref="$1"; shift + local html='index.html' local photo - photo=$(randomphoto "$photos_dir" splash) - template 'splash' 'index.html' \ + if (( $# > 0 )); then + html="$1" + shift + fi + + photo=$(random_splash_photo "$photos_dir" "$blurs_dir") + template 'splash' "$html" \ html_dir "$html_dir" \ backhref "$backhref" \ blurs_dir "$blurs_dir" \ @@ -1374,6 +1381,43 @@ render_album_pages() { render_album_index "$photos_dir" "$html_dir" "$blurs_dir" "$backhref" } +splash_photo_files() { + local -r photos_dir="$1"; shift + local -r blurs_dir="$1"; shift + local photo + + while IFS= read -r photo; do + if [ -f "$DIST_DIR/$blurs_dir/$photo" ]; then + printf '%s\n' "$photo" + fi + done < <( + find "$DIST_DIR/$photos_dir" -maxdepth 1 -type f -printf '%f\n' \ + | sort + ) +} + +random_splash_photo() { + local -r photos_dir="$1"; shift + local -r blurs_dir="$1"; shift + local -i index + local photo + local -a photos=() + + while IFS= read -r photo; do + photos+=("$photo") + done < <(splash_photo_files "$photos_dir" "$blurs_dir") + + if (( ${#photos[@]} == 0 )); then + printf 'ERROR: No splash photos found in %s with matching blurs in %s\n' \ + "$(_display_path "$DIST_DIR/$photos_dir")" \ + "$(_display_path "$DIST_DIR/$blurs_dir")" >&2 + return 1 + fi + + index=$(random_index "photo:$photos_dir:splash" "${#photos[@]}") + printf '%s\n' "${photos[index]}" +} + randomphoto() { local -r photos_dir="$1"; shift local -r context="${1:-$photos_dir}" @@ -1536,6 +1580,24 @@ generate() { write_generation_metadata "$tarball_name" } +refresh_splash() { + local tmp_html + local tmp_path + + tmp_path=$(mktemp "$DIST_DIR/.index.html.XXXXXX") + tmp_html=$(basename "$tmp_path") + rm -f "$tmp_path" + + if render_album_splash_page 'photos' '.' 'blurs' '.' "$tmp_html"; then + mv "$DIST_DIR/$tmp_html" "$DIST_DIR/index.html" + log_info "Refreshed splash page $(_display_path "$DIST_DIR/index.html")" + return + fi + + rm -f "$DIST_DIR/$tmp_html" + return 1 +} + dry_run() { local -i image_count=0 local -i html_index_count=1 @@ -1994,6 +2056,45 @@ validate_template_dir() { done } +validate_refresh_splash_config() { + local required_var + local -a required_vars=( + TITLE + DIST_DIR + TEMPLATE_DIR + ) + + for required_var in "${required_vars[@]}"; do + require_config_var "$required_var" + done + + validate_yes_no_config_var SPLASH_PAGE + + if [ "${SPLASH_PAGE:-yes}" != yes ]; then + config_error 'SPLASH_PAGE must be yes to refresh the splash page' + fi + + validate_dist_dir + + if [[ ! -d "$TEMPLATE_DIR" || ! -r "$TEMPLATE_DIR" \ + || ! -x "$TEMPLATE_DIR" ]]; then + config_error "TEMPLATE_DIR $TEMPLATE_DIR must be a readable directory" + fi + + if [ ! -r "$TEMPLATE_DIR/splash.tmpl" ]; then + config_error \ + "template file $TEMPLATE_DIR/splash.tmpl must be readable" + fi + + if [ ! -d "$DIST_DIR/photos" ]; then + config_error "DIST_DIR photos directory $DIST_DIR/photos must exist" + fi + + if [ ! -d "$DIST_DIR/blurs" ]; then + config_error "DIST_DIR blurs directory $DIST_DIR/blurs must exist" + fi +} + validate_imagemagick() { if command -v magick >/dev/null 2>&1; then return @@ -2174,7 +2275,8 @@ parse_cli_arguments() { --quiet) PHOTOALBUM_OUTPUT_MODE=quiet ;; - --version|--init|--clean|--generate|--dry-run|--print-config) + --version|--init|--clean|--generate|--refresh-splash|--dry-run|\ + --print-config) set_cli_action "$option" ;; *) @@ -2258,6 +2360,10 @@ run_configured_action() { validate_generation_config generate_staged ;; + --refresh-splash) + validate_refresh_splash_config + refresh_splash + ;; --dry-run) validate_generation_config no dry_run @@ -2274,7 +2380,7 @@ run_action() { --version|--init) run_simple_action ;; - --clean|--generate|--dry-run|--print-config) + --clean|--generate|--refresh-splash|--dry-run|--print-config) run_configured_action ;; *) diff --git a/tests/cli.sh b/tests/cli.sh index be71e6a..2cc9d19 100755 --- a/tests/cli.sh +++ b/tests/cli.sh @@ -1987,6 +1987,86 @@ test_generate_cli_no_splash_overrides_config() { test::teardown } +test_refresh_splash_rewrites_only_index_from_existing_assets() { + local after_index + local after_metadata + local after_page + local before_index + local before_metadata + local before_page + local config_file + local failing_bin + local fake_bin + local output + + test::setup + fake_bin="$TEST_TMPDIR/bin" + failing_bin="$TEST_TMPDIR/failing-bin" + config_file="$TEST_TMPDIR/photoalbum.conf" + + test::install_fake_imagemagick "$fake_bin" + PATH="$fake_bin:$PATH" \ + test::generate_fixture_images "$TEST_TMPDIR/incoming" + test::write_album_config \ + "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \ + 'Refresh splash album' 2 + + ( + cd "$TEST_TMPDIR" + PATH="$fake_bin:$PATH" "$TEST_PHOTOALBUM" \ + --generate --random-seed seed-one + ) + + before_index=$(<"$TEST_TMPDIR/dist/index.html") + before_page=$(<"$TEST_TMPDIR/dist/page-1.html") + before_metadata=$(<"$TEST_TMPDIR/dist/photoalbum.json") + + test::install_failing_imagemagick "$failing_bin" + output=$( + cd "$TEST_TMPDIR" + PATH="$failing_bin:$PATH" "$TEST_PHOTOALBUM" \ + --refresh-splash --random-seed seed-two + ) + + after_index=$(<"$TEST_TMPDIR/dist/index.html") + after_page=$(<"$TEST_TMPDIR/dist/page-1.html") + after_metadata=$(<"$TEST_TMPDIR/dist/photoalbum.json") + + test::assert_contains 'Refreshed splash page' "$output" + test::assert_contains '&2 + exit 1 + fi + test "$before_page" = "$after_page" + test "$before_metadata" = "$after_metadata" + test::teardown +} + +test_refresh_splash_requires_existing_generated_assets() { + local config_file + local output + + test::setup + config_file="$TEST_TMPDIR/photoalbum.conf" + mkdir -p "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" + test::write_album_config \ + "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \ + 'Missing splash assets album' 40 + + output=$( + cd "$TEST_TMPDIR" + test::capture_failure_output "$TEST_PHOTOALBUM" --refresh-splash + ) + + test::assert_contains \ + "ERROR: DIST_DIR photos directory $TEST_TMPDIR/dist/photos must exist" \ + "$output" + test::assert_path_absent "$TEST_TMPDIR/dist/index.html" + test::teardown +} + test_generate_replaces_dist_after_success() { local config_file local fake_bin @@ -2696,6 +2776,12 @@ main() { test::run_case \ '--generate --no-splash keeps root index redirect' \ test_generate_cli_no_splash_overrides_config + test::run_case \ + '--refresh-splash rewrites only root index from existing assets' \ + test_refresh_splash_rewrites_only_index_from_existing_assets + test::run_case \ + '--refresh-splash requires existing generated assets' \ + test_refresh_splash_requires_existing_generated_assets test::run_case \ '--generate replaces final dist after success' \ test_generate_replaces_dist_after_success -- cgit v1.2.3