feat: settings panel + multi-folder scanning
Settings panel (⚙ in header): - Watch Folders: add/remove paths, toggle enabled; worker polls every 30s and calls watcher.add/unwatch without restart - Ingestion: thumb/preview size and quality, applied per-file at ingest time - Duplicate Detection: configurable Hamming distance threshold - Danger Zone: clear all dismissed duplicate pairs Multi-folder scanning: - watch_folders table replaces single PHOTO_DIR env var - PHOTO_DIR env var seeds the first entry on startup - settings table with defaults for all tunable values - Worker reads settings from DB on each drain cycle so changes are live Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
8c5c36857f
commit
d18c855571
8 files changed
+494
-73
No files matched your search
+4
-17
@@ -1,9 +1,7 @@
|
||||
import { hammingDistance } from '../worker/hash.js';
|
||||
|
||||
const THRESHOLD = parseInt(process.env.DEDUPE_THRESHOLD ?? '10', 10);
|
||||
|
||||
// Run pairwise pHash comparison and store new similar pairs.
|
||||
export function scan(db) {
|
||||
export function scan(db, threshold) {
|
||||
const THRESHOLD = threshold ?? parseInt(process.env.DEDUPE_THRESHOLD ?? '10', 10);
|
||||
const start = Date.now();
|
||||
|
||||
const photos = db.raw.prepare(
|
||||
@@ -30,13 +28,10 @@ export function scan(db) {
|
||||
return { photos: photos.length, newPairs, duration: Date.now() - start };
|
||||
}
|
||||
|
||||
// Build connected-component groups from non-dismissed pairs.
|
||||
// Returns an array of photo-row arrays, largest groups first.
|
||||
export function getGroups(db) {
|
||||
const pairs = db.getDupePairs();
|
||||
if (!pairs.length) return [];
|
||||
|
||||
// Union-Find with path compression
|
||||
const parent = new Map();
|
||||
|
||||
function find(x) {
|
||||
@@ -45,18 +40,11 @@ export function getGroups(db) {
|
||||
return parent.get(x);
|
||||
}
|
||||
|
||||
function union(a, b) {
|
||||
parent.set(find(a), find(b));
|
||||
}
|
||||
function union(a, b) { parent.set(find(a), find(b)); }
|
||||
|
||||
for (const { photo_id_a, photo_id_b } of pairs) {
|
||||
union(photo_id_a, photo_id_b);
|
||||
}
|
||||
|
||||
// Normalize roots (path compression may have left stale entries)
|
||||
for (const { photo_id_a, photo_id_b } of pairs) union(photo_id_a, photo_id_b);
|
||||
for (const id of [...parent.keys()]) find(id);
|
||||
|
||||
// Group IDs by root
|
||||
const groups = new Map();
|
||||
for (const id of parent.keys()) {
|
||||
const root = find(id);
|
||||
@@ -64,7 +52,6 @@ export function getGroups(db) {
|
||||
groups.get(root).push(id);
|
||||
}
|
||||
|
||||
// Fetch photo details for all IDs
|
||||
const allIds = [...parent.keys()];
|
||||
const rows = db.raw.prepare(
|
||||
`SELECT id, thumb_path, preview_path, date_taken, filesize, width, height, camera_model
|
||||
|
||||
Reference in new issue
Block a user