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.
This commit is contained in:
Jiho Andrew Lee 2026-08-13 14:51:57 +09:00
commit 0e1dde287d
9 changed files with 32 additions and 14 deletions

5
.gitignore vendored
View file

@ -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/

View file

@ -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")

View file

@ -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))

View file

@ -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

View file

@ -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())

View file

@ -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"

View file

@ -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,
)
}

View file

@ -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"
})

View file

@ -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}}),