Commit Graph

16 Commits

Author SHA1 Message Date
Carlos
a7b023c193 Stage 2 #4: Destinations management UI
Adds 'Destinations' sidebar entry + view + add/edit/delete/test modal.
Generate-keypair button shows the public key for the user to paste into
the remote authorized_keys.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 20:29:22 -04:00
Carlos
7436b23db3 Stage 2 #1: SFTP destinations CRUD + connection test
Foundation for the move/quarantine pipeline. Lets users register one or
more remote SFTP destinations through the API, store credentials at rest
under /data/sftp/{id}.{password|key} (mode 600), and verify connectivity
+ write access via a test endpoint.

Endpoints:
  GET    /api/sftp/destinations
  POST   /api/sftp/destinations             — create
  PUT    /api/sftp/destinations/{id}        — update
  DELETE /api/sftp/destinations/{id}
  POST   /api/sftp/destinations/{id}/test   — connect, stat base_path, mkdir probe
  POST   /api/sftp/keypair                  — generate ED25519 keypair

Host keys pinned per-destination on first connect (TOFU); subsequent
mismatches are rejected. paramiko added to requirements.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 20:04:42 -04:00
Carlos
8b0fee0055 Folder priority + path penalty: match folder segments only, not filenames
Both _folder_priority and _path_penalty were scanning the entire path
string including the basename. A file named 'mytrashed_pic.jpg' in
/photos/MobileBackup/ would falsely match the 'trash' token.

Now only directory segments are checked; filename never influences keeper
selection beyond its actual path location.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 18:48:30 -04:00
Carlos
3128ddc593 Fix 'failed to load group' on click
The detail-panel insertion logic mixed parent contexts: it called
grid.parentNode.insertBefore() but used a child-of-grid as the reference
node. insertBefore requires the reference node to be a child of the
target parent — it threw 'node is not a child of this node' on every
click.

Replaced the inter-row positioning with simple insert-after-grid. Same
visual outcome since panel.scrollIntoView() handles user focus.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 18:29:32 -04:00
Carlos
759288b37e Pre-generate all thumbnails up-front, not on scroll
After every scan, automatically kick off a background thread that
generates a JPEG thumbnail for every file in a duplicate group and
caches it locally at /data/thumbs/. Idempotent — already-cached files
are skipped.

New endpoints:
  POST /api/thumbs/generate            — start pre-gen for all files
  POST /api/thumbs/generate?only_in_groups=true  — only dup-group files
  GET  /api/thumbs/status              — progress (total/done/skipped/failed)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 16:33:19 -04:00
Carlos
4c21e9fa1c Add workstation-local thumbnail cache + HEIC support
Thumbnails (256px JPEG, q80) generated on first request and cached at
/data/thumbs/<shard>/<file_id>.jpg — i.e. on the workstation's local SSD,
not the NAS. Subsequent requests serve straight from cache, never
re-fetching from /photos.

HEIC/HEIF decoded via pillow-heif so iPhone photos finally render.
Videos cached as a single ffmpeg-extracted frame, not regenerated each
request. New DELETE /api/thumb/cache endpoint to wipe it.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 16:29:29 -04:00
Carlos
81b38cb5bb CSV export: path column now contains directory only
Filename was duplicated in both columns; trimmed the basename off path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 16:03:34 -04:00
Carlos
6827c5965f Lowest priority (11) for Google Photos / Takeout / backup folders
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 15:53:15 -04:00
Carlos
399a80cb70 Add explicit folder-priority ranking for keeper selection
#recycle (10) ranks worst, MobileBackup (1) best, default 2.
Folder priority dominates resolution + path-penalty; mtime stays as final
tiebreak. Override via /data/folder_priority.json (cached per process).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 15:52:04 -04:00
Carlos
d95bf69be0 Fix CSV export crash on filenames with embedded newlines
Use QUOTE_ALL + sanitise NUL/CR/LF in path/filename/exif fields. Default
csv dialect rejected fields containing line terminators with 'need to
escape, but no escapechar set'.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-26 13:17:11 -04:00
Carlos
14c6012808 Smarter keeper selection: folder-name + mtime signals
Adds a path-penalty score that downranks files in folders named Trashed,
Dups, Backup, Copy, Old, Archive, plus a penalty for repeated path segments
(e.g. Desktop/Desktop/Files) and very deep paths. Also captures and uses
file mtime as a tiebreaker — older files are usually the originals.

Applied to all four detection passes (sha256, phash, exif, filesize+dim)
and to auto-resolve-exact.

New file_mtime column with idempotent migration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 10:56:52 -04:00
Carlos
4d57b0af74 Bump package to 1.0.2
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 01:41:50 -04:00
Carlos
79ab0dbb05 Fix stale Gitea token in build-deb.sh
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-24 01:20:53 -04:00
Carlos
077fbd7e8f Fix .deb source staging — preserve app/ subdir for Dockerfile
build-deb.sh used 'cp -r app/ source/' which renames app to source
when source doesn't yet exist, dropping the app/ wrapper that the
Dockerfile's COPY app/ /app/ depends on. The 2>/dev/null || true
on the cp lines hid the resulting failures, so the .deb shipped a
broken /opt/dupfinder/source/ that build-from-source could not use.

Pre-create the source dir and copy each item to its explicit
destination path. Bump package version to 1.0.1.

Also rework dupfinder-setup.sh's image-prep step: prefer a local
image, then a quiet registry pull, then build from the bundled
source. Removes the loud registry-not-found error that scared users
when the (unpublished) tocmo0nlord/dupfinder image wasn't on Docker
Hub.
2026-04-24 01:05:53 -04:00
Carlos
76e89a7313 Fix .deb install path and Gitea upload auth
The .deb install instructions in the README pointed at a URL that
doesn't exist — Gitea exposes the Debian registry as an apt repo, not
as plain file downloads. Switched the README to the apt-repo flow
(add a sources.list line, then apt install).

Also fixed build-deb.sh: Gitea's Debian package endpoint returns
HTTP 405 for token-bearer auth; it requires HTTP basic auth (user +
token-as-password) and the literal /upload suffix on the URL.

Package built and pushed to the registry — apt install works now.
2026-04-24 00:55:11 -04:00
tocmo
f9164b4fa0 Add Debian package and Gitea APT repository support
debian/control, postinst, prerm, postrm — standard dpkg package lifecycle
debian/files/opt/dupfinder/dupfinder-setup.sh — interactive setup:
  checks Docker, detects NVIDIA GPU, prompts for photos/data paths,
  writes docker-compose.override.yml with GPU reservation if present,
  pulls image from registry (builds from source as fallback)
debian/files/usr/local/bin/dupfinder — CLI wrapper:
  setup / start / stop / restart / status / logs / open / update
debian/files/etc/systemd/system/dupfinder.service — systemd unit,
  guards against starting before setup has run
debian/build-deb.sh — builds .deb and uploads to Gitea package registry;
  prints the exact apt sources.list line on success

Install on any Debian/Ubuntu machine:
  echo "deb [trusted=yes] http://192.168.1.64:3000/api/packages/tocmo0nlord/debian bookworm main" \
    | sudo tee /etc/apt/sources.list.d/dupfinder.list
  sudo apt update && sudo apt install dupfinder
  sudo dupfinder setup

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 01:42:45 -04:00