summaryrefslogtreecommitdiffstats
path: root/DESIGN.md
diff options
context:
space:
mode:
authorKen D'Ambrosio <ken@jots.org>2026-05-22 22:50:35 +0000
committerKen D'Ambrosio <ken@jots.org>2026-05-22 22:50:35 +0000
commitd32b5e99afc6f0cffefa594510cda0e4f414db75 (patch)
treeb4c24a1a7264bcbde72c0fff906e7bf380c18a02 /DESIGN.md
parentde80b9871ebe1497c672f3c7c7bb5467dabcb83a (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 'DESIGN.md')
-rw-r--r--DESIGN.md25
1 files changed, 21 insertions, 4 deletions
diff --git a/DESIGN.md b/DESIGN.md
index 2edcc1a..7639ffc 100644
--- a/DESIGN.md
+++ b/DESIGN.md
@@ -163,6 +163,7 @@ used. The file is written atomically (write to a `.tmp` file, then
| `visible` | `true` | If `false`, hidden from non-admin visitors |
| `taken_at` | `null` | ISO 8601 timestamp from EXIF; used for chronological sorting |
| `width` / `height` | `null` | Pixel dimensions recorded by `update.rb` |
+| `exif_absent` | `null` | Set to `true` by `update.rb` when exiftool found no metadata; skips re-extraction on future rescans |
When `taken_at` is present on *any* file in an album, the entire album is
sorted chronologically. Albums with no `taken_at` data stay in filename
@@ -342,17 +343,31 @@ Run this after copying new media files onto the server. It is safe to
re-run at any time — all operations are idempotent.
```bash
-ruby /opt/albumen/scripts/update.rb [optional/subdir]
+ruby /opt/albumen/scripts/update.rb # full tree, skip unchanged dirs
+ruby /opt/albumen/scripts/update.rb 2024-Italy # explicit subtree, always runs
+ruby /opt/albumen/scripts/update.rb --force # full tree, ignore all sentinels
```
-**What it does, per directory:**
+**Change detection** — each directory gets a `.albumen_scanned` sentinel file
+whose mtime is set at the end of a successful scan. On subsequent runs the
+script compares `sentinel.mtime >= dir.mtime`: if true the directory is skipped
+entirely (no file I/O). A global run with nothing new completes in well under a
+second regardless of library size.
+
+Providing an explicit subdirectory argument bypasses the sentinel for that
+subtree, so `update.rb some-album` always rescans that album even if the
+directory mtime appears unchanged. `--force` bypasses sentinels for the whole
+tree.
+
+**What it does, per directory (when not skipped):**
1. Reads the existing `album.json` (or starts from defaults).
-2. Removes stale `files` entries for deleted files.
+2. Removes stale `files` entries for deleted files (and their thumbnails).
3. For each media file:
- **Images:** reads EXIF `DateTimeOriginal` (or `CreateDate`) and stores
it as `taken_at`; reads pixel dimensions. Both are skipped if already
- recorded.
+ recorded. If exiftool finds no metadata at all, sets `exif_absent: true`
+ so the tool is not re-invoked on future rescans of that file.
- **Videos:** runs `ffprobe` to record duration. Skipped if already
recorded.
- **All non-audio:** generates a thumbnail if one doesn't already exist.
@@ -361,6 +376,8 @@ ruby /opt/albumen/scripts/update.rb [optional/subdir]
fields (`title`, `description`, `cover`, `sort_reverse`, `visible`,
per-file `title`/`caption`/`visible`).
5. Writes the updated JSON atomically.
+6. Touches the `.albumen_scanned` sentinel so the next global run skips
+ this directory.
**Ownership:** When run as root (the typical case after an rsync), the
script calls `FileUtils.chown_R` to transfer ownership of the media tree