Purge Identical Photos Everywhere
Problem
The user sends photos of themselves to many contacts. Identical files accumulate across all contact folders, wasting disk space and leaving personal photos scattered everywhere.
Solution
A two-step flow: scan (preview) then replace (purge).
Backend — MediaService
Four new methods:
| Method | Purpose |
|---|---|
getPlaceholder(size) |
Generates a grey 200×200 PNG with “Removed” text via SVG overlay through sharp. Result cached in memory |
getPlaceholderJpeg(size, quality) |
Same but JPEG output, used for thumbs/ and micro/ directories. Also cached |
findIdenticalFiles(folder, filename) |
Reads source file, computes MD5. Scans ALL contact folders (photos/ subdirs + flat-layout roots). Uses stat.size pre-check to skip non-matching files without reading them. Returns { hash, size, matches[] } |
purgeIdenticalEverywhere(folder, filename) |
Calls findIdenticalFiles(), then overwrites each match with the PNG placeholder, its thumbnail with a JPEG placeholder, and its micro-thumbnail with a smaller JPEG placeholder. Returns { replaced, affectedFolders[] } |
Performance Optimizations
Size pre-check: Before computing an MD5 hash (which reads the entire file), findIdenticalFiles compares stat.size against the source. Most files differ in byte size, so >99% of candidates are eliminated with a single syscall.
Placeholder caching: The PNG, thumb JPEG, and micro JPEG placeholders are generated once and held in memory for the process lifetime. The placeholder’s own MD5 is also cached to prevent re-purging files that are already placeholders (idempotency guard).
API Routes
| Route | Method | Handler | Purpose |
|---|---|---|---|
/api/media/purge-preview/:folder/:filename |
GET | purgePreview |
Scan for matches, return count + list for confirmation |
/api/media/purge-everywhere/:folder/:filename |
POST | purgeEverywhere |
Execute replacement, return affected count |
Frontend UI
Three interaction points:
-
Photo grid hover button — Orange Ban icon, positioned left of download/delete buttons. Appears on hover over any photo thumbnail.
-
Lightbox top bar button — Same orange Ban icon in the full-screen viewer, next to download and delete.
-
Confirmation modal (z-60, above lightbox z-50):
- Loading spinner while scanning all folders
- Match count, affected folder count, file size, truncated MD5
- Scrollable list of every match (folder + filename)
- Warning that replacement is irreversible
- Cancel / “Replace N files” buttons
- After confirm: replaces files, closes lightbox, refreshes folder view
Files Modified
| File | Changes |
|---|---|
backend/src/services/MediaService.ts |
getPlaceholder(), getPlaceholderJpeg(), findIdenticalFiles(), purgeIdenticalEverywhere() + cache fields |
backend/src/controllers/mediaController.ts |
purgePreview, purgeEverywhere controllers |
backend/src/routes/mediaRoutes.ts |
Two new routes |
frontend/src/services/api.ts |
purgePreview(), purgeEverywhere() methods |
frontend/src/pages/MediaBrowserPage.tsx |
Purge button (grid + lightbox), confirmation modal |