- `;
+
+ `;
t.querySelector("img").onclick = ()=>openQuickView(rec);
- t.querySelector("button").onclick = ()=>api("/api/reveal", {path: rec.path});
+ t.querySelector(".btn-reveal").onclick = ()=>api("/api/reveal", {path: rec.path});
+ t.querySelector(".btn-del-dupe").onclick = async ()=>{
+ if(!confirm(`PERMANENT DISK DELETION\n\nAre you sure you want to permanently delete this duplicate file from disk?\n\nFile: ${rec.path}\n\nThis cannot be undone.`)) return;
+ const resp = await api("/api/delete_photo", {path: rec.path});
+ if(resp.ok){
+ showToast("Deleted duplicate copy: " + os_basename(rec.path), "ok");
+ loadDupeGroups();
+ } else {
+ showToast(resp.error || "Failed to delete duplicate file", "error");
+ }
+ };
rowEl.appendChild(t);
});
grp.appendChild(rowEl);
diff --git a/server.py b/server.py
index b4b9d6a..d06cc07 100644
--- a/server.py
+++ b/server.py
@@ -523,14 +523,33 @@ def build_dedupe_index():
S.dedupe_status = "idle"
def find_duplicate_groups():
- """Groups of exact perceptual-hash matches — visually identical / re-saved copies."""
+ """Groups of exact perceptual-hash matches and size/filename matches."""
records = {r["path"]: r for r in load_search_index()}
cache = load_phash_cache()
- by_hash = {}
+ groups_by_key = {}
+
+ # 1. Group by pHash if available
for path, h in cache.items():
- if path in records:
- by_hash.setdefault(h, []).append(records[path])
- groups = [g for g in by_hash.values() if len(g) > 1]
+ if path in records and os.path.exists(path):
+ groups_by_key.setdefault(f"phash:{h}", []).append(records[path])
+
+ # 2. Also group by (file size, filename) for non-hashed or exact size matches
+ by_size_name = {}
+ for path, r in records.items():
+ if path not in cache and os.path.exists(path):
+ try:
+ sz = os.path.getsize(path)
+ name = os.path.basename(path).lower()
+ key = f"size:{sz}_{name}"
+ by_size_name.setdefault(key, []).append(r)
+ except Exception:
+ pass
+
+ for key, g in by_size_name.items():
+ if len(g) > 1:
+ groups_by_key[key] = g
+
+ groups = [g for g in groups_by_key.values() if len(g) > 1]
groups.sort(key=len, reverse=True)
return groups