From e92a90f5a9c97dbd9b33ddeef8c883561622f7cd Mon Sep 17 00:00:00 2001 From: Deluan Date: Tue, 1 Sep 2026 18:09:39 -0400 Subject: [PATCH] fix(artwork): partition the absent states instead of nesting them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ABSENT with FAILED beside it read as two buckets when the second was inside the first, and "OF WHICH FAILED" fixed the ambiguity by admitting it rather than removing it. The two columns are now disjoint and sum to the absent total: NO IMAGE was answered, FAILED gave up. The struct holds the same split the table prints, so the subtraction happens once where the counts are collected. The collectStatus fixture had two absent artists and no failure among them, which made noImage and the absent total identical and left the subtraction unverified — dropping it broke nothing. One of the two now carries a last_failure. --- cmd/artwork.go | 18 +++++++++--------- cmd/artwork_test.go | 15 +++++++++------ 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/cmd/artwork.go b/cmd/artwork.go index 13734c8f0..6d12037ca 100644 --- a/cmd/artwork.go +++ b/cmd/artwork.go @@ -147,11 +147,11 @@ type sourceCount struct { count int64 } +// absentCount partitions a kind's absent states: noImage was answered, failed gave up. type absentCount struct { - kind model.Kind - count int64 - // afterFailure is the subset that gave up rather than being told "no image". - afterFailure int64 + kind model.Kind + noImage int64 + failed int64 } type statusReport struct { @@ -199,7 +199,7 @@ func collectStatus(ctx context.Context, ds model.DataStore) (statusReport, error if err != nil { return rep, fmt.Errorf("counting failed %s artwork: %w", k, err) } - rep.absent = append(rep.absent, absentCount{kind: k, count: n, afterFailure: failed}) + rep.absent = append(rep.absent, absentCount{kind: k, noImage: n - failed, failed: failed}) } } } @@ -229,12 +229,12 @@ func formatStatus(rep statusReport) string { } fmt.Fprintln(w, "\nAbsent (resolved, no image found)") - fmt.Fprintln(w, " KIND\tABSENT\tOF WHICH FAILED") + fmt.Fprintln(w, " KIND\tNO IMAGE\tFAILED") for _, a := range rep.absent { - fmt.Fprintf(w, " %s\t%d\t%d\n", a.kind, a.count, a.afterFailure) + fmt.Fprintf(w, " %s\t%d\t%d\n", a.kind, a.noImage, a.failed) } - fmt.Fprintln(w, " (nothing retries these; 'artwork reprocess --source absent' retries all of them)") - fmt.Fprintln(w, " (failed = gave up rather than being answered, so the subset most likely to resolve;\n"+ + fmt.Fprintln(w, " (nothing retries these; 'artwork reprocess --source absent' retries both columns)") + fmt.Fprintln(w, " (failed = gave up rather than being answered, so the ones most likely to resolve;\n"+ " 'artwork reprocess --source failed' retries just those)") fmt.Fprintln(w, "\nConfig") diff --git a/cmd/artwork_test.go b/cmd/artwork_test.go index 1caf4d9e8..f2ac45442 100644 --- a/cmd/artwork_test.go +++ b/cmd/artwork_test.go @@ -816,7 +816,9 @@ var _ = Describe("collectStatus", func() { } put(model.KindArtistArtwork, "ar-1", "external:deezer", "h1", time.Now()) put(model.KindArtistArtwork, "ar-2", "", "", time.Now().Add(-24*time.Hour)) - put(model.KindArtistArtwork, "ar-3", "", "", time.Now()) + // ar-3 is absent because it gave up, so the two absent artists split across the columns. + Expect(art.PutItemArtwork(&model.ItemArtwork{ItemKind: "ar", ItemID: "ar-3", + ImageType: model.ImageTypePrimary, LastFailure: "[]", AttemptedAt: time.Now()})).To(Succeed()) put(model.KindAlbumArtwork, "al-1", "folder", "h2", time.Now()) Expect(queue.Enqueue(model.ArtworkQueueItem{ItemKind: "ar", ItemID: "ar-9", ImageType: model.ImageTypePrimary, Priority: model.ArtworkPriorityBackfill})).To(Succeed()) @@ -833,7 +835,8 @@ var _ = Describe("collectStatus", func() { sourceCount{kind: model.KindArtistArtwork, source: "", count: 2}, sourceCount{kind: model.KindAlbumArtwork, source: "folder", count: 1}, )) - Expect(rep.absent).To(ContainElement(absentCount{kind: model.KindArtistArtwork, count: 2})) + Expect(rep.absent).To(ContainElement(absentCount{kind: model.KindArtistArtwork, noImage: 1, failed: 1}), + "two absent artists, one answered and one that gave up") }) It("compares the stored fingerprint against the current one", func() { @@ -867,7 +870,7 @@ var _ = Describe("formatStatus", func() { {kind: model.KindArtistArtwork, source: "", count: 2}, }, absent: []absentCount{ - {kind: model.KindArtistArtwork, count: 2, afterFailure: 1}, + {kind: model.KindArtistArtwork, noImage: 1, failed: 1}, }, inputs: []artwork.FingerprintInput{{Name: "Agents", Value: "deezer,lastfm"}}, stored: "abc123", @@ -900,10 +903,10 @@ var _ = Describe("formatStatus", func() { Expect(sources).To(MatchRegexp(`artist\s+absent\s+2`)) }) - It("presents the failed count as a subset of the absent total, not a second bucket", func() { + It("partitions the absent states into answered and gave up", func() { out := block(formatStatus(rep), "Absent (resolved, no image found)") - Expect(out).To(ContainSubstring("OF WHICH FAILED")) - Expect(out).To(MatchRegexp(`artist\s+2\s+1`), "2 absent, of which 1 failed") + Expect(out).To(ContainSubstring("NO IMAGE")) + Expect(out).To(MatchRegexp(`artist\s+1\s+1`), "1 answered plus 1 failed, summing to 2 absent") Expect(formatStatus(rep)).To(ContainSubstring("artwork reprocess --source failed")) })