From b83687116163e7a5cb5357b325b714aecdd0a02a Mon Sep 17 00:00:00 2001 From: Deluan Date: Wed, 19 Aug 2020 12:15:41 -0400 Subject: [PATCH] Handle CR, LF and CRLF line endings when importing Playlists --- scanner/playlist_sync.go | 29 ++++++++++++++++- scanner/playlist_sync_test.go | 47 +++++++++++++++++++++++++++ tests/fixtures/playlist.m3u | 0 tests/fixtures/playlists/cr-ended.m3u | 1 + tests/fixtures/playlists/lf-ended.m3u | 3 ++ 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 scanner/playlist_sync_test.go delete mode 100644 tests/fixtures/playlist.m3u create mode 100644 tests/fixtures/playlists/cr-ended.m3u create mode 100644 tests/fixtures/playlists/lf-ended.m3u diff --git a/scanner/playlist_sync.go b/scanner/playlist_sync.go index ab689ac2e..b50932f71 100644 --- a/scanner/playlist_sync.go +++ b/scanner/playlist_sync.go @@ -2,6 +2,7 @@ package scanner import ( "bufio" + "bytes" "context" "fmt" "io/ioutil" @@ -73,7 +74,9 @@ func (s *playlistSync) parsePlaylist(ctx context.Context, playlistFile string, b UpdatedAt: info.ModTime(), } + mediaFileRepository := s.ds.MediaFile(ctx) scanner := bufio.NewScanner(file) + scanner.Split(scanLines) for scanner.Scan() { path := scanner.Text() // Skip extended info @@ -83,7 +86,7 @@ func (s *playlistSync) parsePlaylist(ctx context.Context, playlistFile string, b if !filepath.IsAbs(path) { path = filepath.Join(baseDir, path) } - mf, err := s.ds.MediaFile(ctx).FindByPath(path) + mf, err := mediaFileRepository.FindByPath(path) if err != nil { log.Warn(ctx, "Path in playlist not found", "playlist", playlistFile, "path", path, err) continue @@ -118,3 +121,27 @@ func (s *playlistSync) updatePlaylist(ctx context.Context, newPls *model.Playlis } return s.ds.Playlist(ctx).Put(newPls) } + +// From https://stackoverflow.com/a/41433698 +func scanLines(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + if i := bytes.IndexAny(data, "\r\n"); i >= 0 { + if data[i] == '\n' { + // We have a line terminated by single newline. + return i + 1, data[0:i], nil + } + advance = i + 1 + if len(data) > i+1 && data[i+1] == '\n' { + advance += 1 + } + return advance, data[0:i], nil + } + // If we're at EOF, we have a final, non-terminated line. Return it. + if atEOF { + return len(data), data, nil + } + // Request more data. + return 0, nil, nil +} diff --git a/scanner/playlist_sync_test.go b/scanner/playlist_sync_test.go new file mode 100644 index 000000000..1f19cbf88 --- /dev/null +++ b/scanner/playlist_sync_test.go @@ -0,0 +1,47 @@ +package scanner + +import ( + "context" + + "github.com/deluan/navidrome/model" + "github.com/deluan/navidrome/persistence" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("playlistSync", func() { + Describe("parsePlaylist", func() { + var ds model.DataStore + var ps *playlistSync + ctx := context.TODO() + BeforeEach(func() { + ds = &persistence.MockDataStore{ + MockedMediaFile: &mockedMediaFile{}, + } + ps = newPlaylistSync(ds) + }) + + It("parses well-formed playlists", func() { + pls, err := ps.parsePlaylist(ctx, "lf-ended.m3u", "tests/fixtures/playlists") + Expect(err).To(BeNil()) + Expect(pls.Tracks).To(HaveLen(2)) + }) + + It("parses playlists using CR ending (old Mac format)", func() { + pls, err := ps.parsePlaylist(ctx, "cr-ended.m3u", "tests/fixtures/playlists") + Expect(err).To(BeNil()) + Expect(pls.Tracks).To(HaveLen(2)) + }) + }) +}) + +type mockedMediaFile struct { + model.MediaFileRepository +} + +func (r *mockedMediaFile) FindByPath(s string) (*model.MediaFile, error) { + return &model.MediaFile{ + ID: "123", + Path: s, + }, nil +} diff --git a/tests/fixtures/playlist.m3u b/tests/fixtures/playlist.m3u deleted file mode 100644 index e69de29bb..000000000 diff --git a/tests/fixtures/playlists/cr-ended.m3u b/tests/fixtures/playlists/cr-ended.m3u new file mode 100644 index 000000000..a9d4cb28c --- /dev/null +++ b/tests/fixtures/playlists/cr-ended.m3u @@ -0,0 +1 @@ +# This is a comment abc.mp3 def.mp3 \ No newline at end of file diff --git a/tests/fixtures/playlists/lf-ended.m3u b/tests/fixtures/playlists/lf-ended.m3u new file mode 100644 index 000000000..87cc604d5 --- /dev/null +++ b/tests/fixtures/playlists/lf-ended.m3u @@ -0,0 +1,3 @@ +# This is a comment +abc.mp3 +def.mp3 \ No newline at end of file