diff --git a/cmd/artwork.go b/cmd/artwork.go index 7be9bf7d2..e73ed46ed 100644 --- a/cmd/artwork.go +++ b/cmd/artwork.go @@ -71,15 +71,27 @@ func walksPriorityChain(kind model.Kind) bool { return kind == model.KindArtistArtwork || kind == model.KindAlbumArtwork } -// explainResult states the verdict of the walk. Offline runs skip the external tier, so a -// local miss is unknown rather than unresolvable. +// explainResult states the verdict of the walk. An external tier that was skipped or that failed +// leaves the outcome unknown: nothing observed that the item has no artwork. func explainResult(source string, steps []artwork.TraceStep) string { if source != "" { + for _, s := range steps { + if s.Outcome == "hit" { + break + } + if s.Outcome == "would-try" { + return "resolved from " + source + + " (offline: a higher-priority external candidate was not tried; re-run with --live)" + } + } return "resolved from " + source } for _, s := range steps { - if s.Outcome == "would-try" { + switch { + case s.Outcome == "would-try": return "indeterminate (external agents not called; re-run with --live)" + case s.Outcome == "error" && strings.HasPrefix(s.Candidate, "external:"): + return "indeterminate (an external lookup failed; the item may resolve on a later attempt)" } } return "not resolved" diff --git a/cmd/artwork_test.go b/cmd/artwork_test.go index 1da8df3f9..a299c5222 100644 --- a/cmd/artwork_test.go +++ b/cmd/artwork_test.go @@ -44,6 +44,34 @@ var _ = Describe("explainResult", func() { Expect(explainResult("", steps)).To(Equal("not resolved")) }) + It("reports indeterminate when an external lookup failed transiently", func() { + steps := []artwork.TraceStep{ + {Candidate: "artist.*", Outcome: "miss"}, + {Candidate: "external:deezer", Outcome: "error", Detail: "context deadline exceeded"}, + } + Expect(explainResult("", steps)).To(ContainSubstring("indeterminate"), + "a failed network call is not evidence that the item has no artwork") + }) + + It("qualifies a win a skipped higher-priority external candidate could have taken", func() { + steps := []artwork.TraceStep{ + {Candidate: "external:deezer", Outcome: "would-try"}, + {Candidate: "artist.*", Outcome: "hit", Detail: "/music/artist.jpg"}, + } + res := explainResult("artist.*", steps) + Expect(res).To(ContainSubstring("resolved from artist.*")) + Expect(res).To(ContainSubstring("--live"), + "offline, the winner is only the winner because the external tier was skipped") + }) + + It("does not qualify a win that no skipped candidate outranked", func() { + steps := []artwork.TraceStep{ + {Candidate: "artist.*", Outcome: "hit"}, + {Candidate: "external:deezer", Outcome: "would-try"}, + } + Expect(explainResult("artist.*", steps)).To(Equal("resolved from artist.*")) + }) + It("reports indeterminate when external agents were never called", func() { steps := []artwork.TraceStep{ {Candidate: "artist.*", Outcome: "miss"},