diff --git a/Makefile b/Makefile index c1c32b59..7ec5df72 100644 --- a/Makefile +++ b/Makefile @@ -111,6 +111,12 @@ single: warning-noui-build ##@Cross_Compilation Build binaries for a single supp goreleaser build --clean --snapshot -p 2 --single-target --id navidrome_${GOOS}_${GOARCH} .PHONY: single +docker: buildjs ##@Build Build Docker linux/amd64 image (tagged as `deluan/navidrome:develop`) + GOOS=linux GOARCH=amd64 make single + @echo "Building Docker image" + docker build . --platform linux/amd64 -t deluan/navidrome:develop -f .github/workflows/pipeline.dockerfile +.PHONY: docker + warning-noui-build: @echo "WARNING: This command does not build the frontend, it uses the latest built with 'make buildjs'" .PHONY: warning-noui-build diff --git a/core/artwork/cache_warmer.go b/core/artwork/cache_warmer.go index eed935e9..210edde1 100644 --- a/core/artwork/cache_warmer.go +++ b/core/artwork/cache_warmer.go @@ -131,7 +131,7 @@ func (a *cacheWarmer) doCacheImage(ctx context.Context, id model.ArtworkID) erro r, _, err := a.artwork.Get(ctx, id, consts.UICoverArtSize) if err != nil { - return fmt.Errorf("error cacheing id='%s': %w", id, err) + return fmt.Errorf("error caching id='%s': %w", id, err) } defer r.Close() _, err = io.Copy(io.Discard, r) diff --git a/core/playback/mpv/socket_name.go b/core/playback/mpv/socket_name.go new file mode 100644 index 00000000..a15dee8e --- /dev/null +++ b/core/playback/mpv/socket_name.go @@ -0,0 +1,9 @@ +//go:build !windows + +package mpv + +import "github.com/navidrome/navidrome/utils" + +func socketName(prefix, suffix string) string { + return utils.TempFileName(prefix, suffix) +} diff --git a/core/playback/mpv/socket_name_win.go b/core/playback/mpv/socket_name_win.go new file mode 100644 index 00000000..0611c060 --- /dev/null +++ b/core/playback/mpv/socket_name_win.go @@ -0,0 +1,15 @@ +//go:build windows + +package mpv + +import ( + "path/filepath" + + "github.com/google/uuid" +) + +func socketName(prefix, suffix string) string { + // Windows needs to use a named pipe for the socket + // see https://mpv.io/manual/master#using-mpv-from-other-programs-or-scripts + return filepath.Join(`\\.\pipe\mpvsocket`, prefix+uuid.NewString()+suffix) +} diff --git a/core/playback/mpv/track.go b/core/playback/mpv/track.go index 52c87bda..3d4dfcd7 100644 --- a/core/playback/mpv/track.go +++ b/core/playback/mpv/track.go @@ -13,7 +13,6 @@ import ( "github.com/dexterlb/mpvipc" "github.com/navidrome/navidrome/log" "github.com/navidrome/navidrome/model" - "github.com/navidrome/navidrome/utils" ) type MpvTrack struct { @@ -32,7 +31,7 @@ func NewTrack(playbackDoneChannel chan bool, deviceName string, mf model.MediaFi return nil, err } - tmpSocketName := utils.TempFileName("mpv-ctrl-", ".socket") + tmpSocketName := socketName("mpv-ctrl-", ".socket") args := createMPVCommand(deviceName, mf.Path, tmpSocketName) exe, err := start(args) @@ -103,35 +102,6 @@ func (t *MpvTrack) Pause() { } } -func (t *MpvTrack) Close() { - log.Debug("Closing resources", "track", t) - t.CloseCalled = true - // trying to shutdown mpv process using socket - if t.isSocketFilePresent() { - log.Debug("sending shutdown command") - _, err := t.Conn.Call("quit") - if err != nil { - log.Error("Error sending quit command to mpv-ipc socket", err) - - if t.Exe != nil { - log.Debug("cancelling executor") - err = t.Exe.Cancel() - if err != nil { - log.Error("Error canceling executor", err) - } - } - } - } - - if t.isSocketFilePresent() { - log.Debug("Removing socketfile", "socketfile", t.IPCSocketName) - err := os.Remove(t.IPCSocketName) - if err != nil { - log.Error("Error cleaning up socketfile", "socketfile", t.IPCSocketName, err) - } - } -} - func (t *MpvTrack) isSocketFilePresent() bool { if len(t.IPCSocketName) < 1 { return false diff --git a/core/playback/mpv/track_close.go b/core/playback/mpv/track_close.go new file mode 100644 index 00000000..2dee3b02 --- /dev/null +++ b/core/playback/mpv/track_close.go @@ -0,0 +1,38 @@ +//go:build !windows + +package mpv + +import ( + "os" + + "github.com/navidrome/navidrome/log" +) + +func (t *MpvTrack) Close() { + log.Debug("Closing resources", "track", t) + t.CloseCalled = true + // trying to shutdown mpv process using socket + if t.isSocketFilePresent() { + log.Debug("sending shutdown command") + _, err := t.Conn.Call("quit") + if err != nil { + log.Error("Error sending quit command to mpv-ipc socket", err) + + if t.Exe != nil { + log.Debug("cancelling executor") + err = t.Exe.Cancel() + if err != nil { + log.Error("Error canceling executor", err) + } + } + } + } + + if t.isSocketFilePresent() { + log.Debug("Removing socketfile", "socketfile", t.IPCSocketName) + err := os.Remove(t.IPCSocketName) + if err != nil { + log.Error("Error cleaning up socketfile", "socketfile", t.IPCSocketName, err) + } + } +} diff --git a/core/playback/mpv/track_close_win.go b/core/playback/mpv/track_close_win.go new file mode 100644 index 00000000..fd8fbd01 --- /dev/null +++ b/core/playback/mpv/track_close_win.go @@ -0,0 +1,8 @@ +//go:build windows + +package mpv + +func (t *MpvTrack) Close() { + // Windows automatically handles closing + // and cleaning up named pipe +} diff --git a/core/playback/playbackserver.go b/core/playback/playbackserver.go index 3fbe3ced..9687aade 100644 --- a/core/playback/playbackserver.go +++ b/core/playback/playbackserver.go @@ -68,7 +68,7 @@ func (ps *playbackServer) initDeviceStatus(devices []conf.AudioDeviceDefinition, defaultDeviceFound := false if defaultDevice == "" { - // if there are no devices given and no default device, we create a sythetic device named "auto" + // if there are no devices given and no default device, we create a synthetic device named "auto" if len(devices) == 0 { pbDevices[0] = *NewPlaybackDevice(ps, "auto", "auto") } diff --git a/go.mod b/go.mod index 3e2f5db9..879061d3 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( github.com/mileusna/useragent v1.3.4 github.com/onsi/ginkgo/v2 v2.17.1 github.com/onsi/gomega v1.32.0 - github.com/pelletier/go-toml/v2 v2.2.0 + github.com/pelletier/go-toml/v2 v2.2.1 github.com/pocketbase/dbx v1.10.1 github.com/pressly/goose/v3 v3.19.2 github.com/prometheus/client_golang v1.19.0 diff --git a/go.sum b/go.sum index e94fcf45..e8659af8 100644 --- a/go.sum +++ b/go.sum @@ -328,8 +328,8 @@ github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4a github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s= github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= -github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= -github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.1 h1:9TA9+T8+8CUCO2+WYnDLCgrYi9+omqKXyjDtosvtEhg= +github.com/pelletier/go-toml/v2 v2.2.1/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= diff --git a/resources/i18n/fr.json b/resources/i18n/fr.json index 30160f83..b3ca0558 100644 --- a/resources/i18n/fr.json +++ b/resources/i18n/fr.json @@ -148,9 +148,9 @@ "name": "Nom", "duration": "Durée", "ownerName": "Propriétaire", - "public": "Public", + "public": "Publique", "updatedAt": "Mise à jour le", - "createdAt": "Créé le", + "createdAt": "Créée le", "songCount": "Titres", "comment": "Commentaire", "sync": "Import automatique", @@ -174,7 +174,7 @@ "name": "Nom", "streamUrl": "Lien du stream", "homePageUrl": "Lien de la page d'accueil", - "updatedAt": "Mis à jour le", + "updatedAt": "Mise à jour le", "createdAt": "Créée le" }, "actions": { @@ -195,7 +195,7 @@ "maxBitRate": "Bitrate maximum", "updatedAt": "Mis à jour le", "createdAt": "Créé le", - "downloadable": "Autoriser les téléchargements?" + "downloadable": "Autoriser les téléchargements ?" } } }, @@ -274,7 +274,7 @@ "not_found": "Page manquante", "show": "%{name} #%{id}", "empty": "Pas encore de %{name}.", - "invite": "Voulez-vous en créer un ?" + "invite": "Voulez-vous en créer ?" }, "input": { "file": { @@ -362,18 +362,18 @@ "musicbrainz": "Ouvrir dans MusicBrainz" }, "lastfmLink": "Lire plus...", - "listenBrainzLinkSuccess": "La liaison et le scrobble avec ListenBrainz sont maintenant activés pour l'utilisateur: %{user}", - "listenBrainzLinkFailure": "Échec lors de la liaison avec ListenBrainz: %{error}", + "listenBrainzLinkSuccess": "La liaison et le scrobble avec ListenBrainz sont maintenant activés pour l'utilisateur : %{user}", + "listenBrainzLinkFailure": "Échec lors de la liaison avec ListenBrainz : %{error}", "listenBrainzUnlinkSuccess": "La liaison et le scrobble avec ListenBrainz sont maintenant désactivés", "listenBrainzUnlinkFailure": "Échec lors de la désactivation de la liaison avec ListenBrainz", "downloadOriginalFormat": "Télécharger au format original", "shareOriginalFormat": "Partager avec le format original", "shareDialogTitle": "Partager %{resource} '%{name}'", "shareBatchDialogTitle": "Partager 1 %{resource} |||| Partager %{smart_count} %{resource}", - "shareSuccess": "Lien copié vers le presse-papier: %{url}", + "shareSuccess": "Lien copié vers le presse-papier : %{url}", "shareFailure": "Erreur en copiant le lien %{url} vers le presse-papier", "downloadDialogTitle": "Télécharger %{resource} '%{name}' (%{size})", - "shareCopyToClipboard": "Copier vers le presse-papier: Ctrl+C, Enter" + "shareCopyToClipboard": "Copier vers le presse-papier : Ctrl+C, Enter" }, "menu": { "library": "Bibliothèque", @@ -390,7 +390,7 @@ "lastfmScrobbling": "Scrobbler vers Last.fm", "listenBrainzScrobbling": "Scrobbler vers ListenBrainz", "replaygain": "Mode ReplayGain", - "preAmp": "Pre-amplification ReplayGain (dB)", + "preAmp": "Pré-amplification ReplayGain (dB)", "gain": { "none": "Désactivé", "album": "Utiliser le gain de l'album", @@ -457,4 +457,4 @@ "current_song": "Aller à la chanson en cours" } } -} \ No newline at end of file +} diff --git a/ui/package-lock.json b/ui/package-lock.json index e68b3ff9..5edcb633 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -33,7 +33,7 @@ "react-drag-listview": "^0.1.8", "react-ga": "^3.3.1", "react-hotkeys": "^2.0.0", - "react-icons": "^5.0.1", + "react-icons": "^5.1.0", "react-image-lightbox": "^5.1.4", "react-measure": "^2.5.2", "react-redux": "^7.2.9", @@ -43,12 +43,12 @@ "uuid": "^9.0.1" }, "devDependencies": { - "@testing-library/jest-dom": "^6.2.0", + "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^14.5.2", "css-mediaquery": "^0.1.2", - "prettier": "3.2.2", + "prettier": "3.2.5", "ra-test": "^3.19.12", "react-scripts": "5.0.1", "workbox-cli": "^7.0.0" @@ -4715,9 +4715,9 @@ "dev": true }, "node_modules/@testing-library/jest-dom": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.2.0.tgz", - "integrity": "sha512-+BVQlJ9cmEn5RDMUS8c2+TU6giLvzaHZ8sU/x0Jj7fk+6/46wPdwlgOPcpxS17CjcanBi/3VmGMqVr2rmbUmNw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.2.tgz", + "integrity": "sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==", "dev": true, "dependencies": { "@adobe/css-tools": "^4.3.2", @@ -4736,6 +4736,7 @@ }, "peerDependencies": { "@jest/globals": ">= 28", + "@types/bun": "latest", "@types/jest": ">= 28", "jest": ">= 28", "vitest": ">= 0.32" @@ -4744,6 +4745,9 @@ "@jest/globals": { "optional": true }, + "@types/bun": { + "optional": true + }, "@types/jest": { "optional": true }, @@ -10131,9 +10135,9 @@ "dev": true }, "node_modules/follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true, "funding": [ { @@ -18504,9 +18508,9 @@ } }, "node_modules/prettier": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.2.tgz", - "integrity": "sha512-HTByuKZzw7utPiDO523Tt2pLtEyK7OibUD9suEJQrPUCYQqrHr74GGX6VidMrovbf/I50mPqr8j/II6oBAuc5A==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -19412,9 +19416,9 @@ } }, "node_modules/react-icons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", - "integrity": "sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.1.0.tgz", + "integrity": "sha512-D3zug1270S4hbSlIRJ0CUS97QE1yNNKDjzQe3HqY0aefp2CBn9VgzgES27sRR2gOvFK+0CNx/BW0ggOESp6fqQ==", "peerDependencies": { "react": "*" } @@ -28190,9 +28194,9 @@ } }, "@testing-library/jest-dom": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.2.0.tgz", - "integrity": "sha512-+BVQlJ9cmEn5RDMUS8c2+TU6giLvzaHZ8sU/x0Jj7fk+6/46wPdwlgOPcpxS17CjcanBi/3VmGMqVr2rmbUmNw==", + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.4.2.tgz", + "integrity": "sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==", "dev": true, "requires": { "@adobe/css-tools": "^4.3.2", @@ -32389,9 +32393,9 @@ "dev": true }, "follow-redirects": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz", - "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "dev": true }, "fork-ts-checker-webpack-plugin": { @@ -38703,9 +38707,9 @@ "dev": true }, "prettier": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.2.tgz", - "integrity": "sha512-HTByuKZzw7utPiDO523Tt2pLtEyK7OibUD9suEJQrPUCYQqrHr74GGX6VidMrovbf/I50mPqr8j/II6oBAuc5A==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true }, "pretty-bytes": { @@ -39386,9 +39390,9 @@ } }, "react-icons": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.0.1.tgz", - "integrity": "sha512-WqLZJ4bLzlhmsvme6iFdgO8gfZP17rfjYEJ2m9RsZjZ+cc4k1hTzknEz63YS1MeT50kVzoa1Nz36f4BEx+Wigw==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.1.0.tgz", + "integrity": "sha512-D3zug1270S4hbSlIRJ0CUS97QE1yNNKDjzQe3HqY0aefp2CBn9VgzgES27sRR2gOvFK+0CNx/BW0ggOESp6fqQ==", "requires": {} }, "react-image-lightbox": { diff --git a/ui/package.json b/ui/package.json index a3f09820..92c6b4fa 100644 --- a/ui/package.json +++ b/ui/package.json @@ -28,7 +28,7 @@ "react-drag-listview": "^0.1.8", "react-ga": "^3.3.1", "react-hotkeys": "^2.0.0", - "react-icons": "^5.0.1", + "react-icons": "^5.1.0", "react-image-lightbox": "^5.1.4", "react-measure": "^2.5.2", "react-redux": "^7.2.9", @@ -38,12 +38,12 @@ "uuid": "^9.0.1" }, "devDependencies": { - "@testing-library/jest-dom": "^6.2.0", + "@testing-library/jest-dom": "^6.4.2", "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^7.0.2", "@testing-library/user-event": "^14.5.2", "css-mediaquery": "^0.1.2", - "prettier": "3.2.2", + "prettier": "3.2.5", "ra-test": "^3.19.12", "react-scripts": "5.0.1", "workbox-cli": "^7.0.0" diff --git a/ui/src/dialogs/AddToPlaylistDialog.js b/ui/src/dialogs/AddToPlaylistDialog.js index 6033d103..db768027 100644 --- a/ui/src/dialogs/AddToPlaylistDialog.js +++ b/ui/src/dialogs/AddToPlaylistDialog.js @@ -54,15 +54,19 @@ export const AddToPlaylistDialog = () => { }) .then(() => { const len = trackIds.length - notify('message.songsAddedToPlaylist', 'info', { smart_count: len }) + notify('message.songsAddedToPlaylist', { + messageArgs: { smart_count: len }, + }) onSuccess && onSuccess(value, len) refresh() }) .catch(() => { - notify('ra.page.error', 'warning') + notify('ra.page.error', { type: 'warning' }) }) } else { - notify('message.songsAddedToPlaylist', 'info', { smart_count: 0 }) + notify('message.songsAddedToPlaylist', { + messageArgs: { smart_count: 0 }, + }) } } diff --git a/ui/src/personal/LastfmScrobbleToggle.js b/ui/src/personal/LastfmScrobbleToggle.js index ec0fddbb..c6499b63 100644 --- a/ui/src/personal/LastfmScrobbleToggle.js +++ b/ui/src/personal/LastfmScrobbleToggle.js @@ -23,7 +23,9 @@ const Progress = (props) => { ) const callbackUrl = `${window.location.origin}${callbackEndpoint}` openedTab.current = openInNewTab( - `https://www.last.fm/api/auth/?api_key=${localStorage.getItem('lastfm-apikey')}&cb=${callbackUrl}`, + `https://www.last.fm/api/auth/?api_key=${localStorage.getItem( + 'lastfm-apikey', + )}&cb=${callbackUrl}`, ) }, []) diff --git a/update-translations.sh b/update-translations.sh index 35d59955..97f7a675 100755 --- a/update-translations.sh +++ b/update-translations.sh @@ -3,6 +3,7 @@ set -e download_lang() { + filename=resources/i18n/"$1".json url=$(curl -s -X POST https://poeditor.com/api/ \ -d api_token="${POEDITOR_APIKEY}" \ -d action="export" \ @@ -13,7 +14,12 @@ download_lang() { echo "Failed to export $1" return 1 fi - curl -sSL "$url" > resources/i18n/"$1".json + curl -sSL "$url" > poeditor.json + if [ "$(jq -c . < $filename)" != "$(jq -c . < poeditor.json)" ]; then + mv poeditor.json "$filename" + else + rm poeditor.json + fi } for file in resources/i18n/*.json; do