DocHub
Find and replace identical photos across all contacts by MD5 hash

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:

  1. Photo grid hover button — Orange Ban icon, positioned left of download/delete buttons. Appears on hover over any photo thumbnail.

  2. Lightbox top bar button — Same orange Ban icon in the full-screen viewer, next to download and delete.

  3. 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