diff --git a/app/cmd/collectPodcastData.go b/app/cmd/collectPodcastData.go index 546169b6..97aa7b5f 100644 --- a/app/cmd/collectPodcastData.go +++ b/app/cmd/collectPodcastData.go @@ -104,6 +104,13 @@ func cmdCollectPodcastData(cmd *cobra.Command, args []string) error { } log.Printf("Requesting 'Podcasts.GetByFeedID' data from podcast index for feed id %d ... successful", podcastInfo.PodcastIndexID) + // Once a podcast is removed from the PodcastIndex, there is nothing left to collect. + // We keep the data we collected in the past instead of failing the whole run. + if !p.Found { + log.Printf("PodcastIndex has no feed for id %d anymore. Keeping the existing data of %s", podcastInfo.PodcastIndexID, absJsonFilePath) + continue + } + // Set basic podcast data podcastInfo.EpisodeCount = p.Feed.EpisodeCount podcastInfo.ItunesID = p.Feed.ItunesID diff --git a/app/podcastindex/podcasts.go b/app/podcastindex/podcasts.go index eb3ceb79..6e515f4b 100644 --- a/app/podcastindex/podcasts.go +++ b/app/podcastindex/podcasts.go @@ -2,6 +2,7 @@ package podcastindex import ( "context" + "encoding/json" "fmt" "net/http" ) @@ -13,7 +14,33 @@ import ( type PodcastsService service type Podcast struct { - Feed PodcastFeed `json:"feed,omitempty"` + Feed PodcastFeed + + // Found reports whether the API returned a feed for the requested id. + // Once a feed is removed from the PodcastIndex, the API answers with + // an empty array ("feed": []) instead of a feed object. + Found bool +} + +func (p *Podcast) UnmarshalJSON(data []byte) error { + var raw struct { + Feed json.RawMessage `json:"feed,omitempty"` + } + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + + // "feed": [] means: No feeds match this id. + if len(raw.Feed) == 0 || raw.Feed[0] == '[' { + return nil + } + + if err := json.Unmarshal(raw.Feed, &p.Feed); err != nil { + return err + } + p.Found = true + + return nil } type PodcastFeed struct { diff --git a/app/podcastindex/podcasts_test.go b/app/podcastindex/podcasts_test.go new file mode 100644 index 00000000..9b2d3de9 --- /dev/null +++ b/app/podcastindex/podcasts_test.go @@ -0,0 +1,50 @@ +package podcastindex + +import ( + "encoding/json" + "testing" +) + +// The PodcastIndex API returns an empty array as feed once a feed id is unknown. +// Decoding this must not fail, otherwise a single removed podcast breaks the whole data collection. +func TestPodcastUnmarshalJSON_RemovedFeed(t *testing.T) { + body := `{"status":"true","query":{"id":"685245"},"feed":[],"description":"No feeds match this id."}` + + p := new(Podcast) + if err := json.Unmarshal([]byte(body), p); err != nil { + t.Fatalf("Unmarshal returned error: %v", err) + } + if p.Found { + t.Error("Found = true, want false") + } + if p.Feed.ID != 0 { + t.Errorf("Feed.ID = %d, want 0", p.Feed.ID) + } +} + +func TestPodcastUnmarshalJSON_ExistingFeed(t *testing.T) { + body := `{"status":"true","query":{"id":"685245"},"feed":{"id":685245,"title":"IT@DB","artwork":"https://example.com/cover.png","itunesId":1462447493,"episodeCount":94},"description":"Found matching feed."}` + + p := new(Podcast) + if err := json.Unmarshal([]byte(body), p); err != nil { + t.Fatalf("Unmarshal returned error: %v", err) + } + if !p.Found { + t.Error("Found = false, want true") + } + if p.Feed.ID != 685245 { + t.Errorf("Feed.ID = %d, want 685245", p.Feed.ID) + } + if p.Feed.Title != "IT@DB" { + t.Errorf("Feed.Title = %q, want %q", p.Feed.Title, "IT@DB") + } + if p.Feed.Artwork != "https://example.com/cover.png" { + t.Errorf("Feed.Artwork = %q, want %q", p.Feed.Artwork, "https://example.com/cover.png") + } + if p.Feed.ItunesID != 1462447493 { + t.Errorf("Feed.ItunesID = %d, want 1462447493", p.Feed.ItunesID) + } + if p.Feed.EpisodeCount != 94 { + t.Errorf("Feed.EpisodeCount = %d, want 94", p.Feed.EpisodeCount) + } +}