From ab29bc8c8e258a6a4165742508617e60a4e7516b Mon Sep 17 00:00:00 2001 From: rxwatcher Date: Mon, 20 Jul 2026 08:21:14 +0200 Subject: [PATCH] fix(ebooks): skip remote lookup for complete local metadata --- internal/ebooks/enrichment.go | 33 ++++++++++++++++++++++++++ internal/ebooks/enrichment_test.go | 37 ++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/internal/ebooks/enrichment.go b/internal/ebooks/enrichment.go index 46a9f569..cde281c4 100644 --- a/internal/ebooks/enrichment.go +++ b/internal/ebooks/enrichment.go @@ -713,6 +713,18 @@ func (e *Enricher) enrichClaimedItem(ctx context.Context, item enrichmentItemRow // provider ever saw it. return EnrichmentOutcomeSkipped, nil } + if ebookHasCompleteLocalMetadata(item) { + slog.DebugContext(ctx, "ebook enrichment: local metadata is complete", + "component", "ebooks", + "content_id", item.ContentID, + ) + return EnrichmentOutcomeSuccess, nil + } + slog.DebugContext(ctx, "ebook enrichment: remote metadata required", + "component", "ebooks", + "content_id", item.ContentID, + "missing_fields", ebookMissingMetadataFields(item), + ) providers, err := metadata.ResolveChain(ctx, item.FolderID, ebookContentType(), e.chainRepo, e.resolver) if err != nil { @@ -721,6 +733,27 @@ func (e *Enricher) enrichClaimedItem(ctx context.Context, item enrichmentItemRow return e.enrichWithProvidersOutcome(ctx, item, providers) } +func ebookHasCompleteLocalMetadata(item enrichmentItemRow) bool { + return len(ebookMissingMetadataFields(item)) == 0 +} + +func ebookMissingMetadataFields(item enrichmentItemRow) []string { + missing := make([]string, 0, 4) + if strings.TrimSpace(item.Title) == "" { + missing = append(missing, "title") + } + if strings.TrimSpace(item.Author) == "" { + missing = append(missing, "author") + } + if strings.TrimSpace(item.Overview) == "" { + missing = append(missing, "description") + } + if strings.TrimSpace(item.PosterPath) == "" { + missing = append(missing, "cover") + } + return missing +} + // enrichWithProviders runs the provider chain for one claimed item. Outcomes: // - metadata obtained: persist it and stamp last_refreshed (nil error); // - providers answered but nothing matched: stamp last_refreshed so the diff --git a/internal/ebooks/enrichment_test.go b/internal/ebooks/enrichment_test.go index 28492235..1809e92e 100644 --- a/internal/ebooks/enrichment_test.go +++ b/internal/ebooks/enrichment_test.go @@ -1318,6 +1318,43 @@ func TestEnricherRunLimitedEnforcesClaimLimitBelowWorkerCount(t *testing.T) { } } +func TestEbookHasCompleteLocalMetadata(t *testing.T) { + complete := enrichmentItemRow{ + Title: "A Book", + Author: "An Author", + Overview: "A useful description.", + PosterPath: "/library/A Book/cover.jpg", + } + if !ebookHasCompleteLocalMetadata(complete) { + t.Fatal("complete embedded metadata was not recognized") + } + if missing := ebookMissingMetadataFields(complete); len(missing) != 0 { + t.Fatalf("complete embedded metadata missing fields = %v", missing) + } + + for name, tc := range map[string]struct { + field string + mutate func(*enrichmentItemRow) + }{ + "title": {field: "title", mutate: func(item *enrichmentItemRow) { item.Title = "" }}, + "author": {field: "author", mutate: func(item *enrichmentItemRow) { item.Author = "" }}, + "description": {field: "description", mutate: func(item *enrichmentItemRow) { item.Overview = "" }}, + "cover": {field: "cover", mutate: func(item *enrichmentItemRow) { item.PosterPath = "" }}, + } { + t.Run(name, func(t *testing.T) { + item := complete + tc.mutate(&item) + if ebookHasCompleteLocalMetadata(item) { + t.Fatalf("metadata missing %s was treated as complete", name) + } + missing := ebookMissingMetadataFields(item) + if len(missing) != 1 || missing[0] != tc.field { + t.Fatalf("missing fields = %v, want [%s]", missing, tc.field) + } + }) + } +} + func (f *fakeEnrichmentQueue) recordTransition(job EnrichmentJob) { if f.transitionedJobs == nil { f.transitionedJobs = make(map[string]EnrichmentJob)