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 (