diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 3418d1dbf..d240f27c6 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -9,6 +9,20 @@ on: branches: - master jobs: + golangci-lint: + name: Lint Server + runs-on: ubuntu-latest + steps: + - name: Check out code into the Go module directory + uses: actions/checkout@v2 + + - name: Run golangci-lint + uses: actions-contrib/golangci-lint@v1 + with: + golangci_lint_version: v1.25.0 +# TODO Enable github actions output format: https://github.com/actions-contrib/golangci-lint/issues/11 +# args: run --out-format github-actions + go: name: Test Server on ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -64,6 +78,11 @@ jobs: cd ui npm ci + - name: npm check-formatting + run: | + cd ui + npm run check-formatting + - name: npm build run: | cd ui @@ -76,7 +95,7 @@ jobs: binaries: name: Binaries - needs: [js, go] + needs: [js, go, golangci-lint] runs-on: ubuntu-latest steps: - name: Checkout Code @@ -115,21 +134,27 @@ jobs: name: Docker images needs: [binaries] runs-on: ubuntu-latest + env: + DOCKER_IMAGE: ${{secrets.DOCKER_IMAGE}} steps: - name: Set up Docker Buildx id: buildx uses: crazy-max/ghaction-docker-buildx@v1 + if: ${{env.DOCKER_IMAGE}} != '' with: version: latest - uses: actions/checkout@v1 + if: ${{env.DOCKER_IMAGE}} != '' - uses: actions/download-artifact@v1 + if: ${{env.DOCKER_IMAGE}} != '' with: name: binaries path: dist - name: Build the Docker image and push + if: ${{env.DOCKER_IMAGE}} != '' env: DOCKER_IMAGE: ${{secrets.DOCKER_IMAGE}} DOCKER_PLATFORM: linux/amd64,linux/arm/v7,linux/arm64 diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 000000000..e99c18cdd --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,11 @@ +linters: + enable: + - goimports + - unconvert + - gosec + +issues: + exclude-rules: + - linters: + - gosec + text: "(G501|G401):" diff --git a/Makefile b/Makefile index c95444e7b..6832e4e98 100644 --- a/Makefile +++ b/Makefile @@ -35,15 +35,18 @@ testall: check_go_env test .PHONY: testall setup: - @which wire || (echo "Installing Wire" && GO111MODULE=off go get -u github.com/google/wire/cmd/wire) @which go-bindata || (echo "Installing BinData" && GO111MODULE=off go get -u github.com/go-bindata/go-bindata/...) @which reflex || (echo "Installing Reflex" && GO111MODULE=off go get -u github.com/cespare/reflex) @which goreman || (echo "Installing Goreman" && GO111MODULE=off go get -u github.com/mattn/goreman) + go mod download +.PHONY: setup + +setup-dev: setup + @which wire || (echo "Installing Wire" && GO111MODULE=off go get -u github.com/google/wire/cmd/wire) @which ginkgo || (echo "Installing Ginkgo" && GO111MODULE=off go get -u github.com/onsi/ginkgo/ginkgo) @which goose || (echo "Installing Goose" && GO111MODULE=off go get -u github.com/pressly/goose/cmd/goose) @which lefthook || (echo "Installing Lefthook" && GO111MODULE=off go get -u github.com/Arkweid/lefthook) @lefthook install - go mod download @(cd ./ui && npm ci) .PHONY: setup diff --git a/db/migration/20200325185135_add_album_artist_id.go b/db/migration/20200325185135_add_album_artist_id.go index 23d0cb7d2..213494bac 100644 --- a/db/migration/20200325185135_add_album_artist_id.go +++ b/db/migration/20200325185135_add_album_artist_id.go @@ -2,6 +2,7 @@ package migration import ( "database/sql" + "github.com/pressly/goose" ) diff --git a/db/migration/20200326090707_fix_album_artists_importing.go b/db/migration/20200326090707_fix_album_artists_importing.go index 05db74bbc..0608dbeef 100644 --- a/db/migration/20200326090707_fix_album_artists_importing.go +++ b/db/migration/20200326090707_fix_album_artists_importing.go @@ -2,6 +2,7 @@ package migration import ( "database/sql" + "github.com/pressly/goose" ) diff --git a/db/migration/20200327193744_add_year_range_to_album.go b/db/migration/20200327193744_add_year_range_to_album.go index 5757a6adc..5f201ece4 100644 --- a/db/migration/20200327193744_add_year_range_to_album.go +++ b/db/migration/20200327193744_add_year_range_to_album.go @@ -2,6 +2,7 @@ package migration import ( "database/sql" + "github.com/pressly/goose" ) diff --git a/db/migration/20200409002249_enable_search_by_tracks_artists.go b/db/migration/20200409002249_enable_search_by_tracks_artists.go index dacb1d012..7b67c6f2c 100644 --- a/db/migration/20200409002249_enable_search_by_tracks_artists.go +++ b/db/migration/20200409002249_enable_search_by_tracks_artists.go @@ -2,6 +2,7 @@ package migration import ( "database/sql" + "github.com/pressly/goose" ) diff --git a/db/migration/20200411164603_add_created_and_updated_fields_to_playlists.go b/db/migration/20200411164603_add_created_and_updated_fields_to_playlists.go index 19e4326f1..0ccfa08f3 100644 --- a/db/migration/20200411164603_add_created_and_updated_fields_to_playlists.go +++ b/db/migration/20200411164603_add_created_and_updated_fields_to_playlists.go @@ -2,6 +2,7 @@ package migration import ( "database/sql" + "github.com/pressly/goose" ) diff --git a/db/migration/20200418110522_reindex_to_fix_album_years.go b/db/migration/20200418110522_reindex_to_fix_album_years.go index c3428d604..f1c23d314 100644 --- a/db/migration/20200418110522_reindex_to_fix_album_years.go +++ b/db/migration/20200418110522_reindex_to_fix_album_years.go @@ -2,6 +2,7 @@ package migration import ( "database/sql" + "github.com/pressly/goose" ) diff --git a/db/migration/20200419222708_reindex_to_change_full_text_search.go b/db/migration/20200419222708_reindex_to_change_full_text_search.go index b92641be4..e713f6969 100644 --- a/db/migration/20200419222708_reindex_to_change_full_text_search.go +++ b/db/migration/20200419222708_reindex_to_change_full_text_search.go @@ -2,6 +2,7 @@ package migration import ( "database/sql" + "github.com/pressly/goose" ) diff --git a/db/migration/20200423204116_add_sort_fields.go b/db/migration/20200423204116_add_sort_fields.go index db5e9f2ce..086325980 100644 --- a/db/migration/20200423204116_add_sort_fields.go +++ b/db/migration/20200423204116_add_sort_fields.go @@ -2,6 +2,7 @@ package migration import ( "database/sql" + "github.com/pressly/goose" ) diff --git a/engine/auth/auth.go b/engine/auth/auth.go index 4c8db1a02..774d33445 100644 --- a/engine/auth/auth.go +++ b/engine/auth/auth.go @@ -1,6 +1,7 @@ package auth import ( + "context" "fmt" "sync" "time" @@ -22,7 +23,7 @@ var ( func InitTokenAuth(ds model.DataStore) { once.Do(func() { - secret, err := ds.Property(nil).DefaultGet(consts.JWTSecretKey, "not so secret") + secret, err := ds.Property(context.TODO()).DefaultGet(consts.JWTSecretKey, "not so secret") if err != nil { log.Error("No JWT secret found in DB. Setting a temp one, but please report this error", err) } diff --git a/engine/browser.go b/engine/browser.go index e85946834..a066883b5 100644 --- a/engine/browser.go +++ b/engine/browser.go @@ -80,10 +80,6 @@ func (b *browser) Artist(ctx context.Context, id string) (*DirectoryInfo, error) return nil, err } log.Debug(ctx, "Found Artist", "id", id, "name", a.Name) - var albumIds []string - for _, al := range albums { - albumIds = append(albumIds, al.ID) - } return b.buildArtistDir(a, albums), nil } @@ -93,11 +89,6 @@ func (b *browser) Album(ctx context.Context, id string) (*DirectoryInfo, error) return nil, err } log.Debug(ctx, "Found Album", "id", id, "name", al.Name) - var mfIds []string - for _, mf := range tracks { - mfIds = append(mfIds, mf.ID) - } - return b.buildAlbumDir(al, tracks), nil } diff --git a/engine/cover.go b/engine/cover.go index 9aa6c45ba..312f291e9 100644 --- a/engine/cover.go +++ b/engine/cover.go @@ -74,7 +74,9 @@ func (c *cover) Get(ctx context.Context, id string, size int, out io.Writer) err log.Error(ctx, "Error loading cover art", "path", path, "size", size, err) return } - io.Copy(w, reader) + if _, err := io.Copy(w, reader); err != nil { + log.Error(ctx, "Error saving covert art to cache", "path", path, "size", size, err) + } }() } else { log.Trace(ctx, "Loading image from cache", "path", path, "size", size, "lastUpdate", lastUpdate) diff --git a/engine/cover_test.go b/engine/cover_test.go index 213c974df..329bc6145 100644 --- a/engine/cover_test.go +++ b/engine/cover_test.go @@ -2,6 +2,7 @@ package engine import ( "bytes" + "context" "image" "github.com/deluan/navidrome/log" @@ -14,7 +15,7 @@ import ( var _ = Describe("Cover", func() { var cover Cover var ds model.DataStore - ctx := log.NewContext(nil) + ctx := log.NewContext(context.TODO()) BeforeEach(func() { ds = &persistence.MockDataStore{MockedTranscoding: &mockTranscodingRepository{}} diff --git a/engine/list_generator.go b/engine/list_generator.go index 9599097a4..fa300e868 100644 --- a/engine/list_generator.go +++ b/engine/list_generator.go @@ -106,18 +106,6 @@ type listGenerator struct { npRepo NowPlayingRepository } -func (g *listGenerator) query(ctx context.Context, qo model.QueryOptions) (Entries, error) { - albums, err := g.ds.Album(ctx).GetAll(qo) - if err != nil { - return nil, err - } - albumIds := make([]string, len(albums)) - for i, al := range albums { - albumIds[i] = al.ID - } - return FromAlbums(albums), err -} - func (g *listGenerator) GetSongs(ctx context.Context, offset, size int, filter ListFilter) (Entries, error) { qo := model.QueryOptions(filter) qo.Offset = offset @@ -170,16 +158,6 @@ func (g *listGenerator) GetAllStarred(ctx context.Context) (artists Entries, alb return nil, nil, nil, err } - var mfIds []string - for _, mf := range mfs { - mfIds = append(mfIds, mf.ID) - } - - var artistIds []string - for _, ar := range ars { - artistIds = append(artistIds, ar.ID) - } - artists = FromArtists(ars) albums = FromAlbums(als) mediaFiles = FromMediaFiles(mfs) @@ -200,7 +178,7 @@ func (g *listGenerator) GetNowPlaying(ctx context.Context) (Entries, error) { } entries[i] = FromMediaFile(mf) entries[i].UserName = np.Username - entries[i].MinutesAgo = int(time.Now().Sub(np.Start).Minutes()) + entries[i].MinutesAgo = int(time.Since(np.Start).Minutes()) entries[i].PlayerId = np.PlayerId entries[i].PlayerName = np.PlayerName diff --git a/engine/media_streamer_test.go b/engine/media_streamer_test.go index ac05fbc2b..0f0dbbb57 100644 --- a/engine/media_streamer_test.go +++ b/engine/media_streamer_test.go @@ -16,7 +16,7 @@ var _ = Describe("MediaStreamer", func() { var streamer MediaStreamer var ds model.DataStore ffmpeg := &fakeFFmpeg{Data: "fake data"} - ctx := log.NewContext(nil) + ctx := log.NewContext(context.TODO()) BeforeEach(func() { ds = &persistence.MockDataStore{MockedTranscoding: &mockTranscodingRepository{}} diff --git a/engine/nowplaying.go b/engine/nowplaying.go index 39a9da34a..643ad0290 100644 --- a/engine/nowplaying.go +++ b/engine/nowplaying.go @@ -110,7 +110,7 @@ func checkExpired(l *list.List, f func() *list.Element) *list.Element { return nil } start := e.Value.(*NowPlayingInfo).Start - if time.Now().Sub(start) < NowPlayingExpire { + if time.Since(start) < NowPlayingExpire { return e } l.Remove(e) diff --git a/engine/players_test.go b/engine/players_test.go index ec2b58677..720274035 100644 --- a/engine/players_test.go +++ b/engine/players_test.go @@ -14,7 +14,7 @@ import ( var _ = Describe("Players", func() { var players Players var repo *mockPlayerRepository - ctx := context.WithValue(log.NewContext(nil), "user", model.User{ID: "userid", UserName: "johndoe"}) + ctx := context.WithValue(log.NewContext(context.TODO()), "user", model.User{ID: "userid", UserName: "johndoe"}) ctx = context.WithValue(ctx, "username", "johndoe") var beforeRegister time.Time diff --git a/engine/playlists.go b/engine/playlists.go index 65eadb841..d37b4f730 100644 --- a/engine/playlists.go +++ b/engine/playlists.go @@ -69,7 +69,7 @@ func (p *playlists) Delete(ctx context.Context, playlistId string) error { if owner != pls.Owner { return model.ErrNotAuthorized } - return p.ds.Playlist(nil).Delete(playlistId) + return p.ds.Playlist(ctx).Delete(playlistId) } func (p *playlists) Update(ctx context.Context, playlistId string, name *string, idsToAdd []string, idxToRemove []int) error { diff --git a/engine/scrobbler.go b/engine/scrobbler.go index a55a2b97e..01253c4e6 100644 --- a/engine/scrobbler.go +++ b/engine/scrobbler.go @@ -2,7 +2,6 @@ package engine import ( "context" - "errors" "fmt" "time" @@ -57,7 +56,7 @@ func (s *scrobbler) NowPlaying(ctx context.Context, playerId int, playerName, tr } if mf == nil { - return nil, errors.New(fmt.Sprintf(`ID "%s" not found`, trackId)) + return nil, fmt.Errorf(`ID "%s" not found`, trackId) } log.Info("Now Playing", "title", mf.Title, "artist", mf.Artist, "user", userName(ctx)) diff --git a/engine/transcoder/ffmpeg.go b/engine/transcoder/ffmpeg.go index f3cbead53..b65f78144 100644 --- a/engine/transcoder/ffmpeg.go +++ b/engine/transcoder/ffmpeg.go @@ -30,7 +30,7 @@ func (ff *ffmpeg) Start(ctx context.Context, command, path string, maxBitRate in args := createTranscodeCommand(command, path, maxBitRate) log.Trace(ctx, "Executing ffmpeg command", "cmd", args) - cmd := exec.Command(args[0], args[1:]...) + cmd := exec.Command(args[0], args[1:]...) // #nosec cmd.Stderr = os.Stderr if f, err = cmd.StdoutPipe(); err != nil { return @@ -38,7 +38,9 @@ func (ff *ffmpeg) Start(ctx context.Context, command, path string, maxBitRate in if err = cmd.Start(); err != nil { return } - go cmd.Wait() // prevent zombies + + go func() { _ = cmd.Wait() }() // prevent zombies + return } diff --git a/log/log.go b/log/log.go index ceffefe1f..7532ef51b 100644 --- a/log/log.go +++ b/log/log.go @@ -180,6 +180,7 @@ func extractLogger(ctx interface{}) (*logrus.Entry, error) { if logger != nil { return logger.(*logrus.Entry), nil } + return extractLogger(NewContext(ctx)) case *http.Request: return extractLogger(ctx.Context()) } diff --git a/log/log_test.go b/log/log_test.go index f11a2b491..eca2a42dd 100644 --- a/log/log_test.go +++ b/log/log_test.go @@ -41,8 +41,8 @@ var _ = Describe("Logger", func() { Expect(hook.LastEntry().Data).To(BeEmpty()) }) - XIt("Empty context", func() { - Error(context.Background(), "Simple Message") + It("Empty context", func() { + Error(context.TODO(), "Simple Message") Expect(hook.LastEntry().Message).To(Equal("Simple Message")) Expect(hook.LastEntry().Data).To(BeEmpty()) }) @@ -70,7 +70,7 @@ var _ = Describe("Logger", func() { }) It("can get data from the request's context", func() { - ctx := NewContext(nil, "foo", "bar") + ctx := NewContext(context.TODO(), "foo", "bar") req := httptest.NewRequest("get", "/", nil).WithContext(ctx) Error(req, "Simple Message", "key1", "value1") diff --git a/persistence/album_repository_test.go b/persistence/album_repository_test.go index e057344f2..1472c2804 100644 --- a/persistence/album_repository_test.go +++ b/persistence/album_repository_test.go @@ -14,7 +14,7 @@ var _ = Describe("AlbumRepository", func() { var repo model.AlbumRepository BeforeEach(func() { - ctx := context.WithValue(log.NewContext(nil), "user", model.User{ID: "userid"}) + ctx := context.WithValue(log.NewContext(context.TODO()), "user", model.User{ID: "userid"}) repo = NewAlbumRepository(ctx, orm.NewOrm()) }) diff --git a/persistence/artist_repository_test.go b/persistence/artist_repository_test.go index ab37bb62d..79726e96c 100644 --- a/persistence/artist_repository_test.go +++ b/persistence/artist_repository_test.go @@ -14,7 +14,7 @@ var _ = Describe("ArtistRepository", func() { var repo model.ArtistRepository BeforeEach(func() { - ctx := context.WithValue(log.NewContext(nil), "user", model.User{ID: "userid"}) + ctx := context.WithValue(log.NewContext(context.TODO()), "user", model.User{ID: "userid"}) repo = NewArtistRepository(ctx, orm.NewOrm()) }) diff --git a/persistence/genre_repository_test.go b/persistence/genre_repository_test.go index b10df31e8..b76d07a60 100644 --- a/persistence/genre_repository_test.go +++ b/persistence/genre_repository_test.go @@ -1,6 +1,8 @@ package persistence_test import ( + "context" + "github.com/astaxie/beego/orm" "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" @@ -13,7 +15,7 @@ var _ = Describe("GenreRepository", func() { var repo model.GenreRepository BeforeEach(func() { - repo = persistence.NewGenreRepository(log.NewContext(nil), orm.NewOrm()) + repo = persistence.NewGenreRepository(log.NewContext(context.TODO()), orm.NewOrm()) }) It("returns all records", func() { diff --git a/persistence/mediafile_repository_test.go b/persistence/mediafile_repository_test.go index ea09e5a64..5231e4e93 100644 --- a/persistence/mediafile_repository_test.go +++ b/persistence/mediafile_repository_test.go @@ -16,7 +16,7 @@ var _ = Describe("MediaRepository", func() { var mr model.MediaFileRepository BeforeEach(func() { - ctx := context.WithValue(log.NewContext(nil), "user", model.User{ID: "userid"}) + ctx := context.WithValue(log.NewContext(context.TODO()), "user", model.User{ID: "userid"}) mr = NewMediaFileRepository(ctx, orm.NewOrm()) }) diff --git a/persistence/persistence_suite_test.go b/persistence/persistence_suite_test.go index 4e6b8703b..920a2651d 100644 --- a/persistence/persistence_suite_test.go +++ b/persistence/persistence_suite_test.go @@ -22,8 +22,7 @@ func TestPersistence(t *testing.T) { //os.Remove("./test-123.db") //conf.Server.Path = "./test-123.db" conf.Server.DbPath = "file::memory:?cache=shared" - orm.RegisterDataBase("default", db.Driver, conf.Server.DbPath) - New() + _ = orm.RegisterDataBase("default", db.Driver, conf.Server.DbPath) db.EnsureLatestVersion() log.SetLevel(log.LevelCritical) RegisterFailHandler(Fail) @@ -85,7 +84,7 @@ var _ = Describe("Initialize test DB", func() { // TODO Load this data setup from file(s) BeforeSuite(func() { o := orm.NewOrm() - ctx := context.WithValue(log.NewContext(nil), "user", model.User{ID: "userid"}) + ctx := context.WithValue(log.NewContext(context.TODO()), "user", model.User{ID: "userid"}) mr := NewMediaFileRepository(ctx, o) for _, s := range testSongs { err := mr.Put(&s) diff --git a/persistence/playlist_repository_test.go b/persistence/playlist_repository_test.go index 271810146..da8f8b413 100644 --- a/persistence/playlist_repository_test.go +++ b/persistence/playlist_repository_test.go @@ -1,6 +1,8 @@ package persistence import ( + "context" + "github.com/astaxie/beego/orm" "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" @@ -12,7 +14,7 @@ var _ = Describe("PlaylistRepository", func() { var repo model.PlaylistRepository BeforeEach(func() { - repo = NewPlaylistRepository(log.NewContext(nil), orm.NewOrm()) + repo = NewPlaylistRepository(log.NewContext(context.TODO()), orm.NewOrm()) }) Describe("Count", func() { diff --git a/persistence/property_repository_test.go b/persistence/property_repository_test.go index dc9e5c331..de1222c33 100644 --- a/persistence/property_repository_test.go +++ b/persistence/property_repository_test.go @@ -1,8 +1,10 @@ package persistence import ( + "context" + "github.com/astaxie/beego/orm" - . "github.com/deluan/navidrome/log" + "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -12,7 +14,7 @@ var _ = Describe("Property Repository", func() { var pr model.PropertyRepository BeforeEach(func() { - pr = NewPropertyRepository(NewContext(nil), orm.NewOrm()) + pr = NewPropertyRepository(log.NewContext(context.TODO()), orm.NewOrm()) }) It("saves and restore a new property", func() { diff --git a/persistence/user_repository_test.go b/persistence/user_repository_test.go index f9c3b2ab8..1feab6915 100644 --- a/persistence/user_repository_test.go +++ b/persistence/user_repository_test.go @@ -1,6 +1,8 @@ package persistence import ( + "context" + "github.com/astaxie/beego/orm" "github.com/deluan/navidrome/log" "github.com/deluan/navidrome/model" @@ -12,7 +14,7 @@ var _ = Describe("UserRepository", func() { var repo model.UserRepository BeforeEach(func() { - repo = NewUserRepository(log.NewContext(nil), orm.NewOrm()) + repo = NewUserRepository(log.NewContext(context.TODO()), orm.NewOrm()) }) Describe("Put/Get/FindByUsername", func() { diff --git a/scanner/change_detector_test.go b/scanner/change_detector_test.go index a3f3d9b2b..d68afb618 100644 --- a/scanner/change_detector_test.go +++ b/scanner/change_detector_test.go @@ -103,8 +103,8 @@ var _ = Describe("ChangeDetector", func() { Expect(changed).To(BeEmpty()) Expect(changed).To(BeEmpty()) - f, err := os.Create(filepath.Join(testFolder, "a", "b", "new.txt")) - f.Close() + f, _ := os.Create(filepath.Join(testFolder, "a", "b", "new.txt")) + _ = f.Close() changed, deleted, err = newScanner.Scan(lastModifiedSince) Expect(err).To(BeNil()) Expect(deleted).To(BeEmpty()) diff --git a/scanner/metadata_ffmpeg.go b/scanner/metadata_ffmpeg.go index 0e856ccd1..b22cc69e3 100644 --- a/scanner/metadata_ffmpeg.go +++ b/scanner/metadata_ffmpeg.go @@ -84,7 +84,7 @@ func ExtractAllMetadata(inputs []string) (map[string]*Metadata, error) { args := createProbeCommand(inputs) log.Trace("Executing command", "args", args) - cmd := exec.Command(args[0], args[1:]...) + cmd := exec.Command(args[0], args[1:]...) // #nosec output, _ := cmd.CombinedOutput() mds := map[string]*Metadata{} if len(output) == 0 { diff --git a/scanner/scanner.go b/scanner/scanner.go index 7a896ca40..1ebade797 100644 --- a/scanner/scanner.go +++ b/scanner/scanner.go @@ -34,7 +34,7 @@ func (s *Scanner) Rescan(mediaFolder string, fullRescan bool) error { log.Debug("Scanning folder (full scan)", "folder", mediaFolder) } - err := folderScanner.Scan(log.NewContext(nil), lastModifiedSince) + err := folderScanner.Scan(log.NewContext(context.TODO()), lastModifiedSince) if err != nil { log.Error("Error importing MediaFolder", "folder", mediaFolder, err) } @@ -59,7 +59,7 @@ func (s *Scanner) RescanAll(fullRescan bool) error { func (s *Scanner) Status() []StatusInfo { return nil } func (s *Scanner) getLastModifiedSince(folder string) time.Time { - ms, err := s.ds.Property(nil).Get(model.PropLastScan + "-" + folder) + ms, err := s.ds.Property(context.TODO()).Get(model.PropLastScan + "-" + folder) if err != nil { return time.Time{} } @@ -72,11 +72,13 @@ func (s *Scanner) getLastModifiedSince(folder string) time.Time { func (s *Scanner) updateLastModifiedSince(folder string, t time.Time) { millis := t.UnixNano() / int64(time.Millisecond) - s.ds.Property(nil).Put(model.PropLastScan+"-"+folder, fmt.Sprint(millis)) + if err := s.ds.Property(context.TODO()).Put(model.PropLastScan+"-"+folder, fmt.Sprint(millis)); err != nil { + log.Error("Error updating DB after scan", err) + } } func (s *Scanner) loadFolders() { - fs, _ := s.ds.MediaFolder(nil).GetAll() + fs, _ := s.ds.MediaFolder(context.TODO()).GetAll() for _, f := range fs { log.Info("Configuring Media Folder", "name", f.Name, "path", f.Path) s.folders[f.Path] = NewTagScanner(f.Path, s.ds) @@ -85,12 +87,6 @@ func (s *Scanner) loadFolders() { type Status int -const ( - StatusComplete Status = iota - StatusInProgress - StatusError -) - type StatusInfo struct { MediaFolder string Status Status diff --git a/scanner/tag_scanner.go b/scanner/tag_scanner.go index 16010b64e..9a6d8add7 100644 --- a/scanner/tag_scanner.go +++ b/scanner/tag_scanner.go @@ -113,7 +113,7 @@ func (s *TagScanner) Scan(ctx context.Context, lastModifiedSince time.Time) erro return err } - err = s.ds.GC(log.NewContext(nil)) + err = s.ds.GC(log.NewContext(context.TODO())) log.Info("Finished Music Folder", "folder", s.rootFolder, "elapsed", time.Since(start)) return err diff --git a/server/app/app.go b/server/app/app.go index 13f27c61e..52f7f8fc4 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -49,7 +49,7 @@ func (app *Router) routes(path string) http.Handler { app.R(r, "/player", model.Player{}) // Keepalive endpoint to be used to keep the session valid (ex: while playing songs) - r.Get("/keepalive/*", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"response":"ok"}`)) }) + r.Get("/keepalive/*", func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(`{"response":"ok"}`)) }) }) // Serve UI app assets diff --git a/server/app/auth.go b/server/app/auth.go index 534f44910..e800ad58c 100644 --- a/server/app/auth.go +++ b/server/app/auth.go @@ -6,7 +6,6 @@ import ( "errors" "net/http" "strings" - "sync" "time" "github.com/deluan/navidrome/consts" @@ -20,7 +19,6 @@ import ( ) var ( - once sync.Once ErrFirstTime = errors.New("no users created") ) @@ -31,7 +29,7 @@ func Login(ds model.DataStore) func(w http.ResponseWriter, r *http.Request) { username, password, err := getCredentialsFromBody(r) if err != nil { log.Error(r, "Parsing request body", err) - rest.RespondWithError(w, http.StatusUnprocessableEntity, err.Error()) + _ = rest.RespondWithError(w, http.StatusUnprocessableEntity, err.Error()) return } @@ -42,21 +40,21 @@ func Login(ds model.DataStore) func(w http.ResponseWriter, r *http.Request) { func handleLogin(ds model.DataStore, username string, password string, w http.ResponseWriter, r *http.Request) { user, err := validateLogin(ds.User(r.Context()), username, password) if err != nil { - rest.RespondWithError(w, http.StatusInternalServerError, "Unknown error authentication user. Please try again") + _ = rest.RespondWithError(w, http.StatusInternalServerError, "Unknown error authentication user. Please try again") return } if user == nil { log.Warn(r, "Unsuccessful login", "username", username, "request", r.Header) - rest.RespondWithError(w, http.StatusUnauthorized, "Invalid username or password") + _ = rest.RespondWithError(w, http.StatusUnauthorized, "Invalid username or password") return } tokenString, err := auth.CreateToken(user) if err != nil { - rest.RespondWithError(w, http.StatusInternalServerError, "Unknown error authenticating user. Please try again") + _ = rest.RespondWithError(w, http.StatusInternalServerError, "Unknown error authenticating user. Please try again") return } - rest.RespondWithJSON(w, http.StatusOK, + _ = rest.RespondWithJSON(w, http.StatusOK, map[string]interface{}{ "message": "User '" + username + "' authenticated successfully", "token": tokenString, @@ -71,7 +69,7 @@ func getCredentialsFromBody(r *http.Request) (username string, password string, decoder := json.NewDecoder(r.Body) if err = decoder.Decode(&data); err != nil { log.Error(r, "parsing request body", err) - err = errors.New("Invalid request payload") + err = errors.New("invalid request payload") return } username = data["username"] @@ -86,21 +84,21 @@ func CreateAdmin(ds model.DataStore) func(w http.ResponseWriter, r *http.Request username, password, err := getCredentialsFromBody(r) if err != nil { log.Error(r, "parsing request body", err) - rest.RespondWithError(w, http.StatusUnprocessableEntity, err.Error()) + _ = rest.RespondWithError(w, http.StatusUnprocessableEntity, err.Error()) return } c, err := ds.User(r.Context()).CountAll() if err != nil { - rest.RespondWithError(w, http.StatusInternalServerError, err.Error()) + _ = rest.RespondWithError(w, http.StatusInternalServerError, err.Error()) return } if c > 0 { - rest.RespondWithError(w, http.StatusForbidden, "Cannot create another first admin") + _ = rest.RespondWithError(w, http.StatusForbidden, "Cannot create another first admin") return } err = createDefaultUser(r.Context(), ds, username, password) if err != nil { - rest.RespondWithError(w, http.StatusInternalServerError, err.Error()) + _ = rest.RespondWithError(w, http.StatusInternalServerError, err.Error()) return } handleLogin(ds, username, password, w, r) @@ -186,11 +184,11 @@ func authenticator(ds model.DataStore) func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { token, err := getToken(ds, r.Context()) if err == ErrFirstTime { - rest.RespondWithJSON(w, http.StatusUnauthorized, map[string]string{"message": ErrFirstTime.Error()}) + _ = rest.RespondWithJSON(w, http.StatusUnauthorized, map[string]string{"message": ErrFirstTime.Error()}) return } if err != nil { - rest.RespondWithError(w, http.StatusUnauthorized, "Not authenticated") + _ = rest.RespondWithError(w, http.StatusUnauthorized, "Not authenticated") return } @@ -200,7 +198,7 @@ func authenticator(ds model.DataStore) func(next http.Handler) http.Handler { newTokenString, err := auth.TouchToken(token) if err != nil { log.Error(r, "signing new token", err) - rest.RespondWithError(w, http.StatusUnauthorized, "Not authenticated") + _ = rest.RespondWithError(w, http.StatusUnauthorized, "Not authenticated") return } diff --git a/server/initial_setup.go b/server/initial_setup.go index cd0ad971a..45083046a 100644 --- a/server/initial_setup.go +++ b/server/initial_setup.go @@ -1,6 +1,7 @@ package server import ( + "context" "encoding/json" "fmt" "time" @@ -18,7 +19,8 @@ func initialSetup(ds model.DataStore) { return err } - _, err := ds.Property(nil).Get(consts.InitialSetupFlagKey) + properties := ds.Property(context.TODO()) + _, err := properties.Get(consts.InitialSetupFlagKey) if err == nil { return nil } @@ -33,13 +35,14 @@ func initialSetup(ds model.DataStore) { } } - err = ds.Property(nil).Put(consts.InitialSetupFlagKey, time.Now().String()) + err = properties.Put(consts.InitialSetupFlagKey, time.Now().String()) return err }) } func createInitialAdminUser(ds model.DataStore) error { - c, err := ds.User(nil).CountAll() + users := ds.User(context.TODO()) + c, err := users.CountAll() if err != nil { panic(fmt.Sprintf("Could not access User table: %s", err)) } @@ -59,7 +62,7 @@ func createInitialAdminUser(ds model.DataStore) error { Password: initialPassword, IsAdmin: true, } - err := ds.User(nil).Put(&initialUser) + err := users.Put(&initialUser) if err != nil { log.Error("Could not create initial admin user", "user", initialUser, err) } @@ -68,13 +71,14 @@ func createInitialAdminUser(ds model.DataStore) error { } func createJWTSecret(ds model.DataStore) error { - _, err := ds.Property(nil).Get(consts.JWTSecretKey) + properties := ds.Property(context.TODO()) + _, err := properties.Get(consts.JWTSecretKey) if err == nil { return nil } jwtSecret, _ := uuid.NewRandom() log.Warn("Creating JWT secret, used for encrypting UI sessions") - err = ds.Property(nil).Put(consts.JWTSecretKey, jwtSecret.String()) + err = properties.Put(consts.JWTSecretKey, jwtSecret.String()) if err != nil { log.Error("Could not save JWT secret in DB", err) } @@ -82,8 +86,8 @@ func createJWTSecret(ds model.DataStore) error { } func createDefaultTranscodings(ds model.DataStore) error { - repo := ds.Transcoding(nil) - c, _ := repo.CountAll() + transcodings := ds.Transcoding(context.TODO()) + c, _ := transcodings.CountAll() if c != 0 { return nil } @@ -98,7 +102,7 @@ func createDefaultTranscodings(ds model.DataStore) error { return err } log.Info("Creating default transcoding config", "name", t.Name) - if err = repo.Put(&t); err != nil { + if err = transcodings.Put(&t); err != nil { return err } } diff --git a/server/subsonic/api.go b/server/subsonic/api.go index ae9c2c8e5..7b36622a3 100644 --- a/server/subsonic/api.go +++ b/server/subsonic/api.go @@ -162,7 +162,7 @@ func H(r chi.Router, path string, f Handler) { func HGone(r chi.Router, path string) { handle := func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(410) - w.Write([]byte("This endpoint will not be implemented")) + _, _ = w.Write([]byte("This endpoint will not be implemented")) } r.HandleFunc("/"+path, handle) r.HandleFunc("/"+path+".view", handle) @@ -207,5 +207,7 @@ func SendResponse(w http.ResponseWriter, r *http.Request, payload *responses.Sub } else { log.Warn(r.Context(), "API: Failed response", "error", payload.Error.Code, "message", payload.Error.Message) } - w.Write(response) + if _, err := w.Write(response); err != nil { + log.Error(r, "Error sending response to client", "payload", string(response), err) + } } diff --git a/server/subsonic/media_retrieval.go b/server/subsonic/media_retrieval.go index 697a684a4..abeea1c78 100644 --- a/server/subsonic/media_retrieval.go +++ b/server/subsonic/media_retrieval.go @@ -27,7 +27,7 @@ func (c *MediaRetrievalController) GetAvatar(w http.ResponseWriter, r *http.Requ return nil, NewError(responses.ErrorDataNotFound, "Avatar image not found") } defer f.Close() - io.Copy(w, f) + _, _ = io.Copy(w, f) return nil, nil } diff --git a/server/subsonic/media_retrieval_test.go b/server/subsonic/media_retrieval_test.go index 9f5d969ae..bd6b4ca8d 100644 --- a/server/subsonic/media_retrieval_test.go +++ b/server/subsonic/media_retrieval_test.go @@ -24,8 +24,8 @@ func (c *fakeCover) Get(ctx context.Context, id string, size int, out io.Writer) } c.recvId = id c.recvSize = size - out.Write([]byte(c.data)) - return nil + _, err := out.Write([]byte(c.data)) + return err } var _ = Describe("MediaRetrievalController", func() { diff --git a/tests/init_tests.go b/tests/init_tests.go index 3ab445f6f..bb43c1663 100644 --- a/tests/init_tests.go +++ b/tests/init_tests.go @@ -22,7 +22,7 @@ func Init(t *testing.T, skipOnShort bool) { appPath, _ := filepath.Abs(filepath.Join(filepath.Dir(file), "..")) confPath, _ := filepath.Abs(filepath.Join(appPath, "tests", "navidrome-test.toml")) println("Loading test configuration file from " + confPath) - os.Chdir(appPath) + _ = os.Chdir(appPath) conf.LoadFromFile("tests/navidrome-test.toml", true) noLog := os.Getenv("NOLOG") diff --git a/ui/package-lock.json b/ui/package-lock.json index 6f4a10cc0..4ab6087ff 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -13044,6 +13044,14 @@ "resolved": "https://registry.npmjs.org/ra-language-chinese/-/ra-language-chinese-2.0.5.tgz", "integrity": "sha512-BwaqQWDNhQX/Ufe5Ki2GrJ3k5OGmH8dKrQn/npvRik80+tpN4Ew4vbyS8o4E74B4UfSJ8Sj10YdB0bA6FZnAOA==" }, + "ra-language-dutch": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/ra-language-dutch/-/ra-language-dutch-3.4.1.tgz", + "integrity": "sha512-Grnmiq3ykixoykB7o+WPY+J8cBUWCBEEdHWxgZ++LeMxNiv170Xf0LEBgwnlD8+YupZUyZOL4RwEJIeoN3oVYg==", + "requires": { + "ra-core": "^3.4.1" + } + }, "ra-language-english": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/ra-language-english/-/ra-language-english-3.4.1.tgz", diff --git a/ui/package.json b/ui/package.json index 01707fc59..beb8beacb 100644 --- a/ui/package.json +++ b/ui/package.json @@ -10,6 +10,7 @@ "prop-types": "^15.7.2", "ra-data-json-server": "^3.4.1", "ra-language-chinese": "^2.0.5", + "ra-language-dutch": "^3.4.1", "ra-language-french": "^3.4.1", "ra-language-italian": "^3.0.0", "ra-language-portuguese": "^1.6.0", @@ -31,7 +32,8 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "prettier": "prettier --write src/**/*.js" + "prettier": "prettier --write src/*.js src/**/*.js", + "check-formatting": "prettier -c src/*.js src/**/*.js" }, "homepage": ".", "proxy": "http://localhost:4633/", diff --git a/ui/src/App.js b/ui/src/App.js index dc6851ea0..a3143bee1 100644 --- a/ui/src/App.js +++ b/ui/src/App.js @@ -35,8 +35,8 @@ const App = () => ( customReducers: { queue: playQueueReducer, albumView: albumViewReducer, - theme: themeReducer - } + theme: themeReducer, + }, })} > ( ) : ( ), - + , ]} diff --git a/ui/src/album/AlbumActions.js b/ui/src/album/AlbumActions.js index 433da1d62..8c647e2db 100644 --- a/ui/src/album/AlbumActions.js +++ b/ui/src/album/AlbumActions.js @@ -8,7 +8,7 @@ import PlayArrowIcon from '@material-ui/icons/PlayArrow' import ShuffleIcon from '@material-ui/icons/Shuffle' import React from 'react' import { useDispatch } from 'react-redux' -import { playAlbum } from '../audioplayer' +import { playAlbum, shuffleAlbum } from '../audioplayer' export const AlbumActions = ({ className, @@ -28,17 +28,6 @@ export const AlbumActions = ({ return acc }, {}) - const shuffle = (data) => { - const ids = Object.keys(data) - for (let i = ids.length - 1; i > 0; i--) { - let j = Math.floor(Math.random() * (i + 1)) - ;[ids[i], ids[j]] = [ids[j], ids[i]] - } - const shuffled = {} - ids.forEach((id) => (shuffled[id] = data[id])) - return shuffled - } - return (