From 0e1dde287d73f36c1d888ffa15b1e667adad0bb0 Mon Sep 17 00:00:00 2001 From: Jiho Andrew Lee Date: Thu, 13 Aug 2026 14:51:57 +0900 Subject: [PATCH] fix(podcast): resolve post-rebase breakage after rebasing onto latest master Rebasing onto master's Podcasting-branch-unrelated changes surfaced several integration gaps the merge conflicts didn't catch: - conf.Server.DataFolder is now a Dir type, not a string; podcasts.go and its tests needed .String()/conf.NewDir() at each call site. - subsonic.New() gained the podcasts.Podcasts parameter; several e2e/unit test call sites elsewhere in the tree were still passing the old arg count. - The uniform-canonical-ids migration (dated after our podcast migrations, so it runs against a schema that already has the podcast tables) didn't know about the new podcast_* id columns, leaving them unrewritten while everything else (including podcast_episode.stream_id's matching media_file.id) got canonicalized. - opensubsonic_test.go's expected extension count was miscounted during conflict resolution (9 Podcasting 2.0 extensions, not 7). - .gitignore's unanchored `podcasts/` entry from an earlier commit accidentally matched core/podcasts/ (source) in addition to the downloaded-episode directory; anchored both to their actual paths. --- .gitignore | 5 +++-- core/podcasts/podcasts.go | 6 +++--- core/podcasts/podcasts_test.go | 6 +++--- db/migrations/20260720015443_uniform_canonical_ids.go | 8 ++++++++ db/migrations/uniform_canonical_ids_test.go | 8 ++++++++ server/subsonic/e2e/e2e_suite_test.go | 1 + server/subsonic/e2e/subsonic_artwork_test.go | 2 +- server/subsonic/lyrics_test.go | 2 +- server/subsonic/opensubsonic_test.go | 8 ++++---- 9 files changed, 32 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 1e4592b26..d0a5a17fa 100644 --- a/.gitignore +++ b/.gitignore @@ -49,5 +49,6 @@ zz_*_test.go /plugins/testdata/.wazero-cache/ /plugins/testdata/*.stage/ -# Downloaded podcast episode audio files should not be tracked -podcasts/ +# Downloaded podcast episode audio files should not be tracked (DataFolder defaults to ".") +/podcasts/ +/ui/podcasts/ diff --git a/core/podcasts/podcasts.go b/core/podcasts/podcasts.go index 9f1022517..36340c018 100644 --- a/core/podcasts/podcasts.go +++ b/core/podcasts/podcasts.go @@ -56,7 +56,7 @@ func (s *podcastService) podcastLibraryID(ctx context.Context) (int, error) { } lib := &model.Library{ Name: podcastLibraryName, - Path: conf.Server.DataFolder, + Path: conf.Server.DataFolder.String(), } if err := s.ds.Library(ctx).Put(lib); err != nil { return 0, err @@ -296,7 +296,7 @@ func (s *podcastService) doDownload(ctx context.Context, ep *model.PodcastEpisod if suffix == "" { suffix = "mp3" } - dir := filepath.Join(conf.Server.DataFolder, "podcasts", ep.ChannelID) + dir := filepath.Join(conf.Server.DataFolder.String(), "podcasts", ep.ChannelID) if err := os.MkdirAll(dir, 0755); err != nil { s.setEpisodeError(ctx, ep, err) return @@ -345,7 +345,7 @@ func (s *podcastService) doDownload(ctx context.Context, ep *model.PodcastEpisod if libErr != nil { log.Warn(ctx, "Failed to get podcast library, streaming may not work", "episode", ep.ID, libErr) } else { - relPath := strings.TrimPrefix(dest, conf.Server.DataFolder+string(filepath.Separator)) + relPath := strings.TrimPrefix(dest, conf.Server.DataFolder.String()+string(filepath.Separator)) now := time.Now() tags := model.Tags{} tags.Add("genre", "Podcast") diff --git a/core/podcasts/podcasts_test.go b/core/podcasts/podcasts_test.go index af0dc9957..8672880b1 100644 --- a/core/podcasts/podcasts_test.go +++ b/core/podcasts/podcasts_test.go @@ -49,7 +49,7 @@ var _ = Describe("PodcastService", func() { })) DeferCleanup(mockServer.Close) - conf.Server.DataFolder = GinkgoT().TempDir() + conf.Server.DataFolder = conf.NewDir(GinkgoT().TempDir()) svc = podcasts.NewPodcastService(ctx, ds, nil, nil) }) @@ -145,7 +145,7 @@ var _ = Describe("PodcastService", func() { It("creates the audio file at the expected path", func() { _ = svc.DownloadEpisode(ctx, "ep-1") - expectedPath := filepath.Join(conf.Server.DataFolder, "podcasts", "ch-1", "ep-1.mp3") + expectedPath := filepath.Join(conf.Server.DataFolder.String(), "podcasts", "ch-1", "ep-1.mp3") Eventually(func() bool { _, err := os.Stat(expectedPath) return err == nil @@ -161,7 +161,7 @@ var _ = Describe("PodcastService", func() { It("records the file path after download", func() { _ = svc.DownloadEpisode(ctx, "ep-1") - expectedPath := filepath.Join(conf.Server.DataFolder, "podcasts", "ch-1", "ep-1.mp3") + expectedPath := filepath.Join(conf.Server.DataFolder.String(), "podcasts", "ch-1", "ep-1.mp3") Eventually(func() string { return episodeRepo.Data["ep-1"].Path }, "3s").Should(Equal(expectedPath)) diff --git a/db/migrations/20260720015443_uniform_canonical_ids.go b/db/migrations/20260720015443_uniform_canonical_ids.go index 346f22050..c488ec8d8 100644 --- a/db/migrations/20260720015443_uniform_canonical_ids.go +++ b/db/migrations/20260720015443_uniform_canonical_ids.go @@ -77,6 +77,14 @@ var idColumns = []struct{ table, col string }{ {"media_file_artists", "media_file_id"}, {"media_file_artists", "artist_id"}, {"album_artists", "album_id"}, {"album_artists", "artist_id"}, {"library_tag", "tag_id"}, + {"podcast_channel", "id"}, + {"podcast_episode", "id"}, {"podcast_episode", "channel_id"}, {"podcast_episode", "stream_id"}, + {"podcast_transcript", "id"}, {"podcast_transcript", "episode_id"}, + {"podcast_person", "id"}, {"podcast_person", "channel_id"}, {"podcast_person", "episode_id"}, + {"podcast_podroll", "id"}, {"podcast_podroll", "channel_id"}, + {"podcast_live_item", "id"}, {"podcast_live_item", "channel_id"}, + {"podcast_funding", "id"}, {"podcast_funding", "channel_id"}, + {"podcast_image", "id"}, {"podcast_image", "channel_id"}, {"podcast_image", "episode_id"}, } // embeddedIDColumns holds ids nested inside a larger value; the id-columns guard checks this diff --git a/db/migrations/uniform_canonical_ids_test.go b/db/migrations/uniform_canonical_ids_test.go index ced75cb7e..05d796c83 100644 --- a/db/migrations/uniform_canonical_ids_test.go +++ b/db/migrations/uniform_canonical_ids_test.go @@ -65,6 +65,14 @@ var _ = Describe("upUniformCanonicalIds", func() { CREATE TABLE library_tag (tag_id text, library_id integer); CREATE TABLE plugin (id text, users text); CREATE TABLE property (id text primary key, value text); + CREATE TABLE podcast_channel (id text); + CREATE TABLE podcast_episode (id text, channel_id text, stream_id text); + CREATE TABLE podcast_transcript (id text, episode_id text); + CREATE TABLE podcast_person (id text, channel_id text, episode_id text); + CREATE TABLE podcast_podroll (id text, channel_id text); + CREATE TABLE podcast_live_item (id text, channel_id text); + CREATE TABLE podcast_funding (id text, channel_id text); + CREATE TABLE podcast_image (id text, channel_id text, episode_id text); `) Expect(err).ToNot(HaveOccurred()) diff --git a/server/subsonic/e2e/e2e_suite_test.go b/server/subsonic/e2e/e2e_suite_test.go index 9879fa181..11f3236c0 100644 --- a/server/subsonic/e2e/e2e_suite_test.go +++ b/server/subsonic/e2e/e2e_suite_test.go @@ -20,6 +20,7 @@ import ( "github.com/navidrome/navidrome/core/artwork" "github.com/navidrome/navidrome/core/auth" "github.com/navidrome/navidrome/core/external" + "github.com/navidrome/navidrome/core/ffmpeg" "github.com/navidrome/navidrome/core/lyrics" "github.com/navidrome/navidrome/core/metrics" "github.com/navidrome/navidrome/core/playback" diff --git a/server/subsonic/e2e/subsonic_artwork_test.go b/server/subsonic/e2e/subsonic_artwork_test.go index 9324ea9e3..a4f72cd9f 100644 --- a/server/subsonic/e2e/subsonic_artwork_test.go +++ b/server/subsonic/e2e/subsonic_artwork_test.go @@ -257,7 +257,7 @@ func buildArtworkRouter(art artwork.Artwork) *subsonic.Router { ds, art, streamerSpy, noopArchiver{}, core.NewPlayers(ds), noopProvider{}, s, events.NoopBroker(), playlists.NewPlaylists(ds, artwork.NewUploader(ds)), scrobbler.NewPlayTracker(ds, events.NoopBroker(), nil), core.NewShare(ds), - playback.PlaybackServer(nil), metrics.NewNoopInstance(), lyrics.NewLyrics(ds, nil), decider, nil, + playback.PlaybackServer(nil), metrics.NewNoopInstance(), lyrics.NewLyrics(ds, nil), decider, nil, nil, ) } diff --git a/server/subsonic/lyrics_test.go b/server/subsonic/lyrics_test.go index 8713b7a3b..479a9c748 100644 --- a/server/subsonic/lyrics_test.go +++ b/server/subsonic/lyrics_test.go @@ -23,7 +23,7 @@ var _ = Describe("GetLyricsBySongId", func() { ds = &tests.MockDataStore{ MockedMediaFile: mockRepo, } - router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, lyrics.NewLyrics(ds, nil), nil, nil) + router = New(ds, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, lyrics.NewLyrics(ds, nil), nil, nil, nil) DeferCleanup(configtest.SetupConfig()) conf.Server.LyricsPriority = "embedded,.lrc" }) diff --git a/server/subsonic/opensubsonic_test.go b/server/subsonic/opensubsonic_test.go index f6ea32d59..0c42c5054 100644 --- a/server/subsonic/opensubsonic_test.go +++ b/server/subsonic/opensubsonic_test.go @@ -44,7 +44,7 @@ var _ = Describe("GetOpenSubsonicExtensions", func() { router = subsonic.New(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil) }) - It("should return the base 15 OpenSubsonicExtensions without sonicSimilarity", func() { + It("should return the base 17 OpenSubsonicExtensions without sonicSimilarity", func() { router.ServeHTTP(w, r) // Make sure the endpoint is public, by not passing any authentication @@ -55,7 +55,7 @@ var _ = Describe("GetOpenSubsonicExtensions", func() { err := json.Unmarshal(w.Body.Bytes(), &response) Expect(err).NotTo(HaveOccurred()) Expect(*response.Subsonic.OpenSubsonicExtensions).To(SatisfyAll( - HaveLen(15), + HaveLen(17), ContainElement(responses.OpenSubsonicExtension{Name: "transcodeOffset", Versions: []int32{1}}), ContainElement(responses.OpenSubsonicExtension{Name: "formPost", Versions: []int32{1}}), ContainElement(responses.OpenSubsonicExtension{Name: "songLyrics", Versions: []int32{1, 2}}), @@ -86,7 +86,7 @@ var _ = Describe("GetOpenSubsonicExtensions", func() { router = subsonic.New(nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, sonicService) }) - It("should return 16 extensions including all podcast 2.0 and sonicSimilarity", func() { + It("should return 18 extensions including all podcast 2.0 and sonicSimilarity", func() { router.ServeHTTP(w, r) Expect(w.Code).To(Equal(http.StatusOK)) @@ -96,7 +96,7 @@ var _ = Describe("GetOpenSubsonicExtensions", func() { err := json.Unmarshal(w.Body.Bytes(), &response) Expect(err).NotTo(HaveOccurred()) Expect(*response.Subsonic.OpenSubsonicExtensions).To(SatisfyAll( - HaveLen(16), + HaveLen(18), ContainElement(responses.OpenSubsonicExtension{Name: "transcodeOffset", Versions: []int32{1}}), ContainElement(responses.OpenSubsonicExtension{Name: "formPost", Versions: []int32{1}}), ContainElement(responses.OpenSubsonicExtension{Name: "songLyrics", Versions: []int32{1, 2}}),