feat(duplicates): phased scan progress with skipped-file reporting
Replace the [scanned, total] tuple progress event with a structured
{phase, processed, total, skipped} payload covering all three scan
phases (checking, hashing, confirming). find_duplicates now returns a
DuplicateScanResult with scanned/candidate/skipped counts, and the UI
surfaces phase labels plus a warning when unreadable files were
skipped. Also adds a clean:app script for cargo clean.
This commit is contained in:
@@ -278,12 +278,16 @@ export function BackgroundTasks() {
|
||||
id: -1,
|
||||
name: "Duplicate Scan",
|
||||
stages: [{
|
||||
label: "Hashing",
|
||||
label: duplicateScanProgress?.phase === "checking"
|
||||
? "Checking"
|
||||
: duplicateScanProgress?.phase === "confirming"
|
||||
? "Confirming"
|
||||
: "Hashing",
|
||||
detail: duplicateScanProgress
|
||||
? `${duplicateScanProgress.scanned.toLocaleString()} / ${duplicateScanProgress.total.toLocaleString()}`
|
||||
? `${duplicateScanProgress.processed.toLocaleString()} / ${duplicateScanProgress.total.toLocaleString()}${duplicateScanProgress.skipped > 0 ? ` · ${duplicateScanProgress.skipped.toLocaleString()} skipped` : ""}`
|
||||
: "Starting…",
|
||||
progress: duplicateScanProgress && duplicateScanProgress.total > 0
|
||||
? (duplicateScanProgress.scanned / duplicateScanProgress.total) * 100
|
||||
? (duplicateScanProgress.processed / duplicateScanProgress.total) * 100
|
||||
: null,
|
||||
failed: false,
|
||||
}],
|
||||
@@ -310,7 +314,8 @@ export function BackgroundTasks() {
|
||||
const embeddingStage = primary.stages.find((s) => s.label === "Embeddings");
|
||||
const taggingStage = primary.stages.find((s) => s.label === "Tags");
|
||||
const scanningStage = primary.stages.find((s) => s.label === "Scanning");
|
||||
const barProgress = embeddingStage?.progress ?? taggingStage?.progress ?? scanningStage?.progress ?? null;
|
||||
const duplicateStage = primary.id === -1 ? primary.stages[0] : null;
|
||||
const barProgress = embeddingStage?.progress ?? taggingStage?.progress ?? scanningStage?.progress ?? duplicateStage?.progress ?? null;
|
||||
|
||||
return (
|
||||
<div className="shrink-0 border-b border-white/[0.06]">
|
||||
@@ -434,7 +439,8 @@ export function BackgroundTasks() {
|
||||
const taskEmbeddingStage = task.stages.find((s) => s.label === "Embeddings");
|
||||
const taskTaggingStage = task.stages.find((s) => s.label === "Tags");
|
||||
const taskScanningStage = task.stages.find((s) => s.label === "Scanning");
|
||||
const taskBarProgress = taskEmbeddingStage?.progress ?? taskTaggingStage?.progress ?? taskScanningStage?.progress ?? null;
|
||||
const taskDuplicateStage = task.id === -1 ? task.stages[0] : null;
|
||||
const taskBarProgress = taskEmbeddingStage?.progress ?? taskTaggingStage?.progress ?? taskScanningStage?.progress ?? taskDuplicateStage?.progress ?? null;
|
||||
const taskHasFailed = (task.hasFailedEmbeddings || task.hasFailedTagging || task.hasFailedCaptions) && task.pendingMediaWork === 0;
|
||||
|
||||
return (
|
||||
|
||||
@@ -117,6 +117,7 @@ export function DuplicateFinder() {
|
||||
const duplicateScanning = useGalleryStore((state) => state.duplicateScanning);
|
||||
const duplicateScanProgress = useGalleryStore((state) => state.duplicateScanProgress);
|
||||
const duplicateScanError = useGalleryStore((state) => state.duplicateScanError);
|
||||
const duplicateScanWarning = useGalleryStore((state) => state.duplicateScanWarning);
|
||||
const duplicateSelectedIds = useGalleryStore((state) => state.duplicateSelectedIds);
|
||||
const duplicateLastScanned = useGalleryStore((state) => state.duplicateLastScanned);
|
||||
const selectedFolderId = useGalleryStore((state) => state.selectedFolderId);
|
||||
@@ -152,8 +153,15 @@ export function DuplicateFinder() {
|
||||
|
||||
const progressPercent =
|
||||
duplicateScanProgress && duplicateScanProgress.total > 0
|
||||
? Math.round((duplicateScanProgress.scanned / duplicateScanProgress.total) * 100)
|
||||
? Math.round((duplicateScanProgress.processed / duplicateScanProgress.total) * 100)
|
||||
: 0;
|
||||
const progressLabel = duplicateScanProgress
|
||||
? duplicateScanProgress.phase === "checking"
|
||||
? "Checking file sizes"
|
||||
: duplicateScanProgress.phase === "hashing"
|
||||
? "Hashing duplicate candidates"
|
||||
: "Confirming exact matches"
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="flex min-h-0 flex-1 flex-col overflow-hidden bg-[#07080f]">
|
||||
@@ -165,7 +173,7 @@ export function DuplicateFinder() {
|
||||
<p className="mt-0.5 text-[11px] text-white/30">
|
||||
{duplicateScanning
|
||||
? duplicateScanProgress
|
||||
? `Scanning… ${duplicateScanProgress.scanned.toLocaleString()} / ${duplicateScanProgress.total.toLocaleString()}`
|
||||
? `${progressLabel}… ${duplicateScanProgress.processed.toLocaleString()} / ${duplicateScanProgress.total.toLocaleString()}${duplicateScanProgress.skipped > 0 ? ` · ${duplicateScanProgress.skipped.toLocaleString()} skipped` : ""}`
|
||||
: "Starting scan…"
|
||||
: hasResults
|
||||
? `${duplicateGroups.length} group${duplicateGroups.length === 1 ? "" : "s"} · ${formatBytes(totalWasted)} reclaimable`
|
||||
@@ -232,6 +240,9 @@ export function DuplicateFinder() {
|
||||
{duplicateScanError ? (
|
||||
<p className="mt-2 text-[11px] text-red-400/80">{duplicateScanError}</p>
|
||||
) : null}
|
||||
{duplicateScanWarning ? (
|
||||
<p className="mt-2 text-[11px] text-amber-300/70">{duplicateScanWarning}</p>
|
||||
) : null}
|
||||
{deleteResult ? (
|
||||
<p className="mt-2 text-[11px] text-white/40">{deleteResult}</p>
|
||||
) : null}
|
||||
@@ -241,7 +252,7 @@ export function DuplicateFinder() {
|
||||
{duplicateScanning && !hasResults ? (
|
||||
<div className="flex flex-1 items-center justify-center gap-3 text-white/25">
|
||||
<div className="h-5 w-5 animate-spin rounded-full border-2 border-white/15 border-t-white/50" />
|
||||
<span className="text-sm">Hashing files…</span>
|
||||
<span className="text-sm">{progressLabel ? `${progressLabel}…` : "Preparing scan…"}</span>
|
||||
</div>
|
||||
) : !hasScanned ? (
|
||||
<div className="flex flex-1 items-center justify-center px-8">
|
||||
|
||||
Reference in New Issue
Block a user