diff options
| author | Ken D'Ambrosio <ken@jots.org> | 2026-05-22 22:50:35 +0000 |
|---|---|---|
| committer | Ken D'Ambrosio <ken@jots.org> | 2026-05-22 22:50:35 +0000 |
| commit | d32b5e99afc6f0cffefa594510cda0e4f414db75 (patch) | |
| tree | b4c24a1a7264bcbde72c0fff906e7bf380c18a02 /scripts/update.rb | |
| parent | de80b9871ebe1497c672f3c7c7bb5467dabcb83a (diff) | |
Speed up update.rb and fix UI always forcing full rescan
- update.rb: skip exiftool on images marked exif_absent (set after first
failed attempt); prevents repeated slow scans of old photos with no EXIF
- update.rb: explicit directory argument now implies force — passing a path
always rescans that subtree regardless of sentinel mtime
- app.rb: /admin/update no longer hardcodes --force; sentinel-based skipping
is used by default, making UI updates finish in seconds instead of minutes
- admin/album.erb: add "Force rescan all" checkbox to Run Update button;
checked state passes force=1 to the server and restores --force behavior
- README.md, DESIGN.md: document sentinel skipping, exif_absent flag, and
explicit-directory force behavior
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'scripts/update.rb')
| -rw-r--r-- | scripts/update.rb | 36 |
1 files changed, 31 insertions, 5 deletions
diff --git a/scripts/update.rb b/scripts/update.rb index 9953505..822405f 100644 --- a/scripts/update.rb +++ b/scripts/update.rb @@ -1,9 +1,12 @@ #!/usr/bin/env ruby # frozen_string_literal: true # -# Usage: ruby update.rb [relative/path] -# Without argument: process entire MEDIA_ROOT tree. -# With argument: process only that subdirectory (and its children). +# Usage: ruby update.rb [--force] [relative/path] +# Without argument: process entire MEDIA_ROOT tree, skipping directories +# whose mtime hasn't changed since the last scan. +# With argument: process only that subdirectory (and its children), +# always scanning regardless of mtime (explicit request). +# --force: scan entire tree ignoring all mtime sentinels. # # Resilience guarantees: # - album.json is written atomically (temp-file + rename), so a crash @@ -11,6 +14,8 @@ # - Thumbnails are checked before generation; already-done work is skipped. # - EXIF and dimension extraction are skipped if already recorded. # - Safe to re-run at any time; all operations are idempotent. +# - Unchanged directories are skipped via a .albumen_scanned sentinel file; +# pass --force to bypass. require 'json' require 'yaml' @@ -27,12 +32,25 @@ VIDEO_EXTS = %w[mp4 mov avi mkv webm m4v ogv].freeze AUDIO_EXTS = %w[mp3 flac ogg wav m4a aac].freeze MEDIA_EXTS = (IMAGE_EXTS + VIDEO_EXTS + AUDIO_EXTS).freeze TRANSCODE_EXTS = %w[avi mkv mov].freeze # not universally browser-playable; convert to MP4 +SENTINEL_FILE = '.albumen_scanned'.freeze + +# Explicit directory argument implies force — you asked for it, it should run. +FORCE_UPDATE = !!(ARGV.delete('--force') || ARGV[0]) # ── Directory processing ─────────────────────────────────────────────────────── def process_dir(dir) rel = dir.delete_prefix(MEDIA_ROOT).delete_prefix('/') label = rel.empty? ? '(root)' : rel + + unless FORCE_UPDATE + sentinel = File.join(dir, SENTINEL_FILE) + if File.exist?(sentinel) && File.mtime(sentinel) >= File.mtime(dir) + puts "Skipping #{label} (unchanged)" + return + end + end + puts "Scanning #{label}" json_path = File.join(dir, 'album.json') @@ -102,13 +120,15 @@ def process_dir(dir) end atomic_write_json(json_path, data) + FileUtils.touch(File.join(dir, SENTINEL_FILE)) end # ── Metadata enrichment ──────────────────────────────────────────────────────── def enrich_image(full, name, meta) - needs_exif = meta['taken_at'].nil? || meta['camera'].nil? || - meta['aperture'].nil? || meta['shutter'].nil? || meta['iso'].nil? + needs_exif = !meta['exif_absent'] && + (meta['taken_at'].nil? || meta['camera'].nil? || + meta['aperture'].nil? || meta['shutter'].nil? || meta['iso'].nil?) if needs_exif begin exif = MiniExiftool.new(full, numerical: false) @@ -133,6 +153,12 @@ def enrich_image(full, name, meta) rescue StandardError => e warn " #{name}: EXIF error — #{e.message}" end + + # If exiftool found nothing at all, record that so we don't retry on every re-scan. + if meta['taken_at'].nil? && meta['camera'].nil? && + meta['aperture'].nil? && meta['shutter'].nil? && meta['iso'].nil? + meta['exif_absent'] = true + end end # Dimensions (skip if already recorded) |
