Compare commits

...

2140 Commits

Author SHA1 Message Date
Deluan 61903facdf Revert isDBInitialized 2024-05-22 16:20:57 -04:00
Drew Weymouth b6fce0e686
Fix XML marshaling of OpenSubsonic structured lyrics (#3041) 2024-05-22 12:15:14 -04:00
Deluan f88d3f82da Replace panics with log.Fatals 2024-05-21 17:50:02 -04:00
Deluan 55bff343cd Optimize SQLite3 access. Mainly separate read access from write access.
Based on tips from https://archive.is/Xfjh6#selection-257.0-278.0
2024-05-21 17:19:41 -04:00
dependabot[bot] 68f03d0167
Bump github.com/matoous/go-nanoid/v2 from 2.0.0 to 2.1.0 (#3038)
Bumps [github.com/matoous/go-nanoid/v2](https://github.com/matoous/go-nanoid) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/matoous/go-nanoid/releases)
- [Commits](https://github.com/matoous/go-nanoid/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: github.com/matoous/go-nanoid/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-21 10:45:04 -04:00
Deluan 643c763cb4 Show number of results from a query in the logs 2024-05-20 16:21:41 -04:00
Deluan Quintão 67865512c8
Update Ukrainian translations (#3029)
Co-authored-by: deluan <331353+deluan@users.noreply.github.com>
2024-05-19 22:17:13 -04:00
Deluan b2ecc1d16f Fix G404 gosec lint error 2024-05-19 21:55:19 -04:00
Deluan bcaa180fc7 Fix 32 bits builds 2024-05-19 13:03:13 -04:00
Deluan aeed5a7099 Update caniuse-lite 2024-05-19 12:45:19 -04:00
Deluan 3977ef6e0f Make first WebUI random page stick 2024-05-19 12:35:30 -04:00
Deluan 653b4d97f9 Add missing Test function 2024-05-18 15:05:40 -04:00
Guilherme Souza 98218d045e
Deterministic pagination in random albums sort (#1841)
* Deterministic pagination in random albums sort

* Reseed on first random page

* Add unit tests

* Use rand in Subsonic API

* Use different seeds per user on SEEDEDRAND() SQLite3 function

* Small refactor

* Fix id mismatch

* Add seeded random to media_file (subsonic endpoint `getRandomSongs`)

* Refactor

* Remove unneeded import

---------

Co-authored-by: Deluan <deluan@navidrome.org>
2024-05-18 14:10:53 -04:00
Deluan a9feeac793 Revert "Always run docker steps (#3034)"
This reverts commit 5d41165b5b.
2024-05-18 11:54:16 -04:00
Deluan 1c0551f4f7 Revert "Fix docker publishing for PRs"
This reverts commit 15c9a0ded3.
2024-05-18 11:54:15 -04:00
Deluan 15c9a0ded3 Fix docker publishing for PRs 2024-05-17 22:45:54 -04:00
Deluan Quintão 5d41165b5b
Always run docker steps (#3034) 2024-05-17 22:22:47 -04:00
Deluan 0a763b91d5 Fix lint error 2024-05-17 21:46:59 -04:00
Deluan 4d28d534cc Refactor random.WeightedChooser, unsing generics 2024-05-17 15:45:34 -04:00
Deluan a7a4fb522c Simplify resources.FS 2024-05-16 22:53:51 -04:00
Deluan 7f52ff72dc Simplify image format detection code 2024-05-16 13:49:40 -04:00
Deluan 8ed07333ed Improve resizeImage code readability 2024-05-16 13:49:40 -04:00
Rob Emery 52235c291d
Fix memory leak in CachedGenreRepository (#3031)
that the scanner was run, the ttlcache was also created each time.
This caused (under testing with 166 genres in the database) the
memory consumed by navidrome to 101.18MB over approx 3 days; 96%
of which is in instances of this cache. Swapping to a singleton
has reduced this to down to ~ 2.6MB

Co-authored-by: Rob Emery <git@mintsoft.net>
2024-05-16 12:16:56 -04:00
Fynn Petersen-Frey de0a08915c
fix bug in jukebox: property unavailable (#3024)
* fix bug in jukebox: property unavailable

* fix lint error
2024-05-15 09:48:09 -04:00
Deluan 45c4583f1b Fix race condition 2024-05-13 09:28:19 -04:00
Deluan 478c709a64 Associate main entities with library 2024-05-12 21:37:42 -04:00
Deluan 477bcaee58 Store MusicFolder as a library in DB 2024-05-12 21:37:42 -04:00
Deluan 081ef85db6 Rename MediaFolder to Library 2024-05-12 21:37:42 -04:00
Deluan 6f2643e55e Refactor to use more Go 1.22 features 2024-05-12 20:04:53 -04:00
Deluan 9ee63b39cb Update Go to 1.22.3 2024-05-12 20:04:53 -04:00
Deluan c556088820 Change dsf mime-type to audio/x-dsf.
Fix #3021
2024-05-12 11:33:50 -04:00
Deluan 78f554721a Revert "Add download link to PR" workflow 2024-05-11 20:40:12 -04:00
Deluan 2c8c87a980 Remove duplicated test 2024-05-11 20:15:02 -04:00
Deluan 3463d0c208 Simplify random.Int64 usage with generics 2024-05-11 20:10:46 -04:00
Deluan 0ae2944073 Refactor random functions 2024-05-11 20:04:21 -04:00
Deluan 30ae468dc1 Uses Unix milliseconds support from standard Go lib 2024-05-11 19:50:30 -04:00
Deluan ec68d69d56 Refactor cache.HTTPClient 2024-05-11 19:37:12 -04:00
Deluan 955a9b43af Refactor merge.FS 2024-05-11 19:37:12 -04:00
Deluan 56809419c2 Fix "Add download link to PR" workflow 2024-05-11 18:50:46 -04:00
Deluan 3a2a5e961b Add samplingRate to OpenSubsonic responses 2024-05-11 17:57:45 -04:00
Deluan f3bb022238 Add sampleRate to the DB 2024-05-11 17:57:45 -04:00
Deluan 472324e280 Read sampleRate from audio files 2024-05-11 17:57:45 -04:00
Deluan ed83c22632 Do not panic if when updatePlaylist is called with a non-existent ID.
Fix #2876
2024-05-11 15:37:50 -04:00
edthu 2fdc1677f7
Add Catppuccin Macchiato Theme (#3014)
* Added Catppuccin Macchiato theme

* fixed index.js formatting
2024-05-11 13:08:51 -04:00
Deluan 80e68dfbcd Bump actions/github-script to v7 2024-05-10 16:00:21 -04:00
Deluan a9c745839b Bump actions/stale and dessant/lock-threads versions 2024-05-10 15:51:16 -04:00
Deluan bb96d455f8 Replace sync.WaitGroup with more appropriate errgroup.Group 2024-05-10 15:27:07 -04:00
Deluan c0885b55db Fix M3U mimetype on Debian Bullseye 2024-05-09 22:26:15 -04:00
Deluan 00cbe4c357 Update Go to 1.22.3 2024-05-09 22:26:15 -04:00
Valeri Sokolov 2b49c7ff76
fix: languageName for Persian (#3011)
"انگلیسی" is "English"
2024-05-09 17:08:43 -04:00
Deluan 09d1fd0658 Simplify normalized AlbumPlayCountMode calc 2024-05-09 08:13:42 -04:00
Deluan 747069b229 Remove unused code 2024-05-09 07:47:32 -04:00
Deluan 885cd345ab Clean up runNavidrome function 2024-05-09 07:44:08 -04:00
Deluan Quintão c4b05dac28
Make sorting lists by name/title case-insensitive (#2993)
* Make sort by order_* fields case-insensitive.

* Sort internet radios by name case-insensitive
2024-05-09 07:08:15 -04:00
Deluan Quintão 6408dda948
Terminate all MPV instances when stopping Navidrome (#3008)
* Terminate all mpv instances when stopping Navidrome

* Exit trackSwitcher goroutine when terminating

* Remove potential race condition when starting the Playback device

* Fix lint error

* Removed unused and unneeded vars/functions

* Use device short name in log

* Small refactor

* Small nitpick

* Make start functions more uniform
2024-05-09 06:57:24 -04:00
Deluan 677d9947f3 Make dependency injection more consistent 2024-05-08 22:21:38 -04:00
Deluan a0290587b9 Fix migration package name mismatch 2024-05-08 19:54:48 -04:00
Deluan eb93136b3f Change default transcodings to a proper typed struct 2024-05-08 17:39:25 -04:00
Deluan 62cc8a2d4b Fix ambiguous column when sorting media_files by created_at.
Fix #3006
2024-05-08 08:24:26 -04:00
Deluan dd4374cec6 Limit access to Jukebox for admins only (configurable).
Closes #2849
2024-05-07 19:35:43 -04:00
Deluan 86567f5406 Bump Go dependencies 2024-05-07 19:26:02 -04:00
Matthias Schmidt ff8dca5abe
Guard against missing active track (#2996)
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
2024-05-07 19:22:39 -04:00
Matthias Schmidt b3d70e9264
Persist adjusted volume (#2997)
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
2024-05-07 19:21:35 -04:00
Ludovic Fernandez 4d29184998
Improves golangci-lint configuration and workflow (#3004)
* chore: the default Go version is based on the go.mod

* chore: use linter configuration instead of exclude-rules

* chore: update workflow
2024-05-07 18:52:26 -04:00
Deluan 2470471b2b Pin golangci-lint-action version as a workaround to fix the pipeline.
See https://github.com/golangci/golangci-lint/issues/4695
2024-05-06 21:53:47 +02:00
Deluan 544ae90ec1 Fix CollapsibleComment in PlaylistDetails. Closes #2992 2024-05-02 13:48:10 -04:00
Deluan aef49cb8d6 Add `HTTPSecurityHeaders.CustomFrameOptionsValue` option.
Requested in https://github.com/navidrome/navidrome/issues/248#issuecomment-1783768985
2024-05-02 12:35:16 -04:00
Deluan 7c5eec715d Fix typo 2024-05-01 23:09:11 -04:00
Kendall Garner a4c2232041
Sort repeated lyrics that may be out of order (#2989)
With synchronized lyrics with repeated text, there is not a guarantee that the repeat is in order (e.g. `[00:00.00][00:10.00] a\n[00:05.00]b`).
This change will post-process lyrics with repeated timestamps in one line to ensure that it is always sorted.
2024-05-01 21:54:46 -04:00
Deluan 8f11b991d2 Bump Go dependencies 2024-05-01 20:40:34 -04:00
Deluan d4a9a9e555 Fix PlaylistTracks's loadAllGenres. Fix #2988 2024-05-01 20:17:42 -04:00
Deluan a8955f24e0 Fix AlbumPlayCountMode. Closes #2984 2024-05-01 20:05:36 -04:00
Deluan 2c06a4234e Fix int types in OpenSubsonic responses.
Refer to https://support.symfonium.app/t/symfonium-sync-crashes-when-tpos-is-not-an-int/4204
2024-05-01 13:57:11 -04:00
Deluan 7ab7b5df5e Fix signaler on Windows 2024-04-28 18:32:28 -04:00
Deluan 3d9fff36f7 Use signal.NotifyContext 2024-04-28 17:44:11 -04:00
Deluan 31fcab07d2 Refactor loadGenres, remove duplication 2024-04-28 17:04:12 -04:00
Deluan de90152a71 Refactor DB Album mapping to model.Album 2024-04-28 13:51:57 -04:00
Deluan 27875ba2dd Load mime_types from external file 2024-04-28 12:18:24 -04:00
Deluan 28f7ef43c1 Remove AlbumPlayCountMode from command line options 2024-04-27 20:39:16 -04:00
Deluan 92a98cd558 Add tests for AlbumPlayCountMode, change the calc to match the request from #1032 2024-04-27 15:20:46 -04:00
Deluan 5d50558610 Add tests for AlbumPlayCountMode 2024-04-27 15:07:50 -04:00
vvdveen 8bff1ad512 Add AlbumPlayCountMode config option (#2803)
Closes #1032

* feat(album_repository.go): add kodi-style album playcount option - #1032

Signed-off-by: Victor van der Veen <vvdveen@gmail.com>

* fix format issue and remove reference to kodi (now normalized)

Signed-off-by: Victor van der Veen <vvdveen@gmail.com>

* reduced complexity but added rounding

Signed-off-by: Victor van der Veen <vvdveen@gmail.com>

* Use constants for AlbumPlayCountMode values

---------

Signed-off-by: Victor van der Veen <vvdveen@gmail.com>
Co-authored-by: Deluan <deluan@navidrome.org>
2024-04-27 14:10:40 -04:00
crazygolem 1e96b858a9
Add support for Reverse Proxy auth in Subsonic endpoints (#2558)
* feat(subsonic): Add support for Reverse Proxy auth - #2557

Signed-off-by: Jeremiah Menétrey <superjun1@gmail.com>

* Small refactoring

---------

Signed-off-by: Jeremiah Menétrey <superjun1@gmail.com>
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
2024-04-27 13:47:42 -04:00
Deluan aafd5a952c Bump github.com/spf13/viper from 1.15.0 to 1.18.2 2024-04-26 22:11:43 -04:00
Deluan Quintão d9cd5efd67
Bump Go dependencies (#2976)
* Fix build

* Bump dependencies
2024-04-26 18:21:10 -04:00
Deluan affa9c3478 Bump github.com/pressly/goose/v3 from 3.19.2 to 3.20.0 2024-04-26 18:07:06 -04:00
Anna Smith 651a8fdaf9
Fix typo in comment (#2974) 2024-04-26 17:59:39 -04:00
Deluan f7fc17c0f7 Add OpenSubsonic channelCount 2024-04-26 17:51:04 -04:00
Deluan f5df948eb1 Fix scrobble error spam in the logs.
Relates to #2831 and #2975
2024-04-26 16:59:14 -04:00
crazygolem 18143fa5a1
Use the RealIP middleware also behind a reverse proxy (#2858)
* Use the RealIP middleware only behind a reverse proxy

* Fix proxy ip source in tests

* Fix test for PR#2087

The PR did not update the test after changing the behavior, but the test still
passed because another condition was preventing the user from being created in
the test.

* Use RealIP even without a trusted reverse proxy

* Use own type for context key

* Fix casing to follow go's conventions

* Do not apply RealIP middleware twice

* Fix IP source in logs

The most interesting data point in the log message is the proxy's IP, but
having the client IP too can help identify integration issues.
2024-04-25 20:43:58 -04:00
Tim 8f9ed1b994
Handling long playlist comments (#2973)
Closes #1737

* wrapping playlist comment in a <Collapse> element

* Extract common collapsible logic into a component

---------

Co-authored-by: Deluan <deluan@navidrome.org>
2024-04-25 20:28:25 -04:00
dependabot[bot] cf66594b6d
Bump github.com/onsi/gomega from 1.32.0 to 1.33.0 (#2968)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.32.0 to 1.33.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.32.0...v1.33.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-22 17:09:51 -04:00
Deluan ca005f6457 Include MPV in release Docker image. Refers to #2910 2024-04-21 21:02:36 -04:00
Deluan 6dcfe4d455 Fix typo 2024-04-20 13:16:50 -04:00
Deluan 7871d69adb Allow comments in the NSP file.
See comment https://github.com/navidrome/navidrome/issues/1417#issuecomment-2064731407
2024-04-20 12:50:45 -04:00
Deluan 78182f40d6 Block regular users from changing their own playlists ownership 2024-04-20 12:08:07 -04:00
Deluan 9aeaaa6610 Fix issue in https://github.com/navidrome/navidrome/issues/2767#issuecomment-2065636352 2024-04-19 12:38:02 -04:00
dependabot[bot] 068c1e9a23
Bump golang.org/x/net from 0.21.0 to 0.23.0 (#2962)
Bumps [golang.org/x/net](https://github.com/golang/net) from 0.21.0 to 0.23.0.
- [Commits](https://github.com/golang/net/compare/v0.21.0...v0.23.0)

---
updated-dependencies:
- dependency-name: golang.org/x/net
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-19 09:15:08 -04:00
Jonathan bcec15dc13
Externalize MPV command template (#2948)
* externalise MPVTemplate

* Remove unnecessary comment

---------

Co-authored-by: Deluan <deluan@navidrome.org>
2024-04-15 21:31:54 -04:00
dependabot[bot] cf6603e3ec
Bump react-icons from 5.0.1 to 5.1.0 in /ui (#2957)
Bumps [react-icons](https://github.com/react-icons/react-icons) from 5.0.1 to 5.1.0.
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v5.0.1...v5.1.0)

---
updated-dependencies:
- dependency-name: react-icons
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-15 14:35:00 -04:00
dependabot[bot] 88d6757121
Bump github.com/pelletier/go-toml/v2 from 2.2.0 to 2.2.1 (#2956)
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.2.0 to 2.2.1.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.2.0...v2.2.1)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-15 14:34:33 -04:00
Andrew Katsikas c2f932c21c
Fix jukebox mode under Windows (#2774)
* bug(core/playback/mpv): jukebox mode under windows - #2767

Use named pipe for socket path under windows during mpv playback, change function name, unexport function

Signed-off-by: apkatsikas <apkatsikas@gmail.com>

* bug(core/playback/mpv): jukebox mode under windows - #2767

Fix typo

Signed-off-by: apkatsikas <apkatsikas@gmail.com>

* bug(core/playback/mpv): jukebox mode under windows - navidrome#2767

Early return for Close on Windows

Signed-off-by: apkatsikas <apkatsikas@gmail.com>

* bug(core/playback/mpv): jukebox mode under windows - navidrome#2767

Update import and run prettier

Signed-off-by: apkatsikas <apkatsikas@gmail.com>

* bug(core/playback/mpv): jukebox mode under windows - navidrome#2767

Update function name

Signed-off-by: apkatsikas <apkatsikas@gmail.com>

* bug(core/playback/mpv): jukebox mode under windows - navidrome#2767

Create track_close files for both platforms and move MpvTrack Close into new file

Signed-off-by: apkatsikas <apkatsikas@gmail.com>

* bug(core/playback/mpv): jukebox mode under windows - navidrome#2767

Create SocketName function for both platforms, restore name of TempFileName

Signed-off-by: apkatsikas <apkatsikas@gmail.com>

* bug(core/playback/mpv): jukebox mode under windows - navidrome#2767

Add missing params to SocketName on windows

Signed-off-by: apkatsikas <apkatsikas@gmail.com>

* Unexport SocketName, use socketName in NewTrack

---------

Signed-off-by: apkatsikas <apkatsikas@gmail.com>
2024-04-14 13:50:37 -04:00
Deluan d968f7f530 Remove deprecation warning about `notify` 2024-04-13 15:27:54 -04:00
dependabot[bot] 5fc78f120c
Bump prettier from 3.2.2 to 3.2.5 in /ui (#2844)
Bumps [prettier](https://github.com/prettier/prettier) from 3.2.2 to 3.2.5.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.2.2...3.2.5)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-13 15:10:03 -04:00
dependabot[bot] 52dfa97262
Bump @testing-library/jest-dom from 6.2.0 to 6.4.2 in /ui (#2845)
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 6.2.0 to 6.4.2.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v6.2.0...v6.4.2)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-13 15:09:53 -04:00
dependabot[bot] c1eef058a4
Bump follow-redirects from 1.15.4 to 1.15.6 in /ui (#2911)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.4 to 1.15.6.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.4...v1.15.6)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-13 15:09:34 -04:00
Deluan 7f551a7932 Add make target to build docker image 2024-04-13 13:29:45 -04:00
oftenoccur bcb71b85c0
Fix some typos in comments (#2949)
Signed-off-by: oftenoccur <ezc5@sina.com>
2024-04-11 14:58:14 -04:00
Deluan 8720bd154f Ignore formatting diffs when checking for POEditor changes 2024-04-11 14:55:53 -04:00
Cyrille 699be19bb9
Fix a few mistakes in the French translation (#2872)
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
2024-04-10 19:37:08 -04:00
looklose 22cc9e0cd5
Fix function name in comment (#2947)
Signed-off-by: looklose <shishuaiqun@yeah.net>
2024-04-10 12:53:21 -04:00
dependabot[bot] 6e36abdd62 Bump github.com/go-chi/jwtauth/v5 from 5.3.0 to 5.3.1
Bumps [github.com/go-chi/jwtauth/v5](https://github.com/go-chi/jwtauth) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/go-chi/jwtauth/releases)
- [Commits](https://github.com/go-chi/jwtauth/compare/v5.3.0...v5.3.1)

---
updated-dependencies:
- dependency-name: github.com/go-chi/jwtauth/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-09 20:45:43 -04:00
dependabot[bot] e98c7374a9 Bump github.com/pelletier/go-toml/v2 from 2.1.1 to 2.2.0
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.1.1 to 2.2.0.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.1.1...v2.2.0)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-09 20:45:27 -04:00
Deluan Quintão de7f553526
Update Go to 1.22.2 and TagLib to 2.0.1 (#2946) 2024-04-09 19:00:38 -04:00
dependabot[bot] 9cc0cc2e93 Bump github.com/pressly/goose/v3 from 3.18.0 to 3.19.2
Bumps [github.com/pressly/goose/v3](https://github.com/pressly/goose) from 3.18.0 to 3.19.2.
- [Release notes](https://github.com/pressly/goose/releases)
- [Changelog](https://github.com/pressly/goose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/pressly/goose/compare/v3.18.0...v3.19.2)

---
updated-dependencies:
- dependency-name: github.com/pressly/goose/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 19:52:34 -04:00
dependabot[bot] 24298605d4 Bump github.com/onsi/ginkgo/v2 from 2.15.0 to 2.17.1
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.15.0 to 2.17.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.15.0...v2.17.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 19:46:24 -04:00
Deluan 4865d04ec6 Fix DiscTitle OpenSubsonic compatibility. Closes #2929 2024-04-08 19:05:36 -04:00
dependabot[bot] 81770351de Bump github.com/onsi/gomega from 1.31.1 to 1.32.0
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.31.1 to 1.32.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.31.1...v1.32.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 19:03:15 -04:00
dependabot[bot] b6bbba754a Bump golang.org/x/sync from 0.6.0 to 0.7.0
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.6.0 to 0.7.0.
- [Commits](https://github.com/golang/sync/compare/v0.6.0...v0.7.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 18:57:52 -04:00
deluan 4f6121fae1 Update translations 2024-04-03 07:31:54 -04:00
Kendall Garner f12dfb485a
Expose OpenSubsonic release date for album (#2906)
* [enhancement]: OS expose release date for album, make original optional

* not optional

* remove omitempty
2024-04-03 07:30:01 -04:00
Deluan e81bf5125f Bump actions versions 2024-04-02 19:37:59 -04:00
dependabot[bot] a47acb6674 Bump github.com/lestrrat-go/jwx/v2 from 2.0.20 to 2.0.21
Bumps [github.com/lestrrat-go/jwx/v2](https://github.com/lestrrat-go/jwx) from 2.0.20 to 2.0.21.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v2.0.20...v2.0.21)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx/v2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-26 11:54:28 -04:00
dependabot[bot] 4a15677474 Bump google.golang.org/protobuf from 1.32.0 to 1.33.0
Bumps google.golang.org/protobuf from 1.32.0 to 1.33.0.

---
updated-dependencies:
- dependency-name: google.golang.org/protobuf
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-03-26 11:53:51 -04:00
Deluan 859cdda0bd Bump Go dependencies 2024-03-03 21:30:28 -05:00
Deluan 87ecd118bb Bump goose to 3.18.0.
To fix the ambiguous import issue, I used:
go get -u google.golang.org/genproto/googleapis/rpc
2024-03-03 21:27:33 -05:00
Deluan 5abe156777 Logs don't panic when receiving a `nil` *time.Time 2024-02-18 13:06:01 -05:00
Deluan fa72aaa462 Move `TempFileName` to `utils` 2024-02-18 12:52:06 -05:00
Deluan 6eb13c9f79 Run Test job in ci-goreleaser container 2024-02-18 12:52:06 -05:00
Deluan b67d1c0830 Show taglib and ffmpeg versions in the log 2024-02-18 12:52:06 -05:00
Deluan effd588406 Stop using deprecated TagLib method `length` 2024-02-18 12:52:06 -05:00
Deluan 6f4c55dbde Use new ci-goreleaser (with TagLib 2) 2024-02-18 12:52:06 -05:00
Deluan 176329343a Send Subsonic formatted response on marshalling errors 2024-02-17 10:39:29 -05:00
Deluan 97c7e5daaf Use new `slices` package from Go standard lib 2024-02-16 22:00:44 -05:00
Deluan 166eb37787 Use Go builtin min/max func 2024-02-16 21:53:16 -05:00
Deluan f7a4387d0e Bump github.com/jellydator/ttlcache/v2 to v2.11.1 2024-02-16 21:42:22 -05:00
Deluan 71e5b271fb Bump github.com/xrash/smetrics version 2024-02-16 20:52:23 -05:00
Deluan d51148ea4c Bump github.com/go-chi/chi/v5 to v5.0.12 2024-02-16 20:51:30 -05:00
Deluan 7cb8cc115e Bump github.com/mattn/go-sqlite3 to v1.14.22 2024-02-16 20:50:45 -05:00
Deluan 69d91189c2 Upgrade ginkgo and gomega 2024-02-16 20:49:37 -05:00
Deluan 88063fc189 Upgrade ginkgo and gomega 2024-02-16 20:47:53 -05:00
Deluan 912e144b71 Bump github.com/google/uuid to 1.6.0 2024-02-16 20:46:41 -05:00
Deluan 87484fe7a9 Bump github.com/google/wire to 0.6.0 2024-02-16 20:45:11 -05:00
Deluan 58f64355c2 Bump golang.org/x/exp version 2024-02-16 20:43:12 -05:00
Deluan Quintão 7167e5ac87
Upgrade to Go 1.22 and Node v20 (#2861)
* Remove workaround for missing `context.WithoutCancel` in Go 1.20

* Upgrade to Go 1.22

* Upgrade GitHub Actions

* Upgrade Node to v20
2024-02-16 20:29:16 -05:00
Deluan d8e1748928 Return 500 in case of Subsonic response marshalling errors 2024-02-16 19:59:24 -05:00
Deluan 9a051967f6 Handle "Infinity" values for ReplayGain. Fix #2862 2024-02-16 18:44:58 -05:00
Deluan 0b2cf30096 Don't swallow marshalling errors in the Subsonic API 2024-02-16 18:43:36 -05:00
Deluan 6d253225de Use order/sort album/artist when sorting tracks in playlists. Fixes #2819 2024-02-15 21:52:00 -05:00
Caio Cotts bf2bcb1279
Fix null values in DB (#2840)
* Fix album image_files being null.

* Fix small nitpick.

* Use ExecContext instead of Exec.

* Change more columns to not null and set default values.

* Remove columns that don't need to be changed from migration.

* Fix typo.

* Remove unnecessary select statements.

* Remove duplicate code.

* Do not apply changes to radio table.

* Do not apply changes full_text columns and respective indexes.

* Fix musicbrainz columns.

* Rename migration.

* Make ExternalInfoUpdatedAt nullable

* Make Share's timestamps nullable

---------

Co-authored-by: Deluan Quintão <deluan@navidrome.org>
2024-02-07 20:45:08 -05:00
Deluan Quintão ac4ceab143
Update French translation (#2834)
Co-authored-by: deluan <deluan@users.noreply.github.com>
2024-02-05 20:10:21 -05:00
Deluan 6226741517 Create resources.FS only once 2024-02-03 12:05:19 -05:00
Deluan 79a4d8f6ad Simplify ShortDur code and tests 2024-02-02 21:07:27 -05:00
Deluan Quintão 61257f89d2
Update translations (#2832)
Co-authored-by: deluan <deluan@users.noreply.github.com>
2024-01-30 07:25:42 -05:00
Deluan 1f71e56741 Don't expose Last.fm API Key in the index.html 2024-01-29 21:42:27 -05:00
Kendall Garner 3a9b3452a2
Set rating value to 0 when value is null (#2824) 2024-01-29 06:26:15 -05:00
Deluan 5125558f52 Make Subsonic search query default to `""` if not present.
See https://github.com/orgs/music-assistant/discussions/414#discussioncomment-8265985
2024-01-27 20:00:02 -05:00
Deluan 5f9b6b632d Add a "upgrading schema" log message to the DB initialization when there are pending migrations. 2024-01-27 19:44:49 -05:00
Deluan fa7cc40d23 Add tests for `toSQL` 2024-01-27 12:16:38 -05:00
caiocotts 58218e6dc4 Fix fields not being sent on getPlaylist.view responses. 2024-01-26 12:41:55 -05:00
Deluan 67c82f524b "Fix" Reddit badge 2024-01-24 20:24:13 -05:00
Deluan fb7fd21984 Don't add empty TIPL roles 2024-01-24 19:22:25 -05:00
Deluan a6fc84a2e1 Parse the ID3v2.4 TIPL frame 2024-01-23 20:50:43 -05:00
Deluan 1e5e8be192 Import ID3 sort_* tags 2024-01-23 18:07:11 -05:00
Deluan fd61b29a84 Small readability improvement in MergeFS tests 2024-01-21 16:20:47 -05:00
Deluan 2b33ef72e3 Remove offset and limit from count queries. Fixes #2443 2024-01-20 22:02:05 -05:00
Deluan 2fb913f5c9 Add log message to try to capture error in #2735 2024-01-20 20:18:59 -05:00
Deluan 6c05493cda Improve some Jukebox error messages 2024-01-20 20:10:32 -05:00
Deluan 3ca4f44118 Simplify default middlewares setup 2024-01-20 19:17:21 -05:00
Deluan 34c29a156f Simplify RealIP middleware setup 2024-01-20 18:58:12 -05:00
dependabot[bot] b442736a0f
Bump connected-react-router from 6.9.1 to 6.9.3 in /ui (#2741)
Bumps [connected-react-router](https://github.com/supasate/connected-react-router) from 6.9.1 to 6.9.3.
- [Release notes](https://github.com/supasate/connected-react-router/releases)
- [Commits](https://github.com/supasate/connected-react-router/compare/v6.9.1...v6.9.3)

---
updated-dependencies:
- dependency-name: connected-react-router
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:42:43 -05:00
dependabot[bot] 90fccf00d1
Bump workbox-cli from 6.5.4 to 7.0.0 in /ui (#2737)
Bumps [workbox-cli](https://github.com/googlechrome/workbox) from 6.5.4 to 7.0.0.
- [Release notes](https://github.com/googlechrome/workbox/releases)
- [Commits](https://github.com/googlechrome/workbox/compare/v6.5.4...v7.0.0)

---
updated-dependencies:
- dependency-name: workbox-cli
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:38:44 -05:00
dependabot[bot] bcd4a52616
Bump golang.org/x/sync from 0.5.0 to 0.6.0 (#2779)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.5.0 to 0.6.0.
- [Commits](https://github.com/golang/sync/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:37:46 -05:00
dependabot[bot] 84cffa6b94
Bump github.com/prometheus/client_golang from 1.17.0 to 1.18.0 (#2759)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.17.0 to 1.18.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.17.0...v1.18.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:37:01 -05:00
dependabot[bot] a51b1b25d2
Bump uuid from 8.3.2 to 9.0.1 in /ui (#2740)
Bumps [uuid](https://github.com/uuidjs/uuid) from 8.3.2 to 9.0.1.
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v8.3.2...v9.0.1)

---
updated-dependencies:
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:36:14 -05:00
dependabot[bot] 9f317c054b
Bump @testing-library/user-event from 14.5.1 to 14.5.2 in /ui (#2757)
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 14.5.1 to 14.5.2.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v14.5.1...v14.5.2)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:35:20 -05:00
dependabot[bot] 5f8d01a207
Bump clsx from 2.0.0 to 2.1.0 in /ui (#2758)
Bumps [clsx](https://github.com/lukeed/clsx) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/lukeed/clsx/releases)
- [Commits](https://github.com/lukeed/clsx/compare/v2.0.0...v2.1.0)

---
updated-dependencies:
- dependency-name: clsx
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:35:08 -05:00
dependabot[bot] 8a648d717a
Bump github.com/go-chi/chi/v5 from 5.0.10 to 5.0.11 (#2742)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.10 to 5.0.11.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.10...v5.0.11)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:34:58 -05:00
dependabot[bot] a0dc2ee051
Bump github.com/pelletier/go-toml/v2 from 2.0.6 to 2.1.1 (#2760)
Bumps [github.com/pelletier/go-toml/v2](https://github.com/pelletier/go-toml) from 2.0.6 to 2.1.1.
- [Release notes](https://github.com/pelletier/go-toml/releases)
- [Changelog](https://github.com/pelletier/go-toml/blob/v2/.goreleaser.yaml)
- [Commits](https://github.com/pelletier/go-toml/compare/v2.0.6...v2.1.1)

---
updated-dependencies:
- dependency-name: github.com/pelletier/go-toml/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:32:53 -05:00
dependabot[bot] ffb4de1e27
Bump github.com/unrolled/secure from 1.13.0 to 1.14.0 (#2761)
Bumps [github.com/unrolled/secure](https://github.com/unrolled/secure) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/unrolled/secure/releases)
- [Commits](https://github.com/unrolled/secure/compare/v1.13.0...v1.14.0)

---
updated-dependencies:
- dependency-name: github.com/unrolled/secure
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:31:50 -05:00
dependabot[bot] e1fc7983a5
Bump golang.org/x/image from 0.14.0 to 0.15.0 (#2778)
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.14.0 to 0.15.0.
- [Commits](https://github.com/golang/image/compare/v0.14.0...v0.15.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:31:18 -05:00
dependabot[bot] 2a43f54eb1
Bump follow-redirects from 1.15.2 to 1.15.4 in /ui (#2786)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.2 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.2...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:30:55 -05:00
dependabot[bot] f654e92113
Bump github.com/lestrrat-go/jwx/v2 from 2.0.18 to 2.0.19 (#2792)
Bumps [github.com/lestrrat-go/jwx/v2](https://github.com/lestrrat-go/jwx) from 2.0.18 to 2.0.19.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v2.0.18...v2.0.19)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-20 18:29:46 -05:00
flyingOwl dfa453cc4a
Add (not)inplaylist operator to smart playlists (#1884)
Closes #1417 

A smart playlist can use the playlist id for filtering. This can be
used to create combined playlists or to filter multiple playlists.

To filter by a playlist id, a subquery is created that will match the
media ids with the playlists within the playlist_tracks table.

Signed-off-by: flyingOwl <ofenfisch@googlemail.com>
2024-01-20 18:22:17 -05:00
Johannes Engl 8f03454312
Make server unix socket file permission configurable via flag UnixSocketPerm (#2763)
* feat(any): Add flag unixsocketperm with default 0017 - #2625

Signed-off-by: johannesengl <hello@johannesengl.com>

* feat(server): Update unix socket file perm based on config - #2625

Signed-off-by: johannesengl <hello@johannesengl.com>

* Fix default value of socket.

* Refactor unix socket file creation.

* Remove misplaced comment

---------

Signed-off-by: johannesengl <hello@johannesengl.com>
Co-authored-by: Caio Cotts <caio@cotts.com.br>
Co-authored-by: Deluan <deluan@navidrome.org>
2024-01-20 14:50:30 -05:00
dependabot[bot] 8570773b90 Bump prettier from 3.1.1 to 3.2.2 in /ui
Bumps [prettier](https://github.com/prettier/prettier) from 3.1.1 to 3.2.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.1.1...3.2.2)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-18 09:23:10 -05:00
caiocotts 6cff91e17d Use the default import path for jest-dom. 2024-01-17 17:07:43 -05:00
dependabot[bot] d0df81a8df Bump @testing-library/jest-dom from 5.16.5 to 6.2.0 in /ui
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.16.5 to 6.2.0.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v5.16.5...v6.2.0)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-17 17:07:43 -05:00
dependabot[bot] 75f3ef64e2 Bump react-icons from 4.4.0 to 5.0.1 in /ui
Bumps [react-icons](https://github.com/react-icons/react-icons) from 4.4.0 to 5.0.1.
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v4.4.0...v5.0.1)

---
updated-dependencies:
- dependency-name: react-icons
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-17 16:10:00 -05:00
dependabot[bot] 170ac93926 Bump github.com/onsi/ginkgo/v2 from 2.13.2 to 2.14.0
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.13.2 to 2.14.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.2...v2.14.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-17 15:58:57 -05:00
Deluan 6f7b48202e Make the GetInstance concurrent test more readable 2023-12-28 16:50:07 -05:00
Deluan 6e2be7f95f Don't force a full scan after upgrading the lyrics 2023-12-28 04:55:45 -05:00
Deluan 0d8f8e3afd Optimize Singleton (sometimes a simple lock is a better solution) 2023-12-27 22:12:34 -05:00
Deluan e50382e3bf Fix ReplayGain values not being retrieved from DB 2023-12-27 21:14:54 -05:00
Kendall Garner 814161d78d
Add OS Lyrics extension (#2656)
* draft commit

* time to fight pipeline

* round 2 changes

* remove unnecessary line

* fight taglib. again

* make taglib work again???

* add id3 tags

* taglib 1.12 vs 1.13

* use int instead for windows

* store as json now

* add migration, more tests

* support repeated line, multiline

* fix ms and support .m, .mm, .mmm

* address some concerns, make cpp a bit safer

* separate responses from model

* remove [:]

* Add trace log

* Try to unblock pipeline

* Fix merge errors

* Fix SIGSEGV error (proper handling of empty frames)

* Add fallback artist/title to structured lyrics

* Rename conflicting named vars

* Fix tests

* Do we still need ffmpeg in the pipeline?

* Revert "Do we still need ffmpeg in the pipeline?"

Yes we do.

This reverts commit 87df7f6df7.

* Does this passes now, with a newer ffmpeg version?

* Revert "Does this passes now, with a newer ffmpeg version?"

No, it does not :(

This reverts commit 372eb4b0ae.

* My OCD made me do it :P

---------

Co-authored-by: Deluan Quintão <deluan@navidrome.org>
2023-12-27 20:20:29 -05:00
Deluan 130ab76c79 go mod tidy 2023-12-27 13:04:26 -05:00
Deluan a186a795f6 Omit empty Genre attributes 2023-12-27 12:44:25 -05:00
Deluan 798b03eabd Add "inspect" command to CLI 2023-12-27 12:41:28 -05:00
Deluan ea7ba22699 Discard duplicated tags 2023-12-26 19:35:14 -05:00
Andrew Katsikas b4815ecee5
Add TAK support (#2745)
* bug(consts/mime_types): tak-support - 2514

Add tak to mime_types audioFormats

Signed-off-by: apkatsikas <apkatsikas@gmail.com>

* bug(scanner): tak-support - 2514

Add tak test fixture file and add fixes for tag_scanner and walk_dir_tree tests

Signed-off-by: apkatsikas <apkatsikas@gmail.com>

* Remove comment

---------

Signed-off-by: apkatsikas <apkatsikas@gmail.com>
2023-12-26 18:39:15 -05:00
Deluan 51e07d4cb5 Add log.IsGreaterOrEqualTo, that take into consideration path-scoped log levels 2023-12-25 16:35:16 -05:00
Deluan 03119e5ccf Add more trace log to TagLib Wrapper 2023-12-23 14:10:38 -05:00
Deluan Quintão 15e1394fa3
Implement `originalReleaseDate` in OpenSubsonic responses. (#2733)
See https://github.com/opensubsonic/open-subsonic-api/pull/80
2023-12-22 21:03:55 -05:00
Deluan 3f349b1b58 Add todo as a reminder to replace min/max in Go 1.22 2023-12-21 19:19:46 -05:00
Deluan dfcc189cff Replace all `utils.Param*` with `req.Params` 2023-12-21 17:41:09 -05:00
Deluan 00597e01e9 Add `req.Params` to replace `utils.Param*` 2023-12-21 16:32:37 -05:00
Dany Marcoux 965fc9d9be
Remove beep and the files where it was imported (#2731)
Beep isn't needed anymore since we rely on MPV instead.

The changes to `go.mod` and `go.sum` were done with:
```
go get github.com/faiface/beep@none
go mod tidy
```

Signed-off-by: Dany Marcoux <git@dmarcoux.com>
2023-12-21 08:00:31 -05:00
Deluan Quintão 781ff40464
Bump Go version to 1.21.5 (#2729) 2023-12-20 20:02:40 -05:00
Deluan a6ed0442f2 Name `mapDates` return values 2023-12-20 16:29:39 -05:00
dependabot[bot] 515efe37f0 Bump @testing-library/user-event from 13.5.0 to 14.5.1 in /ui
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 13.5.0 to 14.5.1.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v13.5.0...v14.5.1)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-19 13:18:13 -05:00
dependabot[bot] 6c28c111bb Bump @adobe/css-tools from 4.3.1 to 4.3.2 in /ui
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.3.1 to 4.3.2.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-19 12:34:13 -05:00
dependabot[bot] 92a88ad4d9
Bump golang.org/x/crypto from 0.16.0 to 0.17.0 (#2722)
Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.16.0 to 0.17.0.
- [Commits](https://github.com/golang/crypto/compare/v0.16.0...v0.17.0)

---
updated-dependencies:
- dependency-name: golang.org/x/crypto
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-18 17:45:37 -05:00
dependabot[bot] 4ccc0a92bf
Bump jwt-decode from 3.1.2 to 4.0.0 in /ui (#2714)
* Bump jwt-decode from 3.1.2 to 4.0.0 in /ui

Bumps [jwt-decode](https://github.com/auth0/jwt-decode) from 3.1.2 to 4.0.0.
- [Release notes](https://github.com/auth0/jwt-decode/releases)
- [Changelog](https://github.com/auth0/jwt-decode/blob/main/CHANGELOG.md)
- [Commits](https://github.com/auth0/jwt-decode/compare/v3.1.2...v4.0.0)

---
updated-dependencies:
- dependency-name: jwt-decode
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Make jwt-decode a named import.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Caio Cotts <caio@cotts.com.br>
2023-12-18 17:28:42 -05:00
dependabot[bot] df3de047ca Bump clsx from 1.1.1 to 2.0.0 in /ui
Bumps [clsx](https://github.com/lukeed/clsx) from 1.1.1 to 2.0.0.
- [Release notes](https://github.com/lukeed/clsx/releases)
- [Commits](https://github.com/lukeed/clsx/compare/v1.1.1...v2.0.0)

---
updated-dependencies:
- dependency-name: clsx
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-18 15:15:00 -05:00
Caio Cotts 86757663d6 Reformat code with Prettier's new rules. 2023-12-18 15:12:24 -05:00
dependabot[bot] 735d670a5b Bump prettier from 2.8.2 to 3.1.1 in /ui
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.2 to 3.1.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.2...3.1.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-18 15:12:24 -05:00
dependabot[bot] 30179146c3 Bump deepmerge from 4.2.2 to 4.3.1 in /ui
Bumps [deepmerge](https://github.com/TehShrike/deepmerge) from 4.2.2 to 4.3.1.
- [Changelog](https://github.com/TehShrike/deepmerge/blob/master/changelog.md)
- [Commits](https://github.com/TehShrike/deepmerge/compare/v4.2.2...v4.3.1)

---
updated-dependencies:
- dependency-name: deepmerge
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-18 14:22:11 -05:00
dependabot[bot] 03a9f22ed9 Bump @material-ui/icons from 4.11.2 to 4.11.3 in /ui
Bumps [@material-ui/icons](https://github.com/mui-org/material-ui/tree/HEAD/packages/material-ui-icons) from 4.11.2 to 4.11.3.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Changelog](https://github.com/mui/material-ui/blob/v4.11.3/CHANGELOG.md)
- [Commits](https://github.com/mui-org/material-ui/commits/v4.11.3/packages/material-ui-icons)

---
updated-dependencies:
- dependency-name: "@material-ui/icons"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-18 14:17:25 -05:00
dependabot[bot] 39e92a1918 Bump github.com/mattn/go-sqlite3 from 1.14.18 to 1.14.19
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.18 to 1.14.19.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.18...v1.14.19)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-18 14:07:07 -05:00
Deluan 421ce91a9e Fix mpvipc dependency 2023-12-17 13:57:15 -05:00
Deluan 12aae5e951 Some cleanup in the jukebox code, specially log messages 2023-12-17 13:15:47 -05:00
Deluan 932152eb7e Change required fields in Subsonic Jukebox endpoint
See discussion here: https://gitlab.com/ultrasonic/ultrasonic/-/issues/1266#note_1621953651
2023-12-17 13:15:47 -05:00
Deluan 0e3175ea17 Better workaround for Go 1.20 missing context.WithoutCancel 2023-12-16 13:33:03 -05:00
Deluan d3f6b4692d Temporary fix for scan context cancellation for Go 1.20 2023-12-15 07:59:34 -05:00
Deluan 70effa09e8 Don't cancel Scan on context cancellation 2023-12-14 22:52:48 -05:00
Deluan 7ccf685973 Fix PreferSortTags 2023-12-14 21:45:47 -05:00
Deluan 2aef227572 Add context to SQL queries, enabling cancellation 2023-12-14 17:13:09 -05:00
Deluan d80e1a260b Fix possible authentication bypass 2023-12-13 19:32:05 -05:00
dependabot[bot] fd4605d7dc
Bump github.com/mattn/go-zglob from 0.0.3 to 0.0.4 (#2015)
Bumps [github.com/mattn/go-zglob](https://github.com/mattn/go-zglob) from 0.0.3 to 0.0.4.
- [Release notes](https://github.com/mattn/go-zglob/releases)
- [Commits](https://github.com/mattn/go-zglob/compare/v0.0.3...v0.0.4)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-zglob
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-13 17:27:42 -05:00
Deluan a6493c4c36 Bump github.com/google/uuid to v1.5.0 2023-12-13 16:47:05 -05:00
Kendall Garner 54597bd575
Allow reverse proxy auth for unix socket (#2701) 2023-12-12 06:06:27 -05:00
Deluan Quintão ab53313273
Add new PrefSortTags option (#2696) 2023-12-11 20:37:11 -05:00
Deluan d3669f46a9 go mod tidy 2023-12-11 19:03:27 -05:00
Deluan d89de9060a Bump Go dependencies 2023-12-11 17:25:14 -05:00
Deluan ac3668a33e Removed unused `diodes` package 2023-12-11 17:22:10 -05:00
dependabot[bot] 6d924ad742
Bump github.com/go-chi/jwtauth/v5 from 5.2.0 to 5.3.0 (#2699)
Bumps [github.com/go-chi/jwtauth/v5](https://github.com/go-chi/jwtauth) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/go-chi/jwtauth/releases)
- [Commits](https://github.com/go-chi/jwtauth/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: github.com/go-chi/jwtauth/v5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-11 13:41:03 -05:00
Deluan 78d557c185 Remove LastFM shared key 2023-12-10 21:11:40 -05:00
Deluan 546aa26a0a Removed duplicated code 2023-12-09 14:11:07 -05:00
dependabot[bot] fc677f7951
Bump github.com/lestrrat-go/jwx/v2 from 2.0.17 to 2.0.18 (#2684)
Bumps [github.com/lestrrat-go/jwx/v2](https://github.com/lestrrat-go/jwx) from 2.0.17 to 2.0.18.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v2.0.17...v2.0.18)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-12-09 14:04:56 -05:00
Deluan aed0309161 Return `AlbumID3` in `search3` results 2023-12-09 14:01:22 -05:00
Deluan 465cc091b0 Convert internal disc number representation to int 2023-12-09 13:53:38 -05:00
Deluan 2c9035fdd0 Add discTitles to OpenSubsonic responses 2023-12-09 13:53:38 -05:00
Deluan af7eead037 Add discs to album 2023-12-09 13:53:38 -05:00
Deluan Quintão 0ca0d5da22
Replace beego/orm with dbx (#2693)
* Start migration to dbx package

* Fix annotations and bookmarks bindings

* Fix tests

* Fix more tests

* Remove remaining references to beego/orm

* Add PostScanner/PostMapper interfaces

* Fix importing SmartPlaylists

* Renaming

* More renaming

* Fix artist DB mapping

* Fix playlist updates

* Remove bookmarks at the end of the test

* Remove remaining `orm` struct tags

* Fix user timestamps DB access

* Fix smart playlist evaluated_at DB access

* Fix search3
2023-12-09 13:52:17 -05:00
dependabot[bot] 7074455e0e Bump github.com/onsi/ginkgo/v2 from 2.13.1 to 2.13.2
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.13.1 to 2.13.2.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.13.1...v2.13.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-08 21:35:42 -05:00
caiocotts 2f2fbeb009 Fix ld warnings on taglib_wrapper. 2023-12-04 15:19:12 -05:00
Kendall Garner 742fd16a01
Parse more itunes keys, optimize taglib wrapper (#2680)
* parse more itunes keys

* Move special iTunes M4A logic to Go code

* Simplify ASF/WMA tags handling

* Simplify ASF/WMA tags handling even more, moving compilation logic to `metadata` normalizer

* Remove strdups from C++ code, `C.GoString` already duplicates the strings

* reduced set

* remove strdup

* Small nitpick

---------

Co-authored-by: Deluan <deluan@navidrome.org>
2023-12-03 14:19:16 -05:00
Deluan Quintão 7766ee069c
Return http form post extension (OpenSubsonic) (#2676) 2023-12-02 19:46:57 -05:00
Deluan 4cd7c7f39f Fix FileHaunter tests 2023-12-02 19:40:59 -05:00
Deluan 81daee3b9b Fix FileHaunter tests 2023-12-02 18:43:24 -05:00
Deluan 9b434d743f Ignore flaky FileHaunter tests 2023-12-02 18:32:48 -05:00
Deluan 4641dc0b2b Add ReplayGain to OpenSubsonic API Child response 2023-12-02 15:28:44 -05:00
Deluan 812dc2090f Add support for `timeOffset` in `/stream` endpoint 2023-12-02 13:10:36 -05:00
Deluan a9cf54afef Return genres in bookmark endpoints (OpenSubsonic) 2023-12-02 11:36:16 -05:00
Deluan 595186b1b2 Coalesce null annotation values, to better rank them against annotations with value 0 2023-12-02 11:35:54 -05:00
Deluan cdccdc56c9 Add more OpenSubsonic fields
- isCompilation
- sortName
2023-11-28 21:26:00 -05:00
Deluan f580c5b8bc Add more OpenSubsonic fields
- mediaType
- musicBrainzId (Child)
2023-11-28 21:12:28 -05:00
deluan f0e25c251d Update translations 2023-11-28 06:10:03 -05:00
Deluan abde399e7b Upgrade to Goose 3.15.1 2023-11-27 14:46:44 -05:00
Deluan 1b4483d32b Remove `tools.go` 2023-11-27 14:06:00 -05:00
Deluan f7fe8ba938 npx update-browserslist-db@latest 2023-11-27 13:56:16 -05:00
Deluan f543e7accc Fix getOpenSubsonicExtensions endpoint
Match the current doc: https://opensubsonic.netlify.app/docs/endpoints/getopensubsonicextensions/

openSubsonicExtensions must be an array, not a struct
2023-11-27 13:27:10 -05:00
Deluan Quintão 60a5fbe1fe
Optimize search3, by removing `OFFSET` when paginating (#2655)
* Optimize pagination, removing offset

* For search, don't add `where` clause for empty queries

* Revert "Replace `COUNT(DISTINCT primary_key)` with `COUNT(*)`"

Genres are required as part of the count queries, so filter by genres work

* Optimize search3 query, using order by id if it is a "" query.

Also fix the optimizePagination query logic

* Allow offset optimizer threshold to be configured
2023-11-27 13:06:23 -05:00
Deluan 28dc98dec4 Revert "Replace `COUNT(DISTINCT primary_key)` with `COUNT(*)`"
Genres are required as part of the count queries, so filter by genres work
2023-11-25 23:08:20 -05:00
Deluan 8c8e1ea701 Replace `COUNT(DISTINCT primary_key)` with `COUNT(*)` 2023-11-25 22:46:15 -05:00
Deluan b964018cd7 Show SQL errors in queryAll 2023-11-25 13:54:38 -05:00
Deluan 9aa7b80d0d Generalize BreakUp/RangByChunks functions 2023-11-25 12:13:36 -05:00
Deluan c3efc57259 Use TagLib 1.13.1 for snapshots/releases 2023-11-24 20:35:38 -05:00
Deluan 27a92b05e7 Fixed deprecated GoReleaser options 2023-11-24 18:08:34 -05:00
Deluan 21f1354cd1 Revert "Bump golang.org/x/exp, change slices.SortFunc function call"
This reverts commit 474f32f1
2023-11-24 17:57:22 -05:00
Deluan 069da5d91c Bump Go to 1.21.4 2023-11-24 17:51:36 -05:00
Deluan 69d2ced852 Bump Go dependencies 2023-11-24 16:45:52 -05:00
Deluan 17ac8d25cb Bump dependencies 2023-11-24 16:40:20 -05:00
Deluan 474f32f1b8 Bump golang.org/x/exp, change slices.SortFunc function call 2023-11-24 16:38:47 -05:00
Deluan ecadcfb403 Make `ParamInt` generic (any int type) 2023-11-23 13:40:06 -05:00
Caio Cotts f69c27d146 Return genres in search3 endpoint. 2023-11-21 21:34:03 -05:00
Caio Cotts bb7186ce2f Fix marshaling for genres. 2023-11-21 21:34:03 -05:00
dependabot[bot] 5d1493e845 Bump @adobe/css-tools from 4.0.1 to 4.3.1 in /ui
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.0.1 to 4.3.1.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-21 08:47:14 -05:00
Deluan d0fe406800 Fix Go 1.20 build 2023-11-21 08:32:22 -05:00
Deluan c8fbf6b60e Bump dependencies 2023-11-21 08:22:02 -05:00
deluan e5bc3ca200 Update translations 2023-11-21 08:15:32 -05:00
tarokeitaro 6d88dd2c66 Add Indonesian Language 2023-11-21 08:06:51 -05:00
caiocotts eebfbc5381 Revert walk_dir_tree.go back to using the os package. 2023-11-21 07:17:22 -05:00
Deluan a5dfd2d4a1 Format subsonic response snapshots 2023-11-18 14:43:40 -05:00
Drew Weymouth 7773522803
Expose OpenSubsonic fields Genres, MusicBrainzId, Bpm, Comment (#2597)
* add Genres to subsonic responses

* add genres in GetAlbum response

* add musicBrainzId

* add Bpm and Comment OpenSubsonic fields

* remove omitempty on OpenSubsonic fields

* add custom JSON marshalers to ensure genres attribute is non-nil

* regenerate snapshots to capture now-mandatory fields
2023-11-18 14:40:00 -05:00
Deluan 53607fe114 Publish all new images to Docker Registry 2023-11-16 23:21:20 -05:00
Caio Cotts fee0f40a52 Bump dependencies 2023-11-16 20:38:45 -05:00
Caio Cotts 9d2aaff8cb Bump golang.org/x/tools from 0.13.0 to 0.15.0 2023-11-16 20:19:29 -05:00
Caio Cotts 2ff4023cce Bump golang.org/x/image from 0.12.0 to 0.14.0 2023-11-16 20:16:25 -05:00
Kendall Garner 79870b1090
Do not empty old artist metadata (#2423) 2023-11-16 19:20:37 -05:00
Kendall Garner 7a858a2db3
Fix external link for artist page if LastFM is missinb but Musicbrainz is not (#2533)
* fix mbz link if lastfm does not exist

* use lastfmUrl field

* fix artist info undefined
2023-11-16 19:07:52 -05:00
dependabot[bot] 9cefaf66a4 Bump github.com/onsi/gomega from 1.29.0 to 1.30.0
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.29.0 to 1.30.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.29.0...v1.30.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-16 18:45:21 -05:00
Kendall Garner 3debd31b12
Add more replaygain tests, fix wma (#2356)
* add more replaygain tests, fix wma

* Convert individual specs to a table spec

* Fix pipeline, by commenting incompatible tests

---------

Co-authored-by: Deluan <deluan@navidrome.org>
2023-11-14 20:25:18 -05:00
Deluan Quintão 24d9fb5b48
Update translations (#2409)
Co-authored-by: deluan <deluan@users.noreply.github.com>
2023-11-14 19:21:26 -05:00
certuna 40841ab917
Small date mapping fix (#2584)
* Update mapping.go

fallback in the case there's no Date tagged but Original Date or Release Date are tagged

* Add tests

---------

Co-authored-by: Deluan <deluan@navidrome.org>
2023-11-11 17:13:07 -05:00
certuna bae5fc946b
Fix hardcoded IPv4 literals (#2602)
* Update server_test.go

no hardcoded IPv4 literals

* Update package.json

no hardcoded IPv4 literals
2023-11-11 16:46:53 -05:00
Deluan e055826068 Fix devcontainer for Go 1.21 2023-11-09 18:23:44 -05:00
dependabot[bot] 54bde266b4 Bump github.com/mattn/go-sqlite3 from 1.14.16 to 1.14.18
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.16 to 1.14.18.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.16...v1.14.18)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-07 18:24:08 -05:00
dependabot[bot] 3a7376901b Bump golang.org/x/sync from 0.3.0 to 0.5.0
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.3.0 to 0.5.0.
- [Commits](https://github.com/golang/sync/compare/v0.3.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-07 18:19:32 -05:00
dependabot[bot] de3d870100 Bump github.com/spf13/cobra from 1.7.0 to 1.8.0
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.7.0 to 1.8.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.7.0...v1.8.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-07 18:08:13 -05:00
certuna 03175e1a9d
Use file creation date for Date Added/CreatedAt (#2553)
* Update mapping.go

CreatedAt = BirthTime

* Update metadata.go

Add BirthTime() function

* Update spread_fs.go

Replacing djherbis/atime package with djherbis/times, as times includes the functionality of atime

* Update go.mod

remove djherbis/atime, add djherbis/times

* Update mapping.go

time package not used anymore

* Update go.sum

removed djherbis/atime, added djherbis/times

* Update spread_fs.go

revert to previous, cannot get rid of /atime after all since it's a dependency of /fscache

* Update go.mod

djherbis/times 1.6.0 now released

* Update go.sum

new sums

* Update metadata.go

Inverted if statement, more readable

* Update go.mod

format fix

* Update go.sum

format fix

* Update go.sum

format fix

* Update go.sum

format fix

* Update metadata.go

variable name times -> fileProperties
check for errors

* Update metadata.go

reverse order of error check

* Update metadata.go

typo

* Update metadata.go

https://github.com/navidrome/navidrome/pull/2553#issuecomment-1787967615
2023-11-01 16:41:07 -04:00
Sam Watson 26472f46fe
POST endpoint for importing m3u playlists - #2078 (#2273)
* wip: API endpoint for creating playlists from m3u files

* wip: get user id from context

* temporarily disable failing test

* custom logic for playlist route to accomodate m3u content type

* incorporate playlist parsing into existing logic in core

* re-enable test

* fix locally failing test

* Address requested changes.

* Improve ImportFile tests.

* Remove ownerID as a parameter of ImportM3U.

* Write tests for ImportM3U.

* Separate ImportM3U test into two.

* Test OwnerID and playlist Name.

---------

Co-authored-by: Sam Watson <SwatsonCodes@users.noreply.github.com>
Co-authored-by: caiocotts <caio@cotts.com.br>
2023-11-01 14:59:47 -04:00
dependabot[bot] 6bca7531aa Bump @babel/traverse from 7.19.3 to 7.23.2 in /ui
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.19.3 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-31 17:46:10 -04:00
dependabot[bot] 68d1d5c99f Bump github.com/lestrrat-go/jwx/v2 from 2.0.12 to 2.0.16
Bumps [github.com/lestrrat-go/jwx/v2](https://github.com/lestrrat-go/jwx) from 2.0.12 to 2.0.16.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v2.0.12...v2.0.16)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-31 17:27:45 -04:00
dependabot[bot] db6c46091e Bump github.com/beego/beego/v2 from 2.0.7 to 2.1.3
Bumps [github.com/beego/beego/v2](https://github.com/beego/beego) from 2.0.7 to 2.1.3.
- [Release notes](https://github.com/beego/beego/releases)
- [Changelog](https://github.com/beego/beego/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/beego/beego/compare/v2.0.7...v2.1.3)

---
updated-dependencies:
- dependency-name: github.com/beego/beego/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-31 17:24:55 -04:00
dependabot[bot] 4cd916bb78 Bump react-router-dom from 5.3.0 to 5.3.4 in /ui
Bumps [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) from 5.3.0 to 5.3.4.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/v5.3.4/packages/react-router-dom)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-31 16:55:59 -04:00
dependabot[bot] c40e83efab Bump github.com/go-chi/chi/v5 from 5.0.8 to 5.0.10
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.8 to 5.0.10.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.8...v5.0.10)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-31 16:44:35 -04:00
Stephan Wahlen 9094f41f25
Improve TopSongs findMatchingTrack by de-prioritizing compilations (#2532)
in reference to https://github.com/navidrome/navidrome/issues/1701
2023-10-31 16:00:53 -04:00
dependabot[bot] 9ff95b6ced Bump github.com/onsi/gomega from 1.27.10 to 1.29.0
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.10 to 1.29.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.10...v1.29.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-31 15:48:16 -04:00
Kendall Garner 77ace8570c
Coalesce genre to null for smart playlist (#2573) 2023-10-31 13:22:57 -04:00
Matthias Schmidt 59f0c487e7
Jukebox cleanup (#2554)
* Fixing typo FFmpegPath -> MPVPath

* Fixing panic by applying afontenot patch

* Using mpv audio-device flag and naming for config and playback
2023-10-17 18:12:48 -04:00
Deluan 2cd4358172 Make Jukebox available to Subsonic clients 2023-09-14 20:15:39 -04:00
dependabot[bot] 248bf232ff
Bump github.com/lestrrat-go/jwx/v2 from 2.0.11 to 2.0.12 (#2480)
Bumps [github.com/lestrrat-go/jwx/v2](https://github.com/lestrrat-go/jwx) from 2.0.11 to 2.0.12.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v2.0.11...v2.0.12)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-10 18:06:51 -04:00
dependabot[bot] b5664ab905
Bump github.com/onsi/ginkgo/v2 from 2.11.0 to 2.12.0 (#2497)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.11.0 to 2.12.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.11.0...v2.12.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-10 12:28:43 -04:00
Lukas H ac7f94e620
Fix text being unreadable with Ligera theme (#2517)
Change MuiFormGroup color to make it readable.
2023-09-10 12:28:21 -04:00
dependabot[bot] d45f9f172d
Bump github.com/google/uuid from 1.3.0 to 1.3.1 (#2489)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.3.0 to 1.3.1.
- [Release notes](https://github.com/google/uuid/releases)
- [Changelog](https://github.com/google/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/google/uuid/compare/v1.3.0...v1.3.1)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-10 12:05:58 -04:00
dependabot[bot] 250107d668
Bump golang.org/x/image from 0.9.0 to 0.12.0 (#2507)
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.9.0 to 0.12.0.
- [Commits](https://github.com/golang/image/compare/v0.9.0...v0.12.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-10 12:05:28 -04:00
BoniK 64b14db55a
Add Korean Language (#2463) 2023-09-10 11:52:18 -04:00
dependabot[bot] 73d1851c0d
Bump golang.org/x/tools from 0.9.1 to 0.13.0 (#2516)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.9.1 to 0.13.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.9.1...v0.13.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-10 11:48:33 -04:00
Matthias Schmidt 1b16e1140f
Jukebox mode (#2289)
* Adding cache directory to ignore-list

* Adding jukebox-related config options

* Adding DevEnableJukebox config option pls. dummy server

* Adding types and routers

* Now without panic

* First draft on parsing the action

* Some cleanups

* Adding playback server

* Verify audio device configuration

* Adding debug-build target to have full symbol support

* Adding beep sound library pls some example code. Not working yet

* Play a fixed mp3 on any interface access for testing purposes

* Put action code into separate file, adding stringer, more debug output, prepare structs, validation

* Put action parameter parser code where it belongs

* Have a single Action transporting all information

* User fmt.Errorf for error-generation

* Adding wide playback interface

* Use action map for parsing, stringer instead switch stmt.

* Use but only one switch case and direct dispatch, refactoring

* Add error handling and pushing to client

* send decent errormessage, no internal server error

* Adding playback devices slice and load it from config

* Combine config-verification and structure init

* Return user-specific device

* Separate playback server from device

* Use dataStore to retrieve mediafile by id

* WIP: Playlist and start/stop handling. Doing start/stop the hard way as of now

* WIP: set, start and stop work on one single song. More to come

* Dont need to wait for the end

* Merge jukebox_action.go into jukebox.go

* Remove getParameterAsInt64(). Use existing requiredParamInt() instead

* Dont need to call newFailure() explicitly

* Remove int64, use int instead.

* Add and set action now accept multiple ids

* Kickout copy of childFromMediaFile(). It is not needed here.

* Refactoring devices and playbackServer

* Turn (internal) playback.DeviceStatus into subsonic JukeboxStatus when rendering output. Indexes int64 -> int

* Now we have a position and playing status

* Switching gain to float32, xs:float is defined as 32 bit. Fixing nasty copy/pointer bug

* Now with volume control

* Start working the queue

* Remove user from device interface

* Rename function GetDevice -> GetDeviceForUser to make intention clearer

* Have a nice stringer for the queue

* User Prepared boolean for now to allow pause/unpause

* Skipping works, but without offsets

* Make ChildFromMediaFile public to be used in jukebox get() implementation

* Return position in seconds and implement offset-skip in seconds

* Default offset to 0

* Adding a simple setGain implementation

* Prepare for transcoding AAC

* WIP: transcode to WAV to use beeps wav decoder. Not done yet.

* WIP: out of sheer desparation: convert to MP3 (which works) rather than WAV to troubleshoot issue.

* Use FLAC as intermediate format to play Apple AAC

* A bit of cleanup

* Catching the end-of-stream event for further reactions

* Have a trackSwitching goroutine waiting on channel when track ends

* Move decoder code into own file. Restructure code a bit

* Now with going on to play the next song in the playlist

* Adding shuffle feature

* Implementing remove action

* Cleanup code

* Remove templates for ffmpeg mp3 generation. Not needed anymore.

* Adding some documentation

* Check whether offset into track is in range. Fixing potential remove track bug. Documentation

* Make golangci-lint happy: handling return values

* Adding test suite and example dummy for playback package

* Adding some basic queue tests

* Only use Jukebox.Enabled config option

* Adding stream closing handling

* Pass context.Context to all PlaybackDevice methods

* Remove unneeded function

* Correct spelling

* Reduce visibility of ChildFromMediaFile

* Decomplicate action-parsing

* Adding simple tempfile-based AAC->FLAC transcoding. No parallel reading and writing yet.

* Try to optimize pipe-writing, tempfile-handling and reading. Not done yet.

* Do a synchronous copy of the tempfile. Racecondition detected

* More debugging statements and fixing the play/pause bug. More work needed

* Start the trackSwitcher() with each device once. Return JSON position even if its 0. More debug-output

* Moving all track-handling code into own module

* Fix typo. Do not pass ctx around when not applicable

* WIP: More refactoring, debugging output

* Fix nil pointer

* Repairing MP3 playback by pinning indirect dependencies: hajimehoshi/go-mp3 and hajimehoshi/oto

* Do not forget to cleanup after a skip action

* Make resync with master easy

* Adding missing mocks

* Adding missing error-handling found by linter

* Updating github.com/hajimehoshi/oto

* Removing duplicate function

* Move BEEP-related code into own package

* Juggle beep-related code around as preparation for interface access

* More refactoring for interface separation

* Gather CloseDevice() behind Track interface.

* Adding skeleton, draft audio-interface using mpv.io

* Adding majority of interface commands using messages to mpv socket.

* Adding end-of-stream handling

* MPV: start/stop are working

* postition is given in float in mpv

* Unify Close() and CloseDevice(). Using temp filename for controlling socket

* Wait until control-socket shows up. Cleanup socket in Close()

* Use canceable command. Rename to Executor

* Skipping tracks works now

* Now with actually setting the position

* Fix regain

* Add missing error-handling found by linter

* Adding retry mode on time-pos property getter

* Remove unneeded code on queue

* Putting build-tag beep onto beep files

* Remove deprecated call to rand.Seed()

"As of Go 1.20 there is no reason to call Seed with a random value. Programs that call Seed with a known value to get a specific sequence of results should use New(NewSource(seed)) to obtain a local random generator."

* Using int32 to conform to Subsonic API spec

* Fix merge error

* Minor style changes

* Get username from context

---------

Co-authored-by: Deluan <deluan@navidrome.org>
2023-09-10 11:25:22 -04:00
Deluan Quintão f941347cf1
Upgrade to Go 1.21 (#2475)
* Upgrade to Go 1.21

* Remove 'replacements' from goreleaser config
2023-08-09 11:39:49 -04:00
dependabot[bot] 1b5cefdada
Bump github.com/onsi/gomega from 1.27.8 to 1.27.9 (#2450)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.8 to 1.27.9.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.8...v1.27.9)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-22 19:21:39 -04:00
dependabot[bot] 4cf25fc611
Bump github.com/microcosm-cc/bluemonday from 1.0.24 to 1.0.25 (#2449)
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.24 to 1.0.25.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.24...v1.0.25)

---
updated-dependencies:
- dependency-name: github.com/microcosm-cc/bluemonday
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-22 19:11:15 -04:00
dependabot[bot] 14ba83ea1b
Bump github.com/go-chi/chi/v5 from 5.0.8 to 5.0.10 (#2444)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.8 to 5.0.10.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.8...v5.0.10)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-21 19:12:56 -04:00
dependabot[bot] 08f3fd1343
Bump github.com/pressly/goose/v3 from 3.13.1 to 3.13.4 (#2442)
Bumps [github.com/pressly/goose/v3](https://github.com/pressly/goose) from 3.13.1 to 3.13.4.
- [Release notes](https://github.com/pressly/goose/releases)
- [Changelog](https://github.com/pressly/goose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/pressly/goose/compare/v3.13.1...v3.13.4)

---
updated-dependencies:
- dependency-name: github.com/pressly/goose/v3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Deluan Quintão <deluan@navidrome.org>
2023-07-21 19:11:59 -04:00
dependabot[bot] 3d66f58725
Bump tough-cookie from 4.1.2 to 4.1.3 in /ui (#2441)
Bumps [tough-cookie](https://github.com/salesforce/tough-cookie) from 4.1.2 to 4.1.3.
- [Release notes](https://github.com/salesforce/tough-cookie/releases)
- [Changelog](https://github.com/salesforce/tough-cookie/blob/master/CHANGELOG.md)
- [Commits](https://github.com/salesforce/tough-cookie/compare/v4.1.2...v4.1.3)

---
updated-dependencies:
- dependency-name: tough-cookie
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-21 19:04:32 -04:00
dependabot[bot] 5b1ba3df05
Bump word-wrap from 1.2.3 to 1.2.4 in /ui (#2446)
Bumps [word-wrap](https://github.com/jonschlinkert/word-wrap) from 1.2.3 to 1.2.4.
- [Release notes](https://github.com/jonschlinkert/word-wrap/releases)
- [Commits](https://github.com/jonschlinkert/word-wrap/compare/1.2.3...1.2.4)

---
updated-dependencies:
- dependency-name: word-wrap
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-21 19:02:10 -04:00
Deluan a002830775 Fix EnableMediaFileCoverArt option default value 2023-07-10 18:07:58 -04:00
dependabot[bot] 7b600bed05
Bump golang.org/x/tools from 0.10.0 to 0.11.0 (#2432)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.10.0 to 0.11.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.10.0...v0.11.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-05 16:38:32 -04:00
dependabot[bot] 7d0a1916d8
Bump golang.org/x/image from 0.8.0 to 0.9.0 (#2430)
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.8.0 to 0.9.0.
- [Commits](https://github.com/golang/image/compare/v0.8.0...v0.9.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-05 16:34:58 -04:00
dependabot[bot] c7fe311c7f
Bump github.com/go-chi/jwtauth/v5 from 5.1.0 to 5.1.1 (#2427)
Bumps [github.com/go-chi/jwtauth/v5](https://github.com/go-chi/jwtauth) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/go-chi/jwtauth/releases)
- [Commits](https://github.com/go-chi/jwtauth/compare/v5.1.0...v5.1.1)

---
updated-dependencies:
- dependency-name: github.com/go-chi/jwtauth/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-05 16:31:38 -04:00
dependabot[bot] 4520a34648
Bump github.com/pressly/goose/v3 from 3.11.2 to 3.13.1 (#2428)
Bumps [github.com/pressly/goose/v3](https://github.com/pressly/goose) from 3.11.2 to 3.13.1.
- [Release notes](https://github.com/pressly/goose/releases)
- [Changelog](https://github.com/pressly/goose/blob/master/CHANGELOG.md)
- [Commits](https://github.com/pressly/goose/compare/v3.11.2...v3.13.1)

---
updated-dependencies:
- dependency-name: github.com/pressly/goose/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-07-05 16:31:22 -04:00
BenzLeung 3e14c3c4f8
Add support for lyrics tag `unsynced lyrics` (#2391)
* Add support for lyrics tag `unsynced_lyrics`

* Update metadata.go

* Update metadata.go

resolve lint issue

* format the code with `goimports`

format the code with `goimports`
2023-06-20 09:32:49 -04:00
dependabot[bot] 1e891d6b07
Bump github.com/prometheus/client_golang from 1.15.1 to 1.16.0 (#2408)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.15.1 to 1.16.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.15.1...v1.16.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-19 14:23:36 -04:00
dependabot[bot] caf9b22d35
Bump golang.org/x/image from 0.7.0 to 0.8.0 (#2407)
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.7.0 to 0.8.0.
- [Commits](https://github.com/golang/image/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-06-19 14:23:01 -04:00
Deluan Quintão 4f8742bcd1
Update translations (#2329)
Co-authored-by: deluan <deluan@users.noreply.github.com>
2023-06-19 12:27:56 -04:00
Deluan 26aa0f4fff Fix typo 2023-06-19 11:19:43 -04:00
Deluan 4898f31f6d Add `format` target to Makefile 2023-06-19 11:15:51 -04:00
Philipp Wolfer 9da013f339
Submit duration to ListenBrainz (#2405) 2023-06-17 12:27:00 -04:00
Deluan 5af67c78af Upgrade Go dependencies 2023-06-16 22:34:02 -04:00
Philipp Wolfer c8608956be
Fix listenbrainz submission and clarify MusicBrainz recording ID field (#2279)
* Fix MB recording ID parameter name for ListenBrainz submission

This follows the ListenBrainz API documentation.

Fixes #1657

* Rename MediaFile.MbzTrackID to MbzRecordingID

This better reflects the actual data. That the MusicBrainz
recording ID is stored in file metadata as musicbrainz_trackid
is a historical artifact.

* Rename database column mbz_track_id to mbz_recording_id
2023-06-16 18:00:01 -04:00
Deluan 36eda871f6 Fix locale-dependent tests. Closes #2402 2023-06-16 16:38:03 -04:00
David Casado 7c92a73208
Ignore playlists starting with a dot - #2367 (#2390) 2023-06-16 15:55:17 -04:00
Deluan f5d97823e8 Fix original date (TDOR) mapping for ffmpeg extractor 2023-06-06 19:13:45 -04:00
Deluan d6083dab6e Re-apply "Refactor walkDirTree to use fs.FS" but remove context cancelation logic.
This reverts commit 6b3b4d83ff.
2023-06-04 15:06:19 -04:00
Deluan 6b3b4d83ff Revert "Refactor walkDirTree to use fs.FS"
This reverts commit 3853c3318f.
2023-06-04 14:13:33 -04:00
Deluan 3853c3318f Refactor walkDirTree to use fs.FS 2023-06-03 22:25:19 -04:00
tomleb 257ccc5f43
Allow configuring cache folder (#2357)
* Set all clients to dev_download for make get-music

* Use multiple TranscodingCache instances in tests

This fixes flaky tests. The issue is that the TranscodingCache object
was being reused in tests from media_stream_Internal_test.go and
media_stream_test.go. If tests from the former was run first, the cache
would be filled up, so that when running tests from the latter, the `NON
seekable` test would fail.

* Allow configuring cache folder

This commit introduces a new configuration option to configure the cache
folder. This allows the cache to be in a separate folder such as
/var/cache/navidrome on Linux distributions.

* Fix tests

* Removed unused test setup code

---------

Co-authored-by: Deluan <deluan@deluan.com>
Co-authored-by: Deluan <deluan@navidrome.org>
2023-06-02 17:14:11 -04:00
Deluan cec5fb0d6c Fix lint errors 2023-06-02 16:44:12 -04:00
Deluan 3fc4313e89 Move string slice functions to slice package as generic functions 2023-06-02 16:30:20 -04:00
Deluan c4c99b7f75 Make `GroupAlbumReleases` false by default 2023-05-31 15:40:20 -04:00
Deluan a984bbbc7a Make SmartPlaylists to always be seen as changed for Subsonic clients. 2023-05-25 09:14:00 -04:00
Deluan ba067667c9 Fix date formatting to use UTC 2023-05-24 14:47:51 -04:00
Deluan e38a690632 Order albums by full original date (this time is for real). Fixes #1452 2023-05-23 09:51:02 -04:00
Deluan 7d0656f44a Order albums by full original date. Fixes #1452 2023-05-22 23:50:16 -04:00
Deluan 11f33ff8b6 Update dependencies 2023-05-22 17:26:49 -04:00
Deluan 611363fca7 Add missing translation 2023-05-20 17:35:09 -04:00
Deluan 85d43d2366 Add tests to date roll-ups 2023-05-19 21:22:23 -04:00
Deluan 8faaa3cf91 Use table specs in getDate tests 2023-05-19 17:03:14 -04:00
Deluan 20462c52a5 Restore album "year" translation string 2023-05-19 15:29:30 -04:00
certuna 52b77e4194
Support for Original Date, Release Date & splitting/grouping of album editions (#2162)
* Update AlbumGridView.js

* Update AlbumDetails.js

* Update AlbumDetails.js

* Create DoubleRangeField.js

* Update and rename DoubleRangeField.js to RangeFieldDouble.js

* Update RangeFieldDouble.js

* Update AlbumGridView.js

* Update AlbumDetails.js

* Update RangeFieldDouble.js

* Update index.js

* Update RangeFieldDouble.js

* Update RangeFieldDouble.js

* Update RangeFieldDouble.js

* Update RangeFieldDouble.js

* Update RangeFieldDouble.js

* Update AlbumDetails.js

* Update RangeFieldDouble.js

* Update AlbumDetails.js

* Update RangeFieldDouble.js

* Update AlbumDetails.js

* Update RangeFieldDouble.js

* Update RangeFieldDouble.js

* Update AlbumDetails.js

* Update AlbumDetails.js

* Update RangeFieldDouble.js

* Update RangeFieldDouble.js

* Update AlbumDetails.js

* Update RangeFieldDouble.js

* Update AlbumDetails.js

* Update en.json

* Update en.json

* Update AlbumDetails.js

* Update RangeFieldDouble.js

* Update AlbumGridView.js

* Update AlbumDetails.js

* Update AlbumSongs.js

* Update ContextMenus.js

* Update SongDatagrid.js

* Update AlbumSongs.js

* Update SongDatagrid.js

* Update SongDatagrid.js

* Update SongDatagrid.js

* Update AlbumSongs.js

* Update SongList.js

* Update playlist_track_repository.go

* Update 20230113000000_release_year.go

* Update PlayButton.js

* Update mediafile_repository.go

* Update album.go

* Update playlist_track_repository.go

* Update playlist_track_repository.go

* Update SongDatagrid.js

* Update 20230113000000_release_year.go

* Update SongDatagrid.js

* Update AlbumSongs.js

* Update SongDatagrid.js

* Update SongDatagrid.js

* Update SongDatagrid.js

* Update SongDatagrid.js

* Update AlbumDetails.js

* Update AlbumSongs.js

* Update AlbumSongs.js

* Update RangeFieldDouble.js

* Update SongDatagrid.js

* Update 20230113000000_release_year.go

* Update 20230113000000_release_year.go

* Update 20230113000000_release_year.go

* Update 20230113000000_release_year.go

* Update AlbumSongs.js

* Update AlbumSongs.js

* Update mapping.go

* Update RangeFieldDouble.js

* Update AlbumGridView.js

* Update AlbumSongs.js

* Update en.json

* Update SongDatagrid.js

* Update SongDatagrid.js

* Update metadata.go

* Update mapping.go

* Update AlbumDetails.js

* Update AlbumGridView.js

* Update RangeFieldDouble.js

* Update mapping.go

* Update metadata.go

* Update mapping.go

* Update AlbumDetails.js

* Update 20230113000000_release_year.go

* Update AlbumDetails.js

* Update en.json

* Update configuration.go

* Update mapping.go

* Update configuration.go

* Update mediafile.go

* Update metadata.go

* Update RangeFieldDouble.js

* Update 20230113000000_release_year.go

* Update configuration.go

* Update mapping.go

* Update mediafile.go

* Update mapping.go

* Update RangeFieldDouble.js

* Update RangeFieldDouble.js

* Update RangeFieldDouble.js

* Update RangeFieldDouble.js

* Update RangeFieldDouble.js

* Update 20230113000000_release_year.go

* Update AlbumDetails.js

* Update RangeFieldDouble.js

* Update mapping.go

* Update metadata.go

* Update album.go

* Update mediafile.go

* Update mediafile.go

* Update album.go

* Update fields.go

* Update mediafile_repository.go

* Update playlist_track_repository.go

* Update AlbumSongs.js

* Update SongDatagrid.js

* Update PlayButton.js

* Update SongList.js

* Update ContextMenus.js

* Update SongDatagrid.js

* Update metadata.go

* Update ArtistShow.js

* Update mapping.go

* Update configuration.go

* Update mapping.go

* Update metadata.go

* Update metadata.go

* Update mapping.go

* Update metadata.go

* Update metadata.go

* Update mapping.go

* Update 20230113000000_release_year.go

* Update 20230113000000_release_year.go

* Update mapping.go

* Update metadata.go

* Update metadata.go

* Update album.go

* Update mediafile.go

* Update AlbumDetails.js

* Update AlbumSongs.js

* Update album.go

* Update mediafile.go

* Update metadata.go

* Update mediafile.go

* Update 20230113000000_release_year.go

* Update 20230113000000_release_year.go

* Update album.go

* Update mediafile.go

* Update RangeFieldDouble.js

* Update AlbumDetails.js

* Update AlbumGridView.js

* Update en.json

* Update AlbumGridView.js

* Update RangeFieldDouble.js

* Update and rename 20230113000000_release_year.go to 20230113000000_release_date.go

* Update album.go

* Update mediafile.go

* Update fields.go

* Update playlist_track_repository.go

* Update mediafile_repository.go

* Update mapping.go

* Update metadata.go

* Update mapping.go

* Update SongDatagrid.js

* Update RangeFieldDouble.js

* Update index.js

* Update ContextMenus.js

* Update PlayButton.js

* Create FormatDate.js

* Update SongList.js

* Update AlbumDetails.js

* Update AlbumSongs.js

* Update AlbumSongs.js

* Update en.json

* Update AlbumDetails.js

* Update album.go

fixed conflict I think?

* Update mediafile.go

fixed conflict

* Format with goimports

* Update SongDatagrid.js

only show Cat # in desktop view

* Update metadata_internal_test.go

* Update metadata_test.go

* Delete test.mp3

* Add files via upload

mp3 test file with Date, Original Date and Release Date

* Update metadata_test.go

* Update metadata_test.go

* Update metadata_test.go

* Update metadata_test.go

* Update taglib_test.go

* Delete test.mp3

* Add files via upload

file with replaygain & dates

* Update AlbumGridView.js

* Update AlbumDetails.js

* Update AlbumSongs.js

* Update ContextMenus.js

* Update FormatDate.js

* Update PlayButton.js

* Update RangeFieldDouble.js

* Update SongDatagrid.js

* Update AlbumSongs.js

* Update SongDatagrid.js

* Update AlbumSongs.js

* Fix formatting

* Update mapping.go

* Update AlbumSongs.js

* Update SongDatagrid.js

* Update SongDatagrid.js

prettier

* Create RangeDoubleField.js

rename of RangeFieldDouble.js

* Update AlbumGridView.js

RangeFieldDouble -> RangeDoubleField

* Update mediafile.go

AllOrNothing() -> allOrNothing()

* Update metadata_internal_test.go

getYear -> getDate

* Update AlbumDetails.js

wrote suggested changes

* Update en.json

Editions -> Releases & fixed the field name

* Update configuration.go

Rename Editions -> Releases

* Update 20230113000000_release_date.go

Editions -> Releases

* Update album.go

Editions -> Releases

* Update mediafile.go

Editions -> Releases

* Update AlbumDetails.js

Editions -> Releases

* Update AlbumSongs.js

Editions -> Releases

* Update RangeDoubleField.js

Editions -> Releases

* Update SongDatagrid.js

Editions -> Releases

* Update index.js

FormatFullDate and RangeDoubleField

* Rename FormatDate.js to FormatFullDate.js

* Delete RangeFieldDouble.js

* Update mediafile.go

AllOrNothing -> allOrNothing

* Update mapping.go

Editions -> Releases

* Update AlbumDetails.js

prettier

* Update SongDatagrid.js

showReleaseRow -> showReleaseDivider

* Update AlbumSongs.js

showReleaseRow -> showReleaseDivider for clarity

* Update and rename 20230113000000_release_date.go to 20230515184510_add_release_date.go

- rename the migration file
- fixed the import to goose/v3
- additional db fields for original date & year

* Update 20230515184510_add_release_date.go

* Update fields.go

* Update album.go

* Update mediafile.go

* Update mapping.go

* Update AlbumDetails.js

* Update en.json

* Update AlbumDetails.js

* Update AlbumDetails.js

now hopefully prettier

* Update mapping.go

---------

Co-authored-by: Deluan <deluan@navidrome.org>
2023-05-19 15:27:47 -04:00
Deluan 010ba0d15c Use table specs in ReplayGain tests.
Also use test.mp3 file from Release Date PR, trying to fix a conflict.
2023-05-19 14:49:15 -04:00
Zane van Iperen 9b7fac5147
Update default transcoding commands (#2325)
Changes the default transcoding commands to only use the first audio
stream, instead of the first arbitrary stream.

Co-authored-by: Deluan Quintão <deluan@navidrome.org>
2023-05-19 10:49:29 -04:00
Deluan be12c12b28 Remove unused Badge component from ActivityPanel icon 2023-05-17 16:29:19 -04:00
Kendall Garner a19a643c65
Manually add replaygain tags for m4a (#2346)
* manually add replaygain tags for m4a

* Add replaygain tests for m4a, mp4, ogg

* add new valye for bitrate
2023-05-17 16:00:16 -04:00
Deluan f9b060af18 Removed onBackdropClick deprecated property 2023-05-17 15:48:22 -04:00
Deluan a3d78e95f2 Fix Monokai theme. Closes #2353 2023-05-17 15:36:30 -04:00
Deluan d85b06332c Fix build 2023-05-17 13:49:45 -04:00
Deluan bfa10cab62 Upgrade to Node v18 2023-05-17 13:41:36 -04:00
Deluan 08fcb430e6 Upgrade React-Admin to 3.19.12 2023-05-17 13:18:58 -04:00
Deluan 5d02df62d0 Fix eslint error 2023-05-17 11:57:43 -04:00
Deluan c3a2e084b3 Update caniuse-lite 2023-05-17 11:54:22 -04:00
Deluan 4296741ec0 Simplify EventStream handling 2023-05-17 11:53:09 -04:00
Deluan 6bee4ed147 Sanitize filenames inside zip files. Fixes #1763 2023-05-16 18:34:15 -04:00
Deluan e62c3edc1c Revert: Change fix formatting command 2023-05-16 12:34:09 -04:00
Deluan 0a08d0af3b Change fix formatting command 2023-05-16 12:31:09 -04:00
Deluan ad513354b9 Disable POEditor import job in forks 2023-05-16 10:33:06 -04:00
dependabot[bot] a70b81f931
Bump github.com/onsi/ginkgo/v2 from 2.9.4 to 2.9.5 (#2352)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.9.4 to 2.9.5.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.9.4...v2.9.5)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-15 15:12:31 -04:00
dependabot[bot] 0d920c7832
Bump github.com/prometheus/client_golang from 1.14.0 to 1.15.1 (#2342)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.14.0 to 1.15.1.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.14.0...v1.15.1)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-13 14:34:11 -04:00
dependabot[bot] 957a73e052
Bump github.com/mileusna/useragent from 1.2.1 to 1.3.2 (#2319)
Bumps [github.com/mileusna/useragent](https://github.com/mileusna/useragent) from 1.2.1 to 1.3.2.
- [Release notes](https://github.com/mileusna/useragent/releases)
- [Commits](https://github.com/mileusna/useragent/compare/v1.2.1...v1.3.2)

---
updated-dependencies:
- dependency-name: github.com/mileusna/useragent
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-13 14:32:01 -04:00
dependabot[bot] abc418eaa2
Bump github.com/onsi/ginkgo/v2 from 2.9.2 to 2.9.4 (#2343)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.9.2 to 2.9.4.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.9.2...v2.9.4)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-13 14:28:27 -04:00
dependabot[bot] 1128322011
Bump golang.org/x/tools from 0.8.0 to 0.9.1 (#2350)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.8.0 to 0.9.1.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.8.0...v0.9.1)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-13 14:28:05 -04:00
dependabot[bot] 2e479defd5
Bump github.com/go-chi/httprate from 0.7.1 to 0.7.4 (#2320)
Bumps [github.com/go-chi/httprate](https://github.com/go-chi/httprate) from 0.7.1 to 0.7.4.
- [Release notes](https://github.com/go-chi/httprate/releases)
- [Commits](https://github.com/go-chi/httprate/compare/v0.7.1...v0.7.4)

---
updated-dependencies:
- dependency-name: github.com/go-chi/httprate
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-13 14:24:37 -04:00
dependabot[bot] 8311a7f215
Bump golang.org/x/sync from 0.1.0 to 0.2.0 (#2344)
Bumps [golang.org/x/sync](https://github.com/golang/sync) from 0.1.0 to 0.2.0.
- [Commits](https://github.com/golang/sync/compare/v0.1.0...v0.2.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-13 14:23:40 -04:00
dependabot[bot] 6ec8f78076
Bump github.com/pressly/goose/v3 from 3.10.0 to 3.11.2 (#2341)
Bumps [github.com/pressly/goose/v3](https://github.com/pressly/goose) from 3.10.0 to 3.11.2.
- [Release notes](https://github.com/pressly/goose/releases)
- [Changelog](https://github.com/pressly/goose/blob/master/.goreleaser.yml)
- [Commits](https://github.com/pressly/goose/compare/v3.10.0...v3.11.2)

---
updated-dependencies:
- dependency-name: github.com/pressly/goose/v3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-05-13 14:23:17 -04:00
Logan Marchione 3e879d2a8c
Add K8s manifest (#2330)
* Add K8s manifest

* Update README.md
2023-04-29 16:14:44 -04:00
Jeff Henson 6d3d005fca
Allow the setrlimit syscall - #1961 (#2333)
This appears to be used by newer go versions and navidrome fails to
start unless it's allowed.

Signed-off-by: Jeff Henson <jeff@henson.io>
2023-04-27 21:30:43 -04:00
Deluan c12510d6e2 Update README 2023-04-11 14:00:44 -04:00
Deluan 0bd73bd3f4 Better GH Action names 2023-04-11 09:16:25 -04:00
Deluan 8c120ee3c9 Better GH Action names 2023-04-11 09:15:08 -04:00
Deluan 9590b3c25d Use the highest resolution artist image from Spotify 2023-04-10 15:34:22 -04:00
Deluan 4887c33053 Bump golang.org/x packages 2023-04-10 14:07:12 -04:00
Subhajit Ghosh da21acba92
Give page the right lang attribute (#2299)
* Fixed issue no #2174

Signed-off-by: Subhajit Ghosh <subhajitstd07@gmail.com>

* Fixed issue no #2174

---------

Signed-off-by: Subhajit Ghosh <subhajitstd07@gmail.com>
2023-04-08 13:39:59 -04:00
Deluan Quintão 9154e44eb4
Add initial support for OpenSubsonic. (#2302) 2023-04-08 13:25:37 -04:00
Deluan Quintão 2e01063429
Update translations (#2198)
Co-authored-by: deluan <deluan@users.noreply.github.com>
2023-04-06 22:09:49 -04:00
Deluan 597e5abed6 Fix push develop to Docker Hub 2023-04-06 20:11:35 -04:00
Deluan Quintão 92994efe48
Publish docker images to ghcr.io (#2298)
* Publish all images (including PRs) to GHCR, only releases and `develop` to Docker Hub
2023-04-06 19:53:31 -04:00
Deluan 9628b1389d Add help msg for JS formatting errors 2023-04-06 11:45:32 -04:00
Deluan 347424009d Show Player name, not client, in mobile view. Fix #1659. 2023-04-05 22:48:33 -04:00
Deluan ecac74c2bd Fix getSongsByGenre pagination. Fix #1640 2023-04-05 22:39:32 -04:00
Deluan ddfde7bfc8 Run lint on latest Go 1.20.x 2023-04-04 19:13:24 -04:00
Deluan 96c50d369a Upgrade to Go 1.20.3 and GoRelease 1.16.1 2023-04-04 19:10:03 -04:00
Deluan 310c816cdd Use Go 1.20 for local cross-compilation 2023-04-04 15:33:42 -04:00
Deluan bd402fb2a8 Fix IntelliJ warning 2023-04-04 13:01:32 -04:00
dependabot[bot] 8bb141b730
Bump github.com/spf13/cobra from 1.6.1 to 1.7.0 (#2293)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.6.1 to 1.7.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.6.1...v1.7.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-04 11:04:18 -04:00
Deluan f25b91b4d8 Remove any previous UNIX socket file 2023-04-04 11:03:37 -04:00
dependabot[bot] f959701d9d
Bump github.com/onsi/gomega from 1.27.5 to 1.27.6 (#2292)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.5 to 1.27.6.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.5...v1.27.6)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-04 10:55:36 -04:00
Deluan 61dd8d55ca Fix data race in scanner 2023-04-04 10:51:43 -04:00
Deluan bbb9461000 Increase max Server-Sent Events' ID 2023-04-04 10:46:57 -04:00
Deluan 95016f687e Fix SQL migrations 2023-04-04 10:45:55 -04:00
Deluan c3cc7dee01 Enable SQL migrations 2023-04-04 10:30:28 -04:00
Deluan 7847f19c9d Upgrade goose 2023-04-04 10:05:31 -04:00
dependabot[bot] 7a0df4429e
Bump github.com/onsi/gomega from 1.27.5 to 1.27.6 (#2288)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.5 to 1.27.6.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.5...v1.27.6)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-04-03 21:01:39 -04:00
Deluan 6a8d2dc87d Only use valid images for artist.* artwork 2023-04-03 18:07:15 -04:00
Deluan de816e8e5d Fix lint error 2023-04-03 11:15:46 -04:00
Deluan b22d0366d5 Use channels for EventStream instead of diodes 2023-04-03 10:51:24 -04:00
Deluan fea2de8f90 Add Galician translation. 2023-04-02 18:58:44 -04:00
Deluan d6dd0aaae7 Close SSE connection on write error 2023-04-02 18:40:58 -04:00
Fadeeeeeeee 458017b112
Update Chinese translations (#2260)
* Update Chinese translations

* Update Chinese translations

* Update Chinese translations
2023-04-02 18:40:48 -04:00
Deluan e6bfa2bb0b Convert our usage of go-diodes into a simplified, generic version 2023-04-01 21:53:45 -04:00
Deluan 1c7fb74a1d Fix writeEvents race condition.
This required removing the compress middleware from the /events route.
2023-04-01 20:54:15 -04:00
Deluan 83ae2ba3e6 Fix race condition 2023-04-01 18:40:37 -04:00
Joakim Repomaa 2ccc5bc941
Implement artist art priority (#2266)
* implement artist art priority

* add tests
2023-03-30 18:28:05 -04:00
Deluan 406554f1c4 Remove some tools from dependencies, reducing the modules dependencies 2023-03-30 15:33:47 -04:00
Deluan e89cdf6199 Fix flaky tests 2023-03-30 09:25:18 -04:00
Deluan cf804a52ef Add support for listening on Unix socket.
For that to work, specify the config option `Address` with `unix:/path/to/socket/file`.

Closes #1477
2023-03-29 16:05:59 -04:00
Deluan 628fd69d3d Fix race condition 2023-03-29 15:17:34 -04:00
Deluan 1d00d1e986 Fix `writeEvent` function.
It would not send anything if the `ResponseWriter` was not a `http.Flusher`, and it was leaking channels with `time.After`
2023-03-29 15:04:40 -04:00
Deluan 607c4067b8 Show translation changes on pipeline 2023-03-29 13:03:37 -04:00
Deluan e3079d81ea More tests 2023-03-27 20:36:23 -04:00
Deluan 3bedd89c17 Bump dependencies 2023-03-27 14:48:20 -04:00
dependabot[bot] 57829bfa4c
Bump github.com/lestrrat-go/jwx/v2 from 2.0.8 to 2.0.9 (#2282)
Bumps [github.com/lestrrat-go/jwx/v2](https://github.com/lestrrat-go/jwx) from 2.0.8 to 2.0.9.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v2.0.8...v2.0.9)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-27 14:47:32 -04:00
Deluan b998c05ca0 Some refactorings 2023-03-26 21:28:37 -04:00
Deluan 05d381c26f Add more middleware tests 2023-03-26 21:28:36 -04:00
zayedalsaidi 59a9c056b4
Add Arabic translation (#2277) 2023-03-26 19:56:59 -04:00
Deluan 0de81b8352 Bump caniuse-lite 2023-03-26 19:38:09 -04:00
Deluan 91785ecf36 Add tests for core.Archiver 2023-03-26 19:34:12 -04:00
Deluan 65eeb5ec1a Add tests for serverAddressMiddleware 2023-03-26 13:29:57 -04:00
Julien Voisin 17e0cd5504
Shuffle the tests, just in case (#2272) 2023-03-22 20:12:12 -04:00
Deluan 3a6d2dcd49 More log redaction 2023-03-21 11:16:00 -04:00
Deluan 183b462fed Fix zip comments in Share downloads. 2023-03-21 10:34:04 -04:00
Deluan 16fc4eb792 Fix missing extensions in Share downloads.
See https://github.com/navidrome/navidrome/pull/2246#issuecomment-1476996397
2023-03-21 10:31:00 -04:00
dependabot[bot] 6fee744d99
Bump github.com/onsi/gomega from 1.27.3 to 1.27.4 (#2268)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.27.3 to 1.27.4.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.27.3...v1.27.4)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-20 14:15:32 -04:00
dependabot[bot] 74d5c7bc82
Bump github.com/golangci/golangci-lint from 1.51.2 to 1.52.0 (#2270)
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.51.2 to 1.52.0.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.51.2...v1.52.0)

---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-20 14:15:18 -04:00
dependabot[bot] 880fc9e195
Bump github.com/Masterminds/squirrel from 1.5.3 to 1.5.4 (#2269)
Bumps [github.com/Masterminds/squirrel](https://github.com/Masterminds/squirrel) from 1.5.3 to 1.5.4.
- [Release notes](https://github.com/Masterminds/squirrel/releases)
- [Commits](https://github.com/Masterminds/squirrel/compare/v1.5.3...v1.5.4)

---
updated-dependencies:
- dependency-name: github.com/Masterminds/squirrel
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-20 14:15:01 -04:00
Xidorn Quan 1430aa108d
Update play_date on scrobble only when newer - #2262 (#2263)
* fix(persistence): Update play_date on scrobble only when newer - #2262

Signed-off-by: Xidorn Quan <me@upsuper.org>

* expand iff

---------

Signed-off-by: Xidorn Quan <me@upsuper.org>
2023-03-18 18:28:01 -04:00
Deluan 673880d661 Add option to load TLS cert/key, and use HTTPS 2023-03-17 16:32:13 -04:00
Deluan 7ea111322b Don't pump the volume up to 100% if it is not in a mobile device. Fix #2255
This detection method is not bullet-proof, but should work for now.

Ref: https://stackoverflow.com/a/3540295
2023-03-16 17:25:07 -04:00
Deluan 377e7ebd52 Disable share downloading when `EnableDownloads` is false.
Fixes https://github.com/navidrome/navidrome/pull/2246#issuecomment-1472341635
2023-03-16 13:11:26 -04:00
Deluan 23c483da10 Only freezes issues/prs after 120 days 2023-03-15 17:53:54 -04:00
Deluan c380139606 Fix lint 2023-03-15 13:10:14 -04:00
Deluan 63fbccf5a9 Enable memory profiling 2023-03-15 12:43:25 -04:00
Deluan 1f6ec1d9f5 Add pprof endpoint, disabled by default 2023-03-15 10:56:16 -04:00
dependabot[bot] cad8156353
Bump webpack from 5.74.0 to 5.76.1 in /ui (#2256)
Bumps [webpack](https://github.com/webpack/webpack) from 5.74.0 to 5.76.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.74.0...v5.76.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-15 09:13:22 -04:00
Deluan Quintão f7d4fcdcc1
Convert all Subsonic API ints to int32 as per specification (#2252)
* Fix Genre

* Fix ArtistID3

* Fix AlbumID3

* Fix Child

* Fix NowPlayingEntry

* Fix Playlist

* Fix Share

* Fix User

* Fix Artist

* Fix Directory

* Fix Error
2023-03-14 09:48:52 -04:00
Deluan Quintão 002cb4ed71
Update README.md 2023-03-13 19:34:47 -04:00
Deluan Quintão e13eaebbde
Update README.md 2023-03-13 19:32:13 -04:00
dependabot[bot] 539c0faedb
Bump github.com/onsi/ginkgo/v2 from 2.9.0 to 2.9.1 (#2251)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.9.0 to 2.9.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.9.0...v2.9.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-13 14:42:40 -04:00
Moink 4ccb6ccb09
Update Chinese translations (#2250) 2023-03-12 20:24:31 -04:00
Deluan ec0eb2866b Hide Love button on Artist Page when EnableFavourites=false. Fix #2245 2023-03-10 23:34:02 -05:00
Deluan b520d8827a Add download button in the SharePlayer 2023-03-10 23:33:29 -05:00
Deluan a7d3e6e1f1 Add option to allow share to be downloaded 2023-03-10 23:33:29 -05:00
Deluan a22eef39f7 Add share download endpoint 2023-03-10 23:33:29 -05:00
Torsten Curdt 50d9838652
Add docker compose examples, with traefik or caddy and without, fixes #476 (#2240)
* add docker compose examples, with traefik or caddy and without, fixes #476

* ignore the docker-compose in root, but not the one in contrib
2023-03-10 18:57:09 -05:00
Deluan 016454c217 Bump golangci-lint version 2023-03-10 17:46:05 -05:00
Deluan 41a5db72e7 Update more dependencies 2023-03-10 17:31:13 -05:00
Deluan 6e6ec58429 Update sanitize and golang.org/x dependencies 2023-03-10 17:21:08 -05:00
Deluan c88e1baa7c Make playlist tracks match case-insensitive. Fix #1720 2023-03-10 12:29:38 -05:00
Deluan e16e3d2e7b Fix pipeline. 2023-03-09 22:25:56 -05:00
Deluan 339a6239fd Ignore Recycle Bins in Windows. Fix #1074 2023-03-09 22:14:58 -05:00
Deluan 47f15ccbc3 Make AlbumArtists clickable in AlbumSongs view. Fixes #1627 2023-03-09 18:04:07 -05:00
Deluan 9667f3cd48 Add file path to toggleable columns in SongList view. Fix #1719 2023-03-09 17:47:20 -05:00
Deluan 5773fa0349 Fix discussions links 2023-03-08 14:14:42 -05:00
Deluan 527c378c41 Add feature request link to About dialog 2023-03-08 12:41:51 -05:00
Deluan caa0788853 Fine tune issue templates 2023-03-08 12:27:28 -05:00
Deluan 40b14e6d81 Add log-output to lock-threads bot 2023-03-06 20:12:46 -05:00
Deluan becd50eb68 Remove debug-only option from stale bot 2023-03-06 20:08:02 -05:00
Deluan 15b5aa9143 Add stale/lock-threads bot 2023-03-06 20:01:42 -05:00
Deluan 7987d982cf Fix pipeline's lint error message 2023-03-06 19:38:20 -05:00
Deluan 1dd074bbb4 Add new issue templates 2023-03-06 17:15:36 -05:00
Deluan 7eac9d2bbe Bump dependencies 2023-03-05 21:09:45 -05:00
dependabot[bot] 362d8c50fe
Bump github.com/onsi/gomega from 1.26.0 to 1.27.1 (#2204)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.26.0 to 1.27.1.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.26.0...v1.27.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-05 20:25:16 -05:00
dependabot[bot] 01c604ba7b
Bump github.com/stretchr/testify from 1.8.1 to 1.8.2 (#2216)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.1 to 1.8.2.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.1...v1.8.2)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-05 20:23:36 -05:00
dependabot[bot] 2c129a2890
Bump golang.org/x/image from 0.0.0-20191009234506-e7c1f5e7dbb8 to 0.5.0 (#2217)
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.0.0-20191009234506-e7c1f5e7dbb8 to 0.5.0.
- [Release notes](https://github.com/golang/image/releases)
- [Commits](https://github.com/golang/image/commits/v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-05 20:23:04 -05:00
Deluan 5fc4076aec Fix translation key 2023-02-16 21:05:11 -05:00
Deluan d303ad2676 Bump dependencies 2023-02-15 22:46:56 -05:00
Deluan c4a68c8a0a Fix build pipeline 2023-02-15 22:27:16 -05:00
Deluan ad9ce98cc2 Use GoLang 1.20.1 in pipeline 2023-02-15 22:21:50 -05:00
Deluan a134b1b608 Use sync/atomic package, now that we are at Go 1.19 2023-02-15 21:21:59 -05:00
Deluan 6dce4b2478 Remove custom atomic.Bool, we are now at Go 1.19 2023-02-15 21:18:24 -05:00
Deluan 10108c63c9 Allow BaseURL to contain full server url, including scheme and host. Fix #2183 2023-02-15 21:13:38 -05:00
Deluan aac6e2cb07 Add path to cookies. Fix #1580 2023-02-15 20:23:32 -05:00
Deluan 0ffdb2eee0 Bump minimum Go version to 1.19 2023-02-15 20:20:08 -05:00
Kendall Garner 8b93962fad
Limit share size while handling theme properly (#2171)
* limit player to 768 px

Signed-off-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>

* fix size limitation

---------

Signed-off-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>
2023-02-13 20:00:39 -05:00
Kendall Garner b129cae0d8
Only create context if gain mode active (#2173) 2023-02-13 19:57:23 -05:00
Deluan 2400e4f60d Fix DB migration. Fix #2168 2023-02-12 14:58:33 -05:00
Deluan Quintão 3cd934abd7
Update translations (#2159)
Co-authored-by: deluan <deluan@users.noreply.github.com>
2023-02-11 20:25:01 -05:00
Deluan 727632b616 Refactor play tracking 2023-02-11 18:52:28 -05:00
Kendall Garner 9e268678f2
Limit Share player to 768 px (#2164)
Signed-off-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>
2023-02-11 12:38:35 -05:00
RTapeLoadingError bb29ad3b12
Update Spanish translation (#2165)
Updated some empty fields.
2023-02-11 12:33:59 -05:00
Deluan b68ed2e4f9 Fix album's image_files 2023-02-09 18:29:08 -05:00
Deluan 0c3ac906b8 Enable ReplayGain by default and always import RG tags 2023-02-09 17:45:38 -05:00
Deluan b0e58cb885 Use Navidrome's own public images endpoint for `getAlbumInfo`'s imageURLs 2023-02-08 20:03:31 -05:00
Deluan 806713719f Add lastUpdated to `coverArt` ids. Helps with invalidating art cache client-side. 2023-02-08 20:03:31 -05:00
Deluan a3b8682d44 Fix polling of buffered scrobbles 2023-02-07 19:18:26 -05:00
Deluan 0bbb54934b Use Go 1.20 in pipeline, drop support for 1.18 2023-02-07 14:28:02 -05:00
Deluan 759ff844e2 Make ffmpeg path configurable, also finds it automatically in current folder. Fixes #1932 2023-02-07 13:46:09 -05:00
Deluan b8c5e49dd3 Close stream when downloading files, fix fd leak 2023-02-07 09:58:50 -05:00
Deluan 05c6cdea1a Don't cancel transcoding session if context is canceled 2023-02-07 09:58:50 -05:00
Daniel Hammer fc8462dc8a
"Spell-Jacking" mitigation ~ prevent sensitive data leak from spell checker. (#2091)
@see https://www.otto-js.com/news/article/chrome-and-edge-enhanced-spellcheck-features-expose-pii-even-your-passwords

Co-authored-by: Daniel Hammer <daniel.hammer+oss@gmail.com>
2023-02-06 16:29:28 -05:00
Deluan 9d459fbd0a Abort start-up if config file is invalid 2023-02-06 13:00:07 -05:00
Deluan 9b2dd1bb06 Fix playlist delete and reorder actions 2023-02-06 10:41:33 -05:00
Deluan bfaf4a3388 Add logs to cache hunter 2023-02-06 10:41:33 -05:00
Deluan a7f15facf9 Bump github.com/golangci/golangci-lint to 1.51.1 2023-02-06 10:41:33 -05:00
Deluan ee8f6447eb Add option to disable Cache Warmer. Related to #2142 2023-02-06 10:41:33 -05:00
Deluan dad4949a6d Refactor Subsonic search to make it a bit more readable 2023-02-05 00:58:34 -05:00
Deluan 3ce3185118 Don't retrieve Various Artists and Unknown Artist info from Last.fm 2023-02-04 21:18:51 -05:00
Deluan a50d9c8b67 Use the latest sanitize, to fix some diacritics 2023-02-04 19:09:14 -05:00
Kendall Garner f8dfb3ad86
Clearer lyrics in Nord theme (#2146) 2023-02-04 13:02:15 -05:00
Deluan 255f8e4a76 Update react-player, fix #2117 2023-02-04 12:49:47 -05:00
Deluan eba70ab826 Change throttling log messages 2023-02-04 12:37:47 -05:00
Deluan ee6b10db72 Replace custom code with `errgroup` 2023-02-04 12:37:47 -05:00
Deluan 797cc87141 Enqueue external metadata refreshes 2023-02-04 12:37:47 -05:00
dependabot[bot] bfbe980637
Bump http-cache-semantics from 4.1.0 to 4.1.1 in /ui (#2139)
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/commits)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-03 16:33:50 -05:00
Deluan d9d0a97674 Better log message 2023-02-03 11:35:10 -05:00
Deluan c031167bb1 Don't retrieve all artist external metadata if we just want artist images 2023-02-03 11:06:53 -05:00
Deluan 4a25e6d3d8 Fix Mapped Similar Artists log 2023-02-03 09:57:29 -05:00
Deluan ad2ad514b3 Add dev option to increase external metadata cache expiration. More logs 2023-02-02 16:55:12 -05:00
Deluan 588ee94f7c Discard request for image canceled by the client before any further processing 2023-02-02 14:55:07 -05:00
Deluan 3c5032a3e8 Add migration to rebuild albums paths 2023-02-02 14:42:01 -05:00
Deluan bcab3cc0f9 Add throttling to /share/img endpoint.
See: https://github.com/navidrome/navidrome/issues/2130#issuecomment-1414152343
2023-02-02 13:59:04 -05:00
Deluan 9b81aa4403 Fix artwork resolution when paths contains `:`. Fix #2137 2023-02-02 12:18:55 -05:00
Deluan f904784e67 Bump dependencies 2023-02-02 11:20:52 -05:00
Deluan 0ce750d469 Update golangci-lint and fix lint errors 2023-02-02 11:10:28 -05:00
Deluan cf04db7a98 Don't try to connect to external services if artist is Unknown 2023-02-02 10:57:37 -05:00
Deluan f4b50c493c When retrieving images from external sources, avoid calling it again if data is already cached locally.
Relates to https://github.com/navidrome/navidrome/issues/2130#issuecomment-1412742918
2023-02-02 10:38:17 -05:00
Deluan 4a7e86e989 Fix file descriptor leaking. 2023-02-02 10:36:49 -05:00
vlfldr a1a5b2fc30
Fix invisible checkboxes in Gruvbox theme (#2135)
* Added Gruvbox Dark color theme

* Correct formatting by running prettier

* Fixed invisible checkboxes and tweaked colors in Gruvbox theme
2023-02-01 13:33:55 -05:00
Deluan f00e6117ff Invalidate artist cache (by changing cache key format) 2023-02-01 10:34:55 -05:00
Deluan d8e794317f Return 404 when artwork is not available in `/share/img` endpoint 2023-02-01 10:34:02 -05:00
Deluan 128b626ec9 Add option to change max playlists shown in UI's sidebar, `MaxSidebarPlaylists`. Fix #2077 2023-02-01 10:25:25 -05:00
Deluan d683297fa7 Better behaviour of Prev/Next buttons when share has only one song:
- Allow Prev to restart the song
- Disable Next
2023-01-31 21:27:47 -05:00
Deluan aaf58bbd32 Handle nil pointer dereference. Fix #2133 2023-01-31 20:54:15 -05:00
deluan 58c46827cd Update translations 2023-01-31 10:05:55 -05:00
Deluan 712d8f9fcc Add trace logs to calls to external services 2023-01-31 09:37:09 -05:00
Deluan b6fcfa9fc8 Add a fallback when the browser does not support copying the share link to clipboard (not a secure origin)
See: https://stackoverflow.com/a/51823007
2023-01-30 12:09:01 -05:00
Deluan 762a1ba998 Fix downloading and sharing from a playlist. Fix #2123 2023-01-30 11:20:22 -05:00
deluan 25374b3bbe Update translations 2023-01-30 08:42:01 -05:00
Deluan 68e6115789 Rename `DevEnableShare` to `EnableSharing` 2023-01-29 20:33:10 -05:00
Deluan a651d65a5b Add a comment to the generated zip 2023-01-29 17:08:18 -05:00
Deluan dc56c52557 Refactor zip archiver.
Add `disc` to path when downloading albums. Fix #2121
2023-01-29 15:25:20 -05:00
Deluan 5163df6531 Rollback changes to Chinese translations
Were not updated in POEditor
2023-01-27 11:09:42 -05:00
deluan fc693e5601 Update translations 2023-01-27 11:00:43 -05:00
Deluan 731bd7ee73 Fix update translations job 2023-01-27 10:26:03 -05:00
Deluan 9f684e5a69 Add job to create translations PRs 2023-01-27 10:15:04 -05:00
Deluan e2ea5eba8c Disable creation of shares when feature is disabled.
Fix https://github.com/navidrome/navidrome/pull/2106#issuecomment-1404731388
2023-01-26 10:12:52 -05:00
Deluan Quintão b825d3cfac
Fix versioning releases in the pipeline (#2101)
* Revert "Disable buildvcs flag"

This reverts commit 1374dab087.

* Config /github/workspace folder as trusted
2023-01-25 15:35:01 -05:00
Deluan 1950c07b1d Disable external links when `EnableExternalServices` is false. Fix #2022 2023-01-25 10:28:03 -05:00
Deluan e0fc997adb Fix Share dialog titles for Album and Playlist 2023-01-25 10:20:28 -05:00
Deluan 5eefb265e5 Simplify radio CRUD code 2023-01-25 10:03:55 -05:00
paradajz 39161fdf47
Playlist view: optionally show comment column (#2073)
* playlist view: optionally show genre and comment columns

* Remove genre from Playlist columns, as it is not a valid attribute of playlist

Co-authored-by: Deluan <deluan@navidrome.org>
2023-01-24 21:15:41 -05:00
selfhoster1312 1e24809ed6
Create accounts automatically when authenticating from HTTP header (#2087)
* Create accounts automatically when authenticating from HTTP header

* Disable password check when header auth is enabled

* Formatting

* Password change is valid when no password (old or new) is provided

* Test suite runs with header auth disabled (mock config)
Prevents nil pointer access (panic) while testing password validating logic

* Use a constant prefix for autogenerated passwords (header auth case)

* Add tests

* Add context to log messages

Co-authored-by: Deluan <deluan@navidrome.org>
2023-01-24 20:18:10 -05:00
Deluan 9721ef8974 Fix download translation key 2023-01-24 20:14:51 -05:00
Deluan 16850a9be0 Revert "Replace the LoveButton with ArtistContextMenu in the artist page - #1979"
see https://github.com/navidrome/navidrome/issues/1979#issuecomment-1402904870
2023-01-24 20:14:51 -05:00
Aleksey Lobanov 457e1fc97b
Base SQL metrics in MetricsWorker (#2002)
* feat: Add metrics worker

* refactor: Add todos for useful for metrics methods

* feat: Run MetricsWorker is Prometheus is Enabled

* refactor: Unused low-level variable was removed in metrics

* feat: No worker for metrics, add more

* refactor: Unnecessary todo removed

* refactor: Remove dead unused constant

* Reduce metrics public interface

Co-authored-by: Deluan <deluan@navidrome.org>
2023-01-24 19:26:07 -05:00
Deluan d31faf5249 Bump github.com/onsi/gomega from 1.25.0 to 1.26.0 2023-01-24 19:04:33 -05:00
Deluan 2082948144 Fix downloadOriginalFormat term in English translation 2023-01-24 18:41:43 -05:00
Deluan 39dc9c4310 Disable Subsonic Share endpoints if feature is disabled 2023-01-24 18:36:47 -05:00
Deluan 0c263cf234 Make AlbumSongs BulkActionsToolbar more responsive 2023-01-24 18:36:47 -05:00
Deluan 85084cda57 Add button to share selected songs 2023-01-24 18:36:47 -05:00
Deluan 69b36c75a5 Add meta tags to show cover and share description in social platforms 2023-01-24 18:36:47 -05:00
Deluan cab43c89e6 Mark Share.LastVisited optional in Subsonic API 2023-01-24 18:36:47 -05:00
Deluan 433da37982 Add Share to Context menus, also share artist 2023-01-24 18:36:47 -05:00
Deluan 051e9c556d Use redux for ShareDialog 2023-01-24 18:36:47 -05:00
Deluan 17d9573f4d Refactor dialogs, make it simple to add a new dialog to all views 2023-01-24 18:36:47 -05:00
Deluan 26be5b8396 Keep order of shared mediafiles 2023-01-24 18:36:47 -05:00
Deluan c770229154 Add Share capability to Subsonic user's info 2023-01-24 18:36:47 -05:00
Deluan ef4765c768 Fix getShares sort order 2023-01-24 18:36:47 -05:00
Deluan 6c05fcb699 Create contents label for group of shared mediafiles 2023-01-24 18:36:47 -05:00
Deluan 63e67bd502 Make Share list responsive 2023-01-24 18:36:47 -05:00
Deluan 230f2fdc02 Reduce spacing between album buttons, to avoid breaking the toolbar in two 2023-01-24 18:36:47 -05:00
Deluan d639da9eb5 Enable sharing only selected songs with the Subsonic API 2023-01-24 18:36:47 -05:00
Deluan e34f26588e Fix empty `entry` collection in Shares 2023-01-24 18:36:47 -05:00
Deluan c994ed70ea Fix `expireAt` update error 2023-01-24 18:36:46 -05:00
Deluan 40cac5c367 Fix JS console warning 2023-01-24 18:36:46 -05:00
Deluan 34277f238c Make Share icon dynamic 2023-01-24 18:36:46 -05:00
Deluan dbf80d8592 Change public/share path to `/share` - DSub does not use the URL from the API response... :( 2023-01-24 18:36:46 -05:00
Deluan d5df102f9f Implement `updateShare` and `deleteShare` Subsonic endpoints 2023-01-24 18:36:46 -05:00
Deluan 20271df4fb Workaround to detect empty dates in some Subsonic clients 2023-01-24 18:36:46 -05:00
Deluan d4c1d2ece4 Handle expired shares 2023-01-24 18:36:46 -05:00
Deluan d0dceae094 Add `getShares` and `createShare` Subsonic endpoints 2023-01-24 18:36:46 -05:00
Deluan 94cc2b2ac5 Fix tests and lint errors, plus a bit of refactor 2023-01-24 18:36:46 -05:00
Deluan 72a12e344e More share translations 2023-01-24 18:36:46 -05:00
Deluan 12bb6c3847 Don't expose empty dates in share info 2023-01-24 18:36:46 -05:00
Deluan 58fc271864 Share playlists 2023-01-24 18:36:46 -05:00
Deluan 65174d3fb2 Refactor DownloadMenuDialog to use `useTranscodingOptions` hook 2023-01-24 18:36:46 -05:00
Deluan c8293fcdd8 Extract transcoding options to its own hook 2023-01-24 18:36:46 -05:00
Deluan d9c42b3183 Add share's `contents` and `description` to the DB 2023-01-24 18:36:46 -05:00
Deluan 364fdfbd8d Use defaultDownsamplingFormat in share options 2023-01-24 18:36:45 -05:00
Deluan 63b4a12a93 Fine tune SharePlayer 2023-01-24 18:36:45 -05:00
Deluan 357c0e1e19 Refactor URL builders in UI 2023-01-24 18:36:45 -05:00
Deluan 84aa094e56 More work on Shares 2023-01-24 18:36:45 -05:00
Deluan ab04e33da6 Initial work on Shares 2023-01-24 18:36:45 -05:00
Kendall Garner 5331de17c2
Fixes the slide bar clickable area (#2113) 2023-01-24 11:15:14 -05:00
dependabot[bot] 199f66b8de
Bump @testing-library/react from 12.1.2 to 12.1.5 in /ui (#2109)
Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 12.1.2 to 12.1.5.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v12.1.2...v12.1.5)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-23 12:19:47 -05:00
dependabot[bot] 535171faf8
Bump github.com/onsi/gomega from 1.24.2 to 1.25.0 (#2111)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.24.2 to 1.25.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.24.2...v1.25.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-23 12:19:21 -05:00
dependabot[bot] bee39ad28e
Bump github.com/spf13/viper from 1.14.0 to 1.15.0 (#2110)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.14.0...v1.15.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-23 12:18:51 -05:00
Kendall Garner 2de570fe72
Fix order of gain menu options (#2105) 2023-01-22 11:08:54 -05:00
Deluan 33f033beba Fix artist image not caching on browser 2023-01-20 21:28:44 -05:00
Deluan b9934799ec Increase size of artist image 2023-01-20 20:55:17 -05:00
Deluan adea15ab93 Use constant 2023-01-20 16:01:16 -05:00
Corrado Primier 0c27e7a43b
Fix Illumos build - #2067 (#2069)
Build currently fails on Illumos with error `Undefined symbol sendfile`. Fix it by linking `sendfile` explicitly.
2023-01-19 12:52:01 -05:00
Deluan 8956f5e7fd Fix Album.MaxYear calculation 2023-01-19 09:34:58 -05:00
Deluan 7073d18b54 Make private methods unpublished 2023-01-19 09:34:39 -05:00
Deluan 7fc964aec5 Don't wake CacheWarmer every 10 seconds, let it sleep :) 2023-01-18 19:31:15 -05:00
Deluan 136d5f9a83 Add config option to show album participations under artists in Subsonic clients 2023-01-18 14:20:06 -05:00
vlfldr 8ae0bcb459
Add Gruvbox Dark color theme (#2092)
* Added Gruvbox Dark color theme

* Correct formatting by running prettier
2023-01-18 13:23:36 -05:00
Deluan 127c75e34b Don't try to downsample if requested bitrate is equal or greater than original. Fix #2066 2023-01-18 13:20:51 -05:00
Deluan d5c9cf07bd Fix Playlist show 2023-01-18 09:43:07 -05:00
Deluan 701e301d48 Increase timeout for obtaining login background image list 2023-01-17 22:57:14 -05:00
Deluan 580e9ae4bd Fix timer going awry 2023-01-17 22:04:09 -05:00
Zane van Iperen feb774a149
Change genre.Put() to upsert. Fix #1918 and #1564 (#1920)
* persistence/genre: change Put() to upsert

Absolutely disgusting hack to work around [1]. Try to insert the genre,
but if it conflicts, ignore it and update the genre with the existing
ID.

[1]: https://github.com/navidrome/navidrome/issues/1918.

* scanner: remove cached genre repository

Not needed anytmore. And remember:

  "Many Small Queries Are Efficient In SQLite" [1].

[1]: https://www.sqlite.org/np1queryprob.html

* Revert "scanner: remove cached genre repository"

This reverts commit c5d900aa43.

* Use squirrel to build SQL, to reduce risk of SQL injection

Co-authored-by: Deluan <deluan@navidrome.org>
2023-01-17 21:04:18 -05:00
Deluan 17eab6a88d Fix resized image cache key 2023-01-17 20:58:38 -05:00
Deluan bedd2b2074 Implement better artwork cache keys 2023-01-17 20:37:10 -05:00
Kendall Garner 93adda66d9
Get album info (when available) from Last.fm, add getAlbumInfo endpoint (#2061)
* lastfm album.getInfo, getAlbuminfo(2) endpoints

* ... for description and reduce not found log level

* address first comments

* return all images

* Update migration timestamp

* Handle a few edge cases

* Add CoverArtPriority option to retrieve albumart from external sources

* Make agents methods more descriptive

* Use Last.fm name consistently

Co-authored-by: Deluan <deluan@navidrome.org>
2023-01-17 20:22:54 -05:00
Deluan 5564f00838 Some refactor, log message changes 2023-01-17 17:26:48 -05:00
Kendall Garner 1324a16fc5 ReplayGain support + audio normalization (web player) (#1988)
* ReplayGain support

- extract ReplayGain tags from files, expose via native api
- use metadata to normalize audio in web player

* make pre-push happy

* remove unnecessary prints

* remove another unnecessary print

* add tooltips, see metadata

* address comments, use settings instead

* remove console.log

* use better language for gain modes
2023-01-17 15:57:19 -05:00
Deluan 9ae156dd82 Remove unused prop 2023-01-17 14:31:17 -05:00
Deluan 438d45c176 Change Internet Radio UX 2023-01-17 14:22:10 -05:00
Deluan e76080809d Fix pipeline lint error help message 2023-01-17 11:02:07 -05:00
Deluan 0a65bf171b Change Players icon, to distinguish it from Internet Radios 2023-01-16 20:51:18 -05:00
Deluan e40da183bb Move artwork id encoding to public package 2023-01-16 15:24:25 -05:00
Deluan 13ba08157a Add Size column to Album Songs view 2023-01-16 15:13:05 -05:00
Deluan 7682fddec0 Add Size column to Artist and Album views 2023-01-16 15:00:50 -05:00
Deluan 4a054de3d5 Hide togglable columns when in Album Grid view mode. Fixes #2064 2023-01-16 15:00:33 -05:00
dependabot[bot] b6233e57b3
Bump @material-ui/styles from 4.11.4 to 4.11.5 in /ui (#2093)
Bumps [@material-ui/styles](https://github.com/mui-org/material-ui/tree/HEAD/packages/material-ui-styles) from 4.11.4 to 4.11.5.
- [Release notes](https://github.com/mui-org/material-ui/releases)
- [Commits](https://github.com/mui-org/material-ui/commits/HEAD/packages/material-ui-styles)

---
updated-dependencies:
- dependency-name: "@material-ui/styles"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 12:17:12 -05:00
dependabot[bot] c00040d94e
Bump github.com/dustin/go-humanize from 1.0.0 to 1.0.1 (#2094)
Bumps [github.com/dustin/go-humanize](https://github.com/dustin/go-humanize) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/dustin/go-humanize/releases)
- [Commits](https://github.com/dustin/go-humanize/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: github.com/dustin/go-humanize
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-16 12:16:39 -05:00
Deluan c748d669d6 Sort radio stations by name 2023-01-15 16:12:22 -05:00
Deluan d319b66ff3 Make Radio Create and Edit forms consistent 2023-01-15 15:43:46 -05:00
Deluan a8478ca74c Fix Subsonic XML Internet Radio response 2023-01-15 15:38:38 -05:00
Kendall Garner 8877b1695a
Add Internet Radio support (#2063)
* add internet radio support

* Add dynamic sidebar icon to Radios

* Fix typos

* Make URL suffix consistent

* Fix typo

* address feedback

* Don't need to preload when playing Internet Radios

* Reorder migration, or else it won't be applied

* Make Radio list view responsive

Also added filter by name, removed RadioActions and RadioContextMenu, and added a default radio icon, in case of favicon is not available.

* Simplify StreamField usage

* fix button, hide progress on mobile

* use js styles over index.css

Co-authored-by: Deluan <deluan@navidrome.org>
2023-01-15 15:11:37 -05:00
Gil Desmarais aa21a2a305
Respect prefers-reduced-motion browser configuration (#2090)
Signed-off-by: Gil Desmarais <git@desmarais.de>

Signed-off-by: Gil Desmarais <git@desmarais.de>
2023-01-14 18:42:23 -05:00
Deluan e3496c7eea Fix artist folder detection. Now works when the artist has only one album. 2023-01-14 14:36:27 -05:00
Deluan d3e4a5287d "Touch" playlists to force some clients to reload cover art 2023-01-14 12:21:31 -05:00
Deluan 12dd219e16 Don't refresh artistInfo when setting artist's love/rating 2023-01-14 10:52:03 -05:00
bornav 1d6b04e3ad Replace the LoveButton with ArtistContextMenu in the artist page - #1979 2023-01-14 10:52:03 -05:00
Deluan dfbf86c577 Allow any HTTP methods for public images endpoint. Fix artist covers in Subtracks 2023-01-14 10:17:21 -05:00
Deluan 16c869ec86 Optimize playlist cover generation 2023-01-13 22:18:34 -05:00
Deluan c46a2a5f5f New dev options to control getCoverArt throttling 2023-01-13 22:18:34 -05:00
Deluan ab7668f562 Use a custom artist image cache key.
Invalidate when `Agents` config changes. This should solve https://github.com/navidrome/navidrome/issues/1601#issuecomment-1241702797
2023-01-13 22:18:34 -05:00
Deluan 94c6d47181 More descriptive error when artist.jpg not found 2023-01-13 22:18:34 -05:00
Deluan 0ffef05cc3 Remove "Biography not available" when agents are not available 2023-01-13 22:18:34 -05:00
Deluan 3f2d24695e PreCache artist images 2023-01-13 22:18:34 -05:00
Deluan cbe3adf987 Don't show error when it is nil 2023-01-13 22:18:34 -05:00
Deluan c90468b895 Find `artist.*` image in Artist folder 2023-01-13 22:18:34 -05:00
Deluan 69e0a266f4 Remove size from public image ID JWT 2023-01-13 22:18:34 -05:00
Deluan 8f0d002922 Add local TopSongs 2023-01-13 22:18:34 -05:00
Deluan 77a99a735b Always access artist images through Navidrome (proxy calls to external URLs) 2023-01-13 22:18:34 -05:00
Deluan 918fee3ea3 Artwork reader for Artist 2023-01-13 22:18:34 -05:00
Deluan bf461473ef Add local agent, only for images 2023-01-13 22:18:34 -05:00
Deluan 387acc5f63 Add public endpoint to expose images 2023-01-13 22:18:34 -05:00
Deluan 7fbcb2904a Add function number.RandomInt64 2023-01-13 21:40:24 -05:00
Deluan 7a617d3a1d Remove unused "embed" build tag 2023-01-13 21:35:54 -05:00
Deluan 769e8bedba Rename WeightedChooser's method Put to Add, a better name 2023-01-13 19:43:27 -05:00
Deluan 291455f0b7 Fix Download Dialog not showing in Artist page 2023-01-13 19:40:43 -05:00
Deluan b1b081e3d8 Move react-scripts to devDependencies 2023-01-13 09:33:10 -05:00
dependabot[bot] 9ea9b48891 Bump golang.org/x/tools from 0.4.0 to 0.5.0
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-13 09:15:58 -05:00
dependabot[bot] e6e9260648 Bump decode-uri-component from 0.2.0 to 0.2.2 in /ui
Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2.
- [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases)
- [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2)

---
updated-dependencies:
- dependency-name: decode-uri-component
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-13 09:15:30 -05:00
dependabot[bot] 224e3b3089 Bump json5 from 1.0.1 to 1.0.2 in /ui
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-13 09:14:55 -05:00
dependabot[bot] 023e103720 Bump prettier from 2.4.1 to 2.8.2 in /ui
Bumps [prettier](https://github.com/prettier/prettier) from 2.4.1 to 2.8.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.4.1...2.8.2)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-13 09:14:10 -05:00
dependabot[bot] 53ef50d980 Bump golang.org/x/text from 0.5.0 to 0.6.0
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.5.0 to 0.6.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.5.0...v0.6.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-13 09:11:48 -05:00
Deluan feabcdfe9f Show help message when goimports/go mod tidy breaks the build 2023-01-13 08:58:41 -05:00
Deluan 1374dab087 Disable buildvcs flag 2023-01-12 22:18:50 -05:00
dependabot[bot] 18aac7c729 Bump github.com/onsi/ginkgo/v2 from 2.6.1 to 2.7.0
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.6.1 to 2.7.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.6.1...v2.7.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-12 21:33:06 -05:00
dependabot[bot] c8ecf3b495 Bump github.com/go-chi/httprate from 0.7.0 to 0.7.1
Bumps [github.com/go-chi/httprate](https://github.com/go-chi/httprate) from 0.7.0 to 0.7.1.
- [Release notes](https://github.com/go-chi/httprate/releases)
- [Commits](https://github.com/go-chi/httprate/compare/v0.7.0...v0.7.1)

---
updated-dependencies:
- dependency-name: github.com/go-chi/httprate
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-12 21:32:34 -05:00
Deluan 7e03f8ca82 Upgrade to Go 1.19.5 2023-01-12 21:20:45 -05:00
Deluan fdbece5c92 Use custom sanitize package, fix #2070 2023-01-12 13:39:05 -05:00
Deluan df0f140f9f Don't refresh smart playlists when generating covers 2023-01-01 20:28:03 -05:00
Deluan 950cc28e67 Add coverArt to Subsonic playlist response 2023-01-01 19:35:19 -05:00
Deluan 6260927074 Serve artist placeholder directly, instead of using LastFM's CDN 2022-12-30 20:14:03 -05:00
Celyn Walters b8c171d3d4
Hide LastFM icons if `config.lastFMEnabled` is false (#1935)
Hide LastFM icons if `config.lastFMEnabled` is false
2022-12-30 17:15:14 -05:00
Deluan 80ded63d35 Add test for mapTrackTitle 2022-12-30 15:13:04 -05:00
Deluan cc14485194 When trying to PreCache, wait for ImageCache to be available 2022-12-28 23:26:39 -05:00
Deluan 0c7c6ba020 PreCache Playlists CoverArt 2022-12-28 15:31:56 -05:00
Deluan 14032a524b Reduce retention in CacheWarmer 2022-12-28 15:31:56 -05:00
Deluan 61e5523457 Handle "naked" CoverArtIDs (IDs of album, mediafiles and playlists) 2022-12-28 15:31:56 -05:00
Deluan bc09de6640 Better error handling 2022-12-28 15:31:56 -05:00
Deluan 949331ed24 GetCoverArt generates a tiled (2x2) image for playlists 2022-12-28 15:31:56 -05:00
Deluan 501386b11f Parse correctly playlist CoverArt ids 2022-12-28 15:31:56 -05:00
Deluan 8f3387a894 Fix tests and clean up code a bit 2022-12-28 15:31:56 -05:00
Deluan 332900774d Rename DevFastAccessCoverArt to EnableMediaFileCoverArt 2022-12-28 15:31:56 -05:00
Deluan 722a00cacf Fix artwork caching 2022-12-28 15:31:56 -05:00
Deluan 92ddae4a65 Created dedicated artwork readers 2022-12-28 15:31:56 -05:00
Deluan c1c4645501 Move artwork handling to its own package 2022-12-28 15:31:56 -05:00
Deluan 8cf78efb9c Add timeout for artwork extraction 2022-12-28 15:31:56 -05:00
Deluan 52a4721c91 Remove empty (invalid) entries from the cache 2022-12-28 15:31:56 -05:00
Deluan e89d99aee0 Also caches resized images 2022-12-28 15:31:56 -05:00
Deluan dc16ccdb93 Make tests compatible with GoLang 1.18 2022-12-28 15:31:56 -05:00
Deluan b6eb60f019 Add new Artwork Cache Warmer 2022-12-28 15:31:56 -05:00
Deluan 8c1cd9c273 Refactor file type functions 2022-12-28 15:31:56 -05:00
Deluan 9ec349dce0 Make sure album is updated if external cover changes 2022-12-28 15:31:56 -05:00
Deluan f5719a7571 Fix spaces in CoverArtPriority, more trace logs in artwork resolution 2022-12-28 15:31:56 -05:00
Deluan 3dbd5c8d31 Remove unnecessary cache invalidator, as ID nows contains the updatedAt value 2022-12-28 15:31:56 -05:00
Deluan 73bb0104f0 Cache original images 2022-12-28 15:31:56 -05:00
Deluan 26a7adae5f Change Image cache key format 2022-12-28 15:31:56 -05:00
Deluan 04eab5666a Add back CoverArtPriority 2022-12-28 15:31:56 -05:00
Deluan 045b023b35 Fix `DevFastAccessCoverArt` flag 2022-12-28 15:31:56 -05:00
Deluan 57c3334ea0 Remove unused `DevPreCacheAlbumArtwork` config option 2022-12-28 15:31:56 -05:00
Deluan 847a0432ea If resize fails, send the artwork as is. Closes #1102 2022-12-28 15:31:56 -05:00
Deluan 8e640bb858 Implement new Artist refresh 2022-12-28 15:31:56 -05:00
Deluan bce7b163ba Skip trying to read cover art from mediafile if it does not have one 2022-12-28 15:31:56 -05:00
Deluan 2923f01cd9 Fix UI artwork id creation 2022-12-28 15:31:56 -05:00
Deluan a087f57d2d Handle request (context) cancellation 2022-12-28 15:31:56 -05:00
Deluan 9fcd1c9354 Make internal method unexported 2022-12-28 15:31:56 -05:00
Deluan 2814c818bd go mod tidy 2022-12-28 15:31:56 -05:00
Deluan 73719c3abd Fix cover detection on M4A containers 2022-12-28 15:31:56 -05:00
Deluan e0da1d1589 Log artwork origin (tag, file, etc...) 2022-12-28 15:31:56 -05:00
Deluan 92b42b35b3 Fallback extracting tags using ffmpeg 2022-12-28 15:31:56 -05:00
Deluan abd3274250 Handle empty cover art ID in subsonic API 2022-12-28 15:31:56 -05:00
Deluan 0da27e8a3f Add image cache back 2022-12-28 15:31:56 -05:00
Deluan 40bb211b39 Small test refactor 2022-12-28 15:31:56 -05:00
Deluan 87d4db7638 Handle mediafile covers 2022-12-28 15:31:56 -05:00
Deluan 213ceeca78 Resize if requested 2022-12-28 15:31:56 -05:00
Deluan 7b87386089 Load artwork from embedded 2022-12-28 15:31:56 -05:00
Deluan c36e77d41f Remove CoverArtID, fix tests 2022-12-28 15:31:56 -05:00
Deluan 38bde0ddba Remove current Image Cache implementation 2022-12-28 15:31:56 -05:00
Deluan c430401ea9 Remove current artwork implementation 2022-12-28 15:31:56 -05:00
Deluan 0130c6dc13 Add all images found for each album in the database 2022-12-28 15:31:56 -05:00
Deluan 2f90fc9bd4 Move album refresh to scanner 2022-12-28 15:31:56 -05:00
Deluan 566ae93950 Remove old refresh code 2022-12-28 15:31:56 -05:00
Deluan 83ff44f5f4 Move cover art discovery (temporarily) to model 2022-12-28 15:31:56 -05:00
Deluan 28e7371d93 Moved logic of collapsing songs into albums to model package
(it should really be called domain.... maybe will rename it later)
2022-12-28 15:31:56 -05:00
Deluan e03ccb3166 Replace MinInt/MaxInt with generic versions 2022-12-28 15:31:56 -05:00
Deluan 6f5aaa1ec4 Move alternative tag names mapping to metadata 2022-12-28 15:31:56 -05:00
Deluan 0c22af3585 Invert dependency of metadata and extractors 2022-12-28 15:31:56 -05:00
Kendall Garner 55b0227494
Add Date Added column in Album and Song lists (#2055) 2022-12-22 22:44:07 -05:00
Deluan db6e8e45b7 Fix build badge: https://github.com/badges/shields/issues/8671 2022-12-21 18:41:22 -05:00
Deluan 5943e8f953 Rename log.LevelCritical to log.LevelFatal 2022-12-21 14:53:36 -05:00
Deluan 28389fb05e Add command line M3U exporter. Closes #1914 2022-12-21 14:39:40 -05:00
Deluan 5d8318f7b3 Change "Go to current song" hotkey.
It was blocking Cmd-C (copy on macOS)
2022-12-18 20:58:01 -05:00
dependabot[bot] 75596a6b64
Bump github.com/onsi/gomega from 1.24.1 to 1.24.2 (#2048)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.24.1 to 1.24.2.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.24.1...v1.24.2)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-18 12:41:42 -05:00
dependabot[bot] a9ddb2db6b
Bump github.com/beego/beego/v2 from 2.0.6 to 2.0.7 (#2047)
Bumps [github.com/beego/beego/v2](https://github.com/beego/beego) from 2.0.6 to 2.0.7.
- [Release notes](https://github.com/beego/beego/releases)
- [Changelog](https://github.com/beego/beego/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/beego/beego/compare/v2.0.6...v2.0.7)

---
updated-dependencies:
- dependency-name: github.com/beego/beego/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-18 12:38:55 -05:00
dependabot[bot] fe1a6a7dd5
Bump github.com/onsi/ginkgo/v2 from 2.5.1 to 2.6.1 (#2046)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.5.1 to 2.6.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.5.1...v2.6.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-18 12:38:33 -05:00
dependabot[bot] 9cb1fc4fa1
Bump github.com/go-chi/chi/v5 from 5.0.7 to 5.0.8 (#2040)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.7 to 5.0.8.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.7...v5.0.8)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-18 12:38:20 -05:00
Deluan Quintão 24d520882e
Don't cache transcoded files if the request was cancelled (#2041)
* Don't cache transcoded files if the request was cancelled (or there was a transcoding error)

* Add context to logs

* Simplify Wait error handling

* Fix flaky test

* Change log level for "populating cache" error message

* Small cleanups
2022-12-18 12:22:12 -05:00
Kendall Garner 54395e7e6a
Enable transcoding of downlods (#1667)
* feat(download): Enable transcoding of downlods - #573

Signed-off-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>

* feat(download): Make automatic transcoding of downloads optional

Signed-off-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>

* Fix spelling

* address changes

* prettier

* fix config

* use previous name

Signed-off-by: Kendall Garner <17521368+kgarner7@users.noreply.github.com>
2022-12-18 12:12:37 -05:00
Deluan 6489dd4478 Fix overriding previous logger in context 2022-12-14 11:50:16 -05:00
Deluan 6c4a0be6ff Add endpoints in Subsonic API logs 2022-12-14 10:52:46 -05:00
Deluan 982b604500 Add username to authenticated log messages 2022-12-14 09:35:30 -05:00
Deluan f206d81afd Some cleanup, fixes typos and grammar errors 2022-12-06 20:09:03 -05:00
Deluan c5f7cf97f4 Some cleanup, adding missing context handling 2022-12-06 19:57:47 -05:00
gauth-fr 55ba39cb79
Add global Downsampling feature (#1575)
* Add global downsampling feature

* Default to Opus &  consider player transcoder

* Add a test case for DefaultDownsamplingFormat

Co-authored-by: Deluan <deluan@navidrome.org>
2022-12-06 19:41:16 -05:00
Deluan 0cc1db54d4 Bump github.com/bradleyjkemp/cupaloy to v2.8.0 2022-12-05 22:45:02 -05:00
Deluan 879992eb33 Change "current song" hotkey English label 2022-12-05 13:50:19 -05:00
Robert Sammelson b5b01f78db
Keyboard shortcut to go to current song (#2029)
* feat(hotkeys): keyboard-shortcut-for-current-song - #1336

Signed-off-by: Pavithra Nair <pmpavithranair@gmail.com>

* Fix previously mentioned bugs

Signed-off-by: Pavithra Nair <pmpavithranair@gmail.com>
Co-authored-by: Pavithra Nair <pmpavithranair@gmail.com>
2022-12-05 13:37:49 -05:00
dependabot[bot] cdddd4ce30
Bump golang.org/x/text from 0.4.0 to 0.5.0 (#2030)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.4.0 to 0.5.0.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.4.0...v0.5.0)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-05 13:24:42 -05:00
Reo 4489c34757
Fix Misleading Error Message on unreadable Media due to Permission (#1873)
* fix(taglib): Fix misleading error message on unreadable media - #1576

Signed-off-by: reo <reo_999@proton.me>

* fix(taglib): Add unit test and exclude scan for only unreadable file - #1576

Signed-off-by: reo <reo_999@proton.me>

* fix(taglib): Add unit test and exclude scan for only unreadable file - #1576

Signed-off-by: reo <reo_999@proton.me>

* fix(taglib): Add unit test and exclude scan for only unreadable file - #1576

Signed-off-by: reo <reo_999@proton.me>

* fix(taglib): Add unit test and exclude scan for only unreadable file - #1576

Signed-off-by: reo <reo_999@proton.me>

* fix(taglib): Add unit test and exclude scan for only unreadable file - #1576

Signed-off-by: reo <reo_999@proton.me>

* Fix test and simplify code a bit

We don't need to expose the type of error: `taglib.Parse()` always return nil

* Fix comment

Signed-off-by: reo <reo_999@proton.me>
Co-authored-by: Deluan <deluan@navidrome.org>
2022-12-04 12:48:21 -05:00
Deluan 51b67d18d3 Increase number of "Shuffle All" songs 2022-12-03 20:54:23 -05:00
Robert Sammelson c4d1569441
Fix bug in duration format logic (#2026) 2022-12-03 20:31:02 -05:00
Deluan 68ceeb9ea1 Fix build for non-unix 2022-12-03 10:42:36 -05:00
Deluan 4549b91ae0 Fix build for non-unix 2022-12-02 20:39:44 -05:00
Deluan 9ffd145e82 Add log for signal received 2022-12-02 20:30:30 -05:00
dependabot[bot] 5713010984
Bump github.com/spf13/viper from 1.13.0 to 1.14.0 (#2019)
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.13.0...v1.14.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-02 18:28:43 -05:00
Deluan 00c6545cb1 Bump github.com/go-chi/jwtauth/v5 from 5.0.2 to 5.1.0 2022-12-02 17:58:53 -05:00
dependabot[bot] 3f45a4ed98
Bump github.com/beego/beego/v2 from 2.0.5 to 2.0.6 (#2016)
Bumps [github.com/beego/beego/v2](https://github.com/beego/beego) from 2.0.5 to 2.0.6.
- [Release notes](https://github.com/beego/beego/releases)
- [Changelog](https://github.com/beego/beego/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/beego/beego/compare/v2.0.5...v2.0.6)

---
updated-dependencies:
- dependency-name: github.com/beego/beego/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-02 17:40:08 -05:00
dependabot[bot] 46c09e4b11
Bump github.com/prometheus/client_golang from 1.13.0 to 1.14.0 (#2018)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.13.0 to 1.14.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.13.0...v1.14.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-02 17:39:52 -05:00
Deluan 40395f47f0 Use forked react-player. May fix issue #1472 2022-12-02 17:20:16 -05:00
Deluan 2c214154dc Add nakedret linter 2022-11-30 14:16:30 -05:00
Deluan 03640ca93d Fix background images when BaseURL is specified 2022-11-29 14:48:05 -05:00
Deluan d8c5944ef1 Fix race condition in scanner 2022-11-29 11:08:47 -05:00
Deluan 10cd3152ba Remove misplaced import 2022-11-27 22:01:07 -05:00
Deluan 950b5dc1ce Remove `math/rand` and only use `crypto/rand` 2022-11-27 21:53:13 -05:00
Deluan 195f39182d Host default login background images in Navidrome's own website 2022-11-27 21:37:33 -05:00
Deluan Quintão 334ccac643
Spotify-ish Improvement (#2012)
* spotify-improvement

* fixing the issue of applying styles to filter fields too

* Remove scrollbar styling.

Maybe we should simulate macOS's scrollbar behaviour, with something like this: https://gist.github.com/spemer/a0e218bbb45433bd611e68446523a00b

Co-authored-by: Rishabh Malhotra <rishabhmalhotraa01@gmail.com>
2022-11-27 12:13:00 -05:00
Garvit Galgat 676de79fb3
Don't abort scan if all audio files are in the MediaFolder's root. Fix #868 (#893)
* fixed #868

* Make sure we only abort scanning if it is not a fullScan

Co-authored-by: Deluan <deluan@navidrome.org>
2022-11-27 11:45:37 -05:00
Raghd Hamzeh d5fe0f214c
fix: send content type header in listenbrainz requests - #1944 (#1994)
fixes #1944

Signed-off-by: Raghd Hamzeh <raghd@rhamzeh.com>

Signed-off-by: Raghd Hamzeh <raghd@rhamzeh.com>
2022-11-27 09:47:13 -05:00
Deluan 6ae6e023ea Bump some NPM dependencies 2022-11-27 09:28:47 -05:00
Deluan 7bafbce816 Reduce number of goroutines in test, to avoid hitting the hard limit of 8128 2022-11-26 15:28:30 -05:00
Deluan a69a31a3bf Use custom atomic.Bool, as it is not supported in Go 1.18 2022-11-26 15:14:19 -05:00
Deluan 88823fca76 Fix race conditions in tests 2022-11-26 15:07:53 -05:00
Deluan 0bb133a6ac Kill ffmpeg if context is cancelled 2022-11-26 15:06:59 -05:00
Deluan Quintão 76a94ecb70
Update GH actions
* Update GH actions

* Fix

* Fix "Cannot open: File exists" messages
2022-11-26 14:11:39 -05:00
Deluan 1b5f855bff Compress more http content-types.
Also, some minor refactoring
2022-11-26 13:13:05 -05:00
Zane van Iperen 472f99b2b5
Add AAC default transcoding (#2010) 2022-11-23 10:20:40 -05:00
dependabot[bot] 4d660a2ba7
Bump github.com/golangci/golangci-lint from 1.49.0 to 1.50.1 (#1954)
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.49.0 to 1.50.1.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.49.0...v1.50.1)

---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 10:57:27 -05:00
dependabot[bot] 398101896f
Bump golang.org/x/tools from 0.1.12 to 0.3.0 (#1991)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.1.12 to 0.3.0.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.1.12...v0.3.0)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 10:04:30 -05:00
dependabot[bot] d76985e3f7
Bump github.com/kr/pretty from 0.3.0 to 0.3.1 (#1924)
Bumps [github.com/kr/pretty](https://github.com/kr/pretty) from 0.3.0 to 0.3.1.
- [Release notes](https://github.com/kr/pretty/releases)
- [Commits](https://github.com/kr/pretty/compare/v0.3.0...v0.3.1)

---
updated-dependencies:
- dependency-name: github.com/kr/pretty
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 10:03:06 -05:00
dependabot[bot] e17e4ef146
Bump github.com/microcosm-cc/bluemonday from 1.0.20 to 1.0.21 (#1905)
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.20 to 1.0.21.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.20...v1.0.21)

---
updated-dependencies:
- dependency-name: github.com/microcosm-cc/bluemonday
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 10:02:42 -05:00
dependabot[bot] 0a4a9d485e
Bump github.com/mattn/go-sqlite3 from 1.14.15 to 1.14.16 (#1965)
Bumps [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3) from 1.14.15 to 1.14.16.
- [Release notes](https://github.com/mattn/go-sqlite3/releases)
- [Commits](https://github.com/mattn/go-sqlite3/compare/v1.14.15...v1.14.16)

---
updated-dependencies:
- dependency-name: github.com/mattn/go-sqlite3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 09:43:05 -05:00
dependabot[bot] ce2c579235
Bump github.com/spf13/cobra from 1.5.0 to 1.6.1 (#1966)
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.5.0 to 1.6.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](https://github.com/spf13/cobra/compare/v1.5.0...v1.6.1)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 09:42:48 -05:00
dependabot[bot] 4e19c5e078
Bump github.com/stretchr/testify from 1.8.0 to 1.8.1 (#1951)
Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.0 to 1.8.1.
- [Release notes](https://github.com/stretchr/testify/releases)
- [Commits](https://github.com/stretchr/testify/compare/v1.8.0...v1.8.1)

---
updated-dependencies:
- dependency-name: github.com/stretchr/testify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-22 09:42:26 -05:00
jan666 ab6be8d2dc
Listenbrainz Scrobble (#2009)
- send SubmissionClient and SubmissionClientVersion
2022-11-22 09:32:46 -05:00
dependabot[bot] 586f5c413d
Bump github.com/onsi/ginkgo/v2 from 2.2.0 to 2.5.1 (#2007)
Bumps [github.com/onsi/ginkgo/v2](https://github.com/onsi/ginkgo) from 2.2.0 to 2.5.1.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v2.2.0...v2.5.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-21 22:57:34 -05:00
dependabot[bot] e6a93da75f
Bump github.com/onsi/gomega from 1.20.2 to 1.24.1 (#1990)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.20.2 to 1.24.1.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.20.2...v1.24.1)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-21 21:08:07 -05:00
Deluan fcb891e704 Add an `id` attribute to Search boxes. Should fix #1998 2022-11-21 13:44:16 -05:00
Deluan 19af11efbe Simplify Subsonic API handler implementation 2022-11-21 12:57:56 -05:00
Deluan cd41d9a419 Shutdown gracefully, close DB connection 2022-11-21 12:28:09 -05:00
Deluan 5f3f7afb90 Add note about unstable state of master branch 2022-11-11 21:23:07 -05:00
Deluan 1467036efd Add `DefaultUIVolume` option. Closes #1679 2022-11-11 16:31:28 -05:00
dependabot[bot] ff6c8f7e9d
Bump loader-utils from 2.0.0 to 2.0.3 in /ui (#1978)
Bumps [loader-utils](https://github.com/webpack/loader-utils) from 2.0.0 to 2.0.3.
- [Release notes](https://github.com/webpack/loader-utils/releases)
- [Changelog](https://github.com/webpack/loader-utils/blob/v2.0.3/CHANGELOG.md)
- [Commits](https://github.com/webpack/loader-utils/compare/v2.0.0...v2.0.3)

---
updated-dependencies:
- dependency-name: loader-utils
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-11-07 19:28:02 -05:00
Deluan 3a462c7f07 Fix ARM v5 and v6 builds, by going back to armel.
Also upgrades Go to 1.19.3. Closes #1968
2022-11-07 17:16:30 -05:00
Deluan 9c433b5d68 Add missing context to logger calls 2022-11-04 11:30:12 -04:00
YaoFeng Ruan daa428ede7
Update Chinese translations (#1945)
* Corrected some Simplified Chinese translations

* Fix wrong expression symbols in Traditional Chinese translation

* Modify punctuation to Chinese punctuation in Chinese translation
Add spaces between Chinese and English words in Chinese translation

* Added missing Traditional Chinese translation

* Improve some Chinese translations

* Remove redundant punctuation in Traditional Chinese translation

* Adjust the order of fields in `zh-Hans` and `zh-Hant` to be consistent with `en`
2022-11-04 10:44:32 -04:00
Deluan 76517cab12 Fix potential nil pointer dereference 2022-11-04 10:39:25 -04:00
Deluan 8f02daf337 Reduce spurious error/warn messages, if loglevel != debug 2022-11-03 12:38:05 -04:00
Deluan 80b7311453 Add TrackNumber to "fake" generated filenames. Fixes #1912 2022-11-02 12:11:01 -04:00
Deluan ca2cb26d8e Add `played` field to Subsonic API responses. Fix #1971
This is not an "official" field in the specification, but I guess it does not hurt to expose this ;)
2022-11-02 11:20:51 -04:00
Deluan 081cfe5a9f Fix build badge 2022-10-31 10:35:07 -04:00
Deluan 5f38d9dca2 Fix 60 seconds (again). Fixes #1956 2022-10-26 09:10:01 -04:00
Aleksey Lobanov 64e2a0bcd4
Optimize static images (#1941)
.png files were processed with `optipng -o7` command
2022-10-20 10:51:31 -04:00
Deluan aab4925dfc Restore DefaultLanguage case-sensitiveness by reverting commit bfeb8ef6b3.
Language code should be case-sensitive. Fix #1946. Supersedes #1947.
2022-10-19 09:14:02 -04:00
Deluan af5c2b5a42 Round song duration (instead of truncating it). Relates to #1926 2022-10-10 21:33:00 -04:00
Deluan 62e7492357 Add Linkify test 2022-10-07 17:44:16 -04:00
Deluan 53a4ea673b Linkify urls in playlist comments 2022-10-07 16:12:07 -04:00
Deluan c530ccf138 Linkify urls in album comments. Fixes #1053, supersedes #1570 and #1169
Simple approach, may be extended/enhanced in the future.
2022-10-06 23:46:30 -04:00
Deluan fa5dc5af10 Fix adding songs to plain playlists 2022-10-06 19:45:31 -04:00
Deluan bbd3882a75 Some clean-up in `criteria` package 2022-10-04 15:24:29 -04:00
Deluan 12b4a48842 Fix get info dialog in artist page. Closes #1909 2022-10-04 12:30:04 -04:00
dependabot[bot] 37f7625c7d
Bump github.com/prometheus/client_golang from 1.12.1 to 1.13.0 (#1902)
Bumps [github.com/prometheus/client_golang](https://github.com/prometheus/client_golang) from 1.12.1 to 1.13.0.
- [Release notes](https://github.com/prometheus/client_golang/releases)
- [Changelog](https://github.com/prometheus/client_golang/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prometheus/client_golang/compare/v1.12.1...v1.13.0)

---
updated-dependencies:
- dependency-name: github.com/prometheus/client_golang
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-02 20:34:29 -04:00
dependabot[bot] 7612a55859
Bump github.com/mileusna/useragent from 1.2.0 to 1.2.1 (#1901)
Bumps [github.com/mileusna/useragent](https://github.com/mileusna/useragent) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/mileusna/useragent/releases)
- [Commits](https://github.com/mileusna/useragent/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: github.com/mileusna/useragent
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-02 20:34:01 -04:00
Deluan 3d5a1cef92 Don't allow adding songs to smart playlists 2022-10-02 20:14:15 -04:00
Aleksey Lobanov 552989a05b
Add basic Prometheus metrics handler (#1830)
* feat: Add Prometheus configuration options

* feat: Add Prometheus metrics handler

* build: prometheus became direct dependency

* docs: change description for prometheus metrics path
2022-10-02 19:59:53 -04:00
Renere 6a6fa3e3b5
Nord Theme - Make links have a different colour (#1900) 2022-10-01 22:23:33 -04:00
Zane van Iperen c7ef4bd803
Capture "musicbrainz_releasetrackid" tag (#1827)
* db/migration: typo fix

* model: add MbzReleaseTrackID field

* scanner: capture the musicbrainz_releasetrackid tag
2022-10-01 12:13:47 -04:00
Renere 22507c9789
Add Nord Theme. Closes #1158 and supersedes #1159 (#1899).
* Re-add tpbnick's Nord theme

* Run Prettier formatter on Nord theme

* Update themes index

* Fix button margins

* Modernise the look of switches

* Adjust margins and padding

* Fix sidebar's background colour not applying to all of sidebar when scrolling down

* Adjust App Bar box shadow

* Adjust roundedness

* Adjust shadows

* Adjust outlined inputs

* Add transitions to items in sidebar when hovered / losing hover

* Adjust border radiuses

* Adjust pagination buttons

* Add big play button from Spotify theme

* Remove playlist background gradient

* Adjust colour of MuiChip elelments

* Adjust table borders

* Remove duplicate MuiTableRow key

* Attempt to make switches in both the playlist section and settings section visable against background & the toggle. Not ideal.

* Style the player

* Format CSS to Prettier standards

* Fix mobile player style

* Make play button in album grid view blue

* Make main view background lighter
2022-10-01 12:01:21 -04:00
Deluan 87feac041b Add make target to download some music for development purposes. Closes #1703 2022-09-30 23:10:33 -04:00
Deluan f82df70302 Add nilerr linter 2022-09-30 20:18:14 -04:00
Deluan 364e699ac1 Add asciicheck, bidichk, and durationcheck linters 2022-09-30 20:17:59 -04:00
Deluan 0798959be8 Add asasalint linter 2022-09-30 19:55:44 -04:00
William Lohan 4209e14208
Add theme Electric Purple (#1889)
* add theme file

add theme file electricPurple.js

* import theme file 

import theme file  electricPurple

* add electricPurple.css.js
2022-09-30 19:54:00 -04:00
Deluan 77dbafff0f Add errorlint linter 2022-09-30 19:33:39 -04:00
Deluan db67c1277e Fix error comparisons 2022-09-30 18:54:25 -04:00
Deluan 7b0a8f47de Add exportloopref linter 2022-09-30 18:23:47 -04:00
William Lohan 16865f0fca
remove deprecated linters (#1898) 2022-09-30 18:11:44 -04:00
Deluan 5965459bb9 Update browserlist db 2022-09-30 13:33:42 -04:00
Steve Richter 66818b25ec
Allow ExternalLink icons to be styled (#1503)
* Allow ArtistExternalLink icons to be styled

* Allow AlbumExternalLink icons to be styled

* Standardize external links' classes to kebab-case

Co-authored-by: Deluan <deluan@navidrome.org>
2022-09-30 13:33:35 -04:00
Deluan e7fab8bb7b Show AlbumArtist in Album table view. Fixes #1626 2022-09-29 16:47:44 -04:00
joaomqc 8befe10ee6
fix(UI): Warn if track is already present when adding to playlist - 1604 (#1897)
* fix(UI): Warn if track is already present when adding to playlist - 1604

Signed-off-by: joaomqc <joaomqc@hotmail.com>

* fix tests

Signed-off-by: joaomqc <joaomqc@hotmail.com>

Signed-off-by: joaomqc <joaomqc@hotmail.com>
Co-authored-by: João Coelho <1120458@isep.ipp.pt>
2022-09-29 13:19:14 -04:00
Deluan 218d14727a Bump redux and react-redux versions 2022-09-29 11:05:05 -04:00
Evan.Shu 50a4ce6ba2
Fix add playlist dialog (#1758) 2022-09-28 22:15:39 -04:00
henning mueller 8130c05ccc
Mount devcontainer workspace SELinux compatible (#1816) 2022-09-28 22:10:06 -04:00
Deluan 15952a3c7f npm audit fix 2022-09-28 22:01:13 -04:00
Nemo Xiong 9a99a2bd49
Update Chinese (simplified) translations (#1633)
* add new translations

* translation: fix improper full width character usage in zh-Hans translation

Full width % messed up with format strings.

* translation: fix two machine translations in zh-Hans

* translation: fix one mistranslation in zh-Hans

* translation: fix format in zh-Hans

* translation: fix format and two translations in zh-Hans

* translation: fix format in zh-Hans
2022-09-28 21:47:48 -04:00
dependabot[bot] c7b65509ae
Bump @testing-library/jest-dom from 5.15.0 to 5.16.5 in /ui (#1836)
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.15.0 to 5.16.5.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v5.15.0...v5.16.5)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-09-28 21:43:26 -04:00
Deluan 6b09dc7198 Fix new test-library eslint errors 2022-09-28 21:30:20 -04:00
Deluan 86ab35069d Upgrade react-scripts to 5.0.1
This also upgrades WebPack to v5, which should fix the issue #1768
2022-09-28 21:03:22 -04:00
Deluan 413292da6b Reduce go mod download verbosity 2022-09-28 20:27:53 -04:00
Deluan 694968c607 Bump dependencies 2022-09-28 13:25:08 -04:00
Deluan 6dc70d6810 Don't reset language to default after logout 2022-09-28 13:06:32 -04:00
Deluan bfeb8ef6b3 DefaultLanguage is now case-insensitive 2022-09-28 11:30:22 -04:00
Deluan Quintão ba28e9a109
Update README. Fixes #1834 2022-09-27 21:32:23 -04:00
Andy Klimczak 2f7a3c5eda
feat: Add listenbrainz base url configuration (#1774)
* feat: Add listenbrainz base url configuration

- ListenBrainz.BaseURL config value

* Don't need to store baseUrl

* Use `url.JoinPath` to concatenate url paths

* Replace url.JoinPath (Go 1.19 only) with custom function

Co-authored-by: Deluan <deluan@navidrome.org>
2022-09-27 21:06:28 -04:00
Deluan cb3ba23fce New config DefaultLanguage. Closes #1561 2022-09-27 19:31:09 -04:00
Manuel 72cde6dfde
fix:(middlewares.go) - Set Cookie SameSite mode to Strict - 1776 (#1777)
* None is deprecated and will fallback to Lax in the future.
* Using Strict is future proof and provides additional CSR protection

Signed-off-by: Manuel Kroeber <manuel.kroeber@gmail.com>

Signed-off-by: Manuel Kroeber <manuel.kroeber@gmail.com>
2022-09-27 17:58:47 -04:00
Kendall Garner 751e42c705
Fix creating server (#1894) 2022-09-27 16:53:40 -04:00
Deluan ded9ab53e5 Use armhf for ARM builds 2022-09-27 16:47:47 -04:00
Deluan 416b5c7d13 Fix Linux 32 bits build 2022-09-26 23:54:03 -04:00
Deluan afb31c3eae Fix invalid option in pipeline 2022-09-26 22:56:17 -04:00
Deluan dd57278ba2 Upgrade to GoLang 1.19 and bump golangci-lint version 2022-09-26 22:44:54 -04:00
Deluan 2a3cd08f20 Fix GO-S2114 security issue
See https://deepsource.io/directory/analyzers/go/issues/GO-S2114
2022-09-26 22:33:42 -04:00
Deluan a7a0e23956 Fix formatting 2022-09-26 21:28:10 -04:00
Deluan 4cf43ed735 Only compute version once 2022-09-14 21:09:39 -04:00
Deluan ebad96b8a4 Fix warning about mixing value and pointer receivers 2022-08-21 14:42:17 -04:00
Deluan e981ee27c0 Add test for WithTx 2022-07-30 13:07:38 -04:00
Deluan 965dbccd48 Upgrade to latest go-sqlite3 (it's v1.14, not v2!) 2022-07-30 12:46:20 -04:00
Deluan 695f82a1a0 Upgrade to Beego 2's orm 2022-07-30 12:43:48 -04:00
Deluan 16afd3a490 Remove `//+build` tags, as the code does not compile on older versions of Go anymore 2022-07-29 08:41:28 -04:00
Deluan 67f2a89d89 Fix tracks never "loved" to be selected in Smart Playlists. Refer to https://github.com/navidrome/navidrome/issues/1417#issuecomment-1163423575 2022-07-27 21:09:39 -04:00
dependabot[bot] bf1f93ef1a
Bump github.com/go-chi/httprate from 0.5.2 to 0.6.0 (#1828)
Bumps [github.com/go-chi/httprate](https://github.com/go-chi/httprate) from 0.5.2 to 0.6.0.
- [Release notes](https://github.com/go-chi/httprate/releases)
- [Commits](https://github.com/go-chi/httprate/compare/v0.5.2...v0.6.0)

---
updated-dependencies:
- dependency-name: github.com/go-chi/httprate
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-27 15:29:30 -04:00
Deluan ebf7354df4 Add more info in search log message 2022-07-27 14:59:01 -04:00
Deluan c0066ebd85 Add log warn when request is cancelled/interrupted 2022-07-27 14:27:18 -04:00
Deluan cd5bce7b16 Speed up `/search` subsonic endpoints by parallelizing the queries 2022-07-27 13:56:04 -04:00
Deluan d613b19306 Simplify Singleton usage by leveraging Go 1.18's generics 2022-07-27 12:15:05 -04:00
Deluan a2d9aaeff8 Fix Quality translation in Spanish 2022-07-27 10:42:04 -04:00
Deluan 49392e06a7 Update caniuse-lite 2022-07-26 17:48:29 -04:00
Deluan 181cb8a2b7 Remove `interfacer` linter, as it does not work with Go 1.18 and will not be updated (it is deprecated) 2022-07-26 16:59:52 -04:00
Deluan 31882abf6f Upgrade Ginkgo to V2 2022-07-26 16:53:17 -04:00
Deluan 0d8eaa2878 Remove experimental version of `context` package 2022-07-26 16:41:10 -04:00
Deluan f4bffb1676 Update @djherbis's packages 2022-07-26 15:16:56 -04:00
Deluan f21847308c Remove hardcoded github.com/dhowden/tag branch. Fix #1764 2022-07-26 15:10:16 -04:00
Deluan 9c3b14c5c4 Return 501 for "not implemented". Fixes #1785 2022-07-26 13:18:08 -04:00
Deluan 8cd405d15e Add IP to Subsonic API's invalid login log messages. Closes #1814 2022-07-25 23:54:49 -04:00
Deluan 35bec14d4d Add missing test case for #1778 2022-07-25 23:34:09 -04:00
Deluan 321b3c5a64 Fix fscache key mapping. Closes #1778 2022-07-25 23:01:19 -04:00
Deluan b7e50f7731 Fix docker build in pipeline 2022-07-25 10:54:19 -04:00
dependabot[bot] 2e9c81c3de
Bump github.com/mileusna/useragent from 1.0.2 to 1.1.0 (#1819)
Bumps [github.com/mileusna/useragent](https://github.com/mileusna/useragent) from 1.0.2 to 1.1.0.
- [Release notes](https://github.com/mileusna/useragent/releases)
- [Commits](https://github.com/mileusna/useragent/compare/v1.0.2...v1.1.0)

---
updated-dependencies:
- dependency-name: github.com/mileusna/useragent
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-25 10:43:45 -04:00
dependabot[bot] 49647423aa
Bump github.com/sirupsen/logrus from 1.8.1 to 1.9.0 (#1821)
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.8.1 to 1.9.0.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.8.1...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/sirupsen/logrus
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-25 10:42:51 -04:00
dependabot[bot] 9f62533bb0
Bump github.com/go-chi/cors from 1.2.0 to 1.2.1 (#1822)
Bumps [github.com/go-chi/cors](https://github.com/go-chi/cors) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/go-chi/cors/releases)
- [Commits](https://github.com/go-chi/cors/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: github.com/go-chi/cors
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-25 10:08:48 -04:00
dependabot[bot] 7d58f4469a
Bump github.com/lestrrat-go/jwx from 1.2.17 to 1.2.25 (#1742)
Bumps [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) from 1.2.17 to 1.2.25.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/v1.2.25/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v1.2.17...v1.2.25)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-25 10:08:06 -04:00
dependabot[bot] 974816f0a2
Bump github.com/onsi/gomega from 1.18.1 to 1.20.0 (#1817)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.18.1 to 1.20.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.18.1...v1.20.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-24 20:35:38 -04:00
Deluan 7665478a52 Upgrade golangci-lint and fix new lint error 2022-07-24 19:30:23 -04:00
Deluan bde5be347b Build with GoLang 1.18.4 2022-07-24 19:02:09 -04:00
Deluan aae79b4561 Upgrade to GoLang 1.18 2022-07-24 15:31:22 -04:00
Ian Kerins ce0db8344b
Fix signaler not exiting on cancel (#1638)
* fix: make signaler exit on cancel

`break` is incorrect here, as it just breaks out of the select.
`return` to exit the function instead.

Fixes #1636.

Signed-off-by: Ian Kerins <ianskerins@gmail.com>

* fix: exit non-zero on fatal error

Signed-off-by: Ian Kerins <ianskerins@gmail.com>
2022-03-30 10:04:17 -04:00
Matt Doyle 5987cd0c08
Fixes a coloring glitch with the Monokai theme "unauthorized" popup (#1670)
* Fixes the coloring on the Monokai theme auth popup

* Indentation fix
2022-03-26 22:41:29 -04:00
Matt Doyle e7cf74d863
Adds a Monokai theme (#1669)
* Adds a new Monokai theme

* Deletes a commented-out line
2022-03-26 21:14:13 -04:00
Deluan 2ddd3acba6 Fix translatable label 2022-02-10 18:18:03 -05:00
Deluan 028723f721 Fix loading overridden translations from ${DataFolder}/resources/i18n 2022-02-10 14:56:39 -05:00
Deluan 50ff8bcce7 Add "random" sort option for Smart Playlists 2022-02-09 09:39:42 -05:00
Deluan e966d94c0b Force correct mime-type for JS and CSS files 2022-02-08 15:17:35 -05:00
dependabot[bot] 86fe1e3b2c
Bump github.com/ReneKroon/ttlcache/v2 from 2.9.0 to 2.11.0 (#1529)
Bumps [github.com/ReneKroon/ttlcache/v2](https://github.com/ReneKroon/ttlcache) from 2.9.0 to 2.11.0.
- [Release notes](https://github.com/ReneKroon/ttlcache/releases)
- [Changelog](https://github.com/ReneKroon/ttlcache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ReneKroon/ttlcache/compare/v2.9.0...v2.11.0)

---
updated-dependencies:
- dependency-name: github.com/ReneKroon/ttlcache/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-21 19:35:19 -05:00
dependabot[bot] eed54d7e10
Bump github.com/lestrrat-go/jwx from 1.2.11 to 1.2.17 (#1574)
Bumps [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) from 1.2.11 to 1.2.17.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/main/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v1.2.11...v1.2.17)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-21 19:26:28 -05:00
dependabot[bot] ab36344d76
Bump github.com/microcosm-cc/bluemonday from 1.0.16 to 1.0.17 (#1560)
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.16 to 1.0.17.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.16...v1.0.17)

---
updated-dependencies:
- dependency-name: github.com/microcosm-cc/bluemonday
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-21 19:26:11 -05:00
dependabot[bot] e5d03a3bdb
Bump github.com/Masterminds/squirrel from 1.5.1 to 1.5.2 (#1501)
Bumps [github.com/Masterminds/squirrel](https://github.com/Masterminds/squirrel) from 1.5.1 to 1.5.2.
- [Release notes](https://github.com/Masterminds/squirrel/releases)
- [Commits](https://github.com/Masterminds/squirrel/compare/v1.5.1...v1.5.2)

---
updated-dependencies:
- dependency-name: github.com/Masterminds/squirrel
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-01-21 19:25:38 -05:00
Deluan Quintão 30813cd34b
Update translations (#1578)
* Add Catalan translation

* Move Bulgarian translation to correct path

* Update zh-Hans.json (POEditor.com)

* Update zh-Hant.json (POEditor.com)

* Update cs.json (POEditor.com)

* Update da.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update eo.json (POEditor.com)

* Update fi.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update de.json (POEditor.com)

* Update it.json (POEditor.com)

* Update ja.json (POEditor.com)

* Update fa.json (POEditor.com)

* Update pl.json (POEditor.com)

* Update pt.json (POEditor.com)

* Update ru.json (POEditor.com)

* Update sl.json (POEditor.com)

* Update es.json (POEditor.com)

* Update th.json (POEditor.com)

* Update tr.json (POEditor.com)

* Update uk.json (POEditor.com)

* Update bg.json (POEditor.com)

* Update ca.json (POEditor.com)
2022-01-21 19:21:19 -05:00
MrEddX 6164f37c9e
Added Bulgarian Translation (#1577)
Initial Release
2022-01-21 19:09:48 -05:00
Deluan 9e79b5cbf2 Fix potential SQL injection in Smart Playlists 2022-01-18 21:36:29 -05:00
Steve Richter 8c707b4e0c
Handle invalid theme in ui state (#1504) 2022-01-05 18:47:14 -05:00
Deluan 910091f1f1 Fix `playCount` casing 2021-12-14 09:33:34 -05:00
Deluan 2e1b985d30 Revert "Direct link to dev build"
This reverts commit a99b9b4d44.
2021-12-10 12:41:19 -05:00
Deluan Quintão 100b80528e
Update README.md 2021-12-09 12:24:43 -05:00
Deluan bde9d5f954 Fix `TypeError: Cannot read property 'id' of undefined` 2021-12-03 17:15:39 -05:00
Deluan 69615f1aa1 Trying to fix multiple EventStream connections, one more time 2021-12-02 10:49:32 -05:00
Deluan a99b9b4d44 Direct link to dev build 2021-11-29 22:57:26 -05:00
Deluan 9892524ab8 Connect eventStream after login 2021-11-29 18:49:29 -05:00
Deluan 9fe978953c Try to avoid creating multiple eventStreams 2021-11-29 17:47:34 -05:00
Deluan Quintão 5425c1a4d7
Update translations (#1489)
* Update fi.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update de.json (POEditor.com)

* Update ja.json (POEditor.com)

* Update sl.json (POEditor.com)

* Update es.json (POEditor.com)
2021-11-26 12:34:22 -05:00
Deluan afe1e4cfcd Fix lint for public credentials 2021-11-25 15:55:53 -05:00
Deluan 20cdd38fc4 Better logging for agents configuration 2021-11-25 15:48:32 -05:00
dependabot[bot] 913a4cf546
Bump github.com/onsi/gomega from 1.16.0 to 1.17.0 (#1459)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.16.0 to 1.17.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.16.0...v1.17.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-23 23:43:32 -05:00
dependabot[bot] 121ada5acd
Bump @testing-library/jest-dom from 5.14.1 to 5.15.0 in /ui (#1456)
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.14.1 to 5.15.0.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v5.14.1...v5.15.0)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-23 23:13:22 -05:00
dependabot[bot] e59a95c9eb
Bump github.com/golangci/golangci-lint from 1.42.1 to 1.43.0 (#1460)
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.42.1 to 1.43.0.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.42.1...v1.43.0)

---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-23 23:12:27 -05:00
Mahoo Huang d75f286ae8
Update zh-Hans.json (#1478) 2021-11-23 23:11:49 -05:00
Deluan 30d3f1eda0 Add userRating to Subsonic Album/Artist responses. Closes #1486 2021-11-23 21:50:57 -05:00
dependabot[bot] 6a1f9678b1
Bump github.com/ReneKroon/ttlcache/v2 from 2.8.1 to 2.9.0 (#1414)
Bumps [github.com/ReneKroon/ttlcache/v2](https://github.com/ReneKroon/ttlcache) from 2.8.1 to 2.9.0.
- [Release notes](https://github.com/ReneKroon/ttlcache/releases)
- [Changelog](https://github.com/ReneKroon/ttlcache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ReneKroon/ttlcache/compare/v2.8.1...v2.9.0)

---
updated-dependencies:
- dependency-name: github.com/ReneKroon/ttlcache/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-22 12:50:42 -05:00
dependabot[bot] a0977ce48a
Bump github.com/go-chi/chi/v5 from 5.0.4 to 5.0.7 (#1484)
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from 5.0.4 to 5.0.7.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v5.0.4...v5.0.7)

---
updated-dependencies:
- dependency-name: github.com/go-chi/chi/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-22 12:29:10 -05:00
dependabot[bot] b3d8038686
Bump github.com/lestrrat-go/jwx from 1.2.7 to 1.2.11 (#1485)
Bumps [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) from 1.2.7 to 1.2.11.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/main/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v1.2.7...v1.2.11)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-11-22 12:28:23 -05:00
Deluan 0714f08274 Recover from SIGSEGVs in taglib's code 2021-11-20 12:33:06 -05:00
Deluan cbeaadf8e2 Fix logging smart playlist's song count 2021-11-20 12:29:09 -05:00
Deluan 3e282df639 Set volume to 100% when web player is in mobile mode.
Fix #1429
2021-11-19 19:45:18 -05:00
Deluan ce7940bbfc Allow overriding `name` and `comment` when importing NSP playlists 2021-11-19 19:14:38 -05:00
Deluan 92c31c961d Fix values from annotation table cannot be compared to 0
Solves this issue: https://github.com/navidrome/navidrome/issues/1417#issuecomment-974052454
2021-11-19 18:22:33 -05:00
Deluan 4bf4765442 Bot that adds a download link on pull requests 2021-11-19 13:07:55 -05:00
Brice Johnson 6d947f6f7e
Allowing 3rd party UIs to access `x-total-count` http header (#1470)
* Adding 'x-content-duratin' and 'x-total-count' to CORS exposed headers

* Moving cors setup to middlewares.go

* adding x-nd-authorization to exposed headers
2021-11-19 10:07:54 -05:00
Deluan Quintão 8c7d95c135
Update Translations (#1471)
* Update zh-Hans.json (POEditor.com)

* Update zh-Hant.json (POEditor.com)

* Update cs.json (POEditor.com)

* Update da.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update eo.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update de.json (POEditor.com)

* Update it.json (POEditor.com)

* Update ja.json (POEditor.com)

* Update fa.json (POEditor.com)

* Update pl.json (POEditor.com)

* Update pt.json (POEditor.com)

* Update ru.json (POEditor.com)

* Update sl.json (POEditor.com)

* Update es.json (POEditor.com)

* Update th.json (POEditor.com)

* Update tr.json (POEditor.com)

* Update uk.json (POEditor.com)

* Update fi.json (POEditor.com)
2021-11-18 17:52:12 -05:00
Deluan d4447e373f Fix sorting albums by year (should use name as secondary sort field).
Relates to https://github.com/navidrome/navidrome/issues/961#issuecomment-967624681
2021-11-17 21:47:14 -05:00
Steve Richter 3bd6f82c80
Rename ListenBrainz config flag and enable by default (#1443) 2021-11-17 21:11:53 -05:00
BIKI DAS da26c5cfe0
Combined multiple appends into a single one (#1464) 2021-11-17 19:51:44 -05:00
Deluan 023d7bfa8a Remove link from songs to artist (when artist has no albums) 2021-11-17 18:47:54 -05:00
Deluan 48a627885c Simplify prototype definition for taglib_read 2021-11-13 12:18:42 -05:00
Deluan 91b470c93b Show artist link in Songs lists 2021-11-05 20:25:12 -04:00
Deluan 1c82bf5179 Remove non-album artist_ids from the DB 2021-11-05 20:24:50 -04:00
Deluan 0d9dcebf32 Fix playlist cannot be empty via Subsonic API 2021-11-05 10:23:45 -04:00
Deluan 5994c31f4c Fix migration to support null values 2021-11-04 21:23:41 -04:00
Deluan 804fb716db Show in the logs how long it took to startup 2021-11-04 13:49:05 -04:00
Deluan d3a2f769b7 Better logging of GetSimilar call 2021-11-03 15:59:16 -04:00
Deluan 68a84ec832 Smarter cache of external info calls (last.fm / spotify) 2021-11-03 14:13:50 -04:00
Deluan 9e48d87f84 Add a new index for album, to optimize the `getAlbumList?type=alphabeticalByArtist` Subsonic query 2021-11-02 22:08:21 -04:00
Deluan 9712a5b1c6 Fix error codes for required parameters in getAlbumList 2021-11-02 21:38:08 -04:00
Deluan 9422373be0 Optimize AlbumRepository.GetAll and add a GetAllWithoutGenres method specifically for Subsonic API, where multiple-genres are not required 2021-11-02 21:19:49 -04:00
dependabot[bot] bc8132ef1f Bump @testing-library/user-event from 13.2.1 to 13.5.0 in /ui
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 13.2.1 to 13.5.0.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v13.2.1...v13.5.0)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-02 15:54:18 -04:00
dependabot[bot] 82bc8cd0aa Bump github.com/go-chi/httprate from 0.5.1 to 0.5.2
Bumps [github.com/go-chi/httprate](https://github.com/go-chi/httprate) from 0.5.1 to 0.5.2.
- [Release notes](https://github.com/go-chi/httprate/releases)
- [Commits](https://github.com/go-chi/httprate/compare/v0.5.1...v0.5.2)

---
updated-dependencies:
- dependency-name: github.com/go-chi/httprate
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-02 15:53:53 -04:00
dependabot[bot] 1e5ab59df8 Bump github.com/onsi/ginkgo from 1.16.4 to 1.16.5
Bumps [github.com/onsi/ginkgo](https://github.com/onsi/ginkgo) from 1.16.4 to 1.16.5.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v1.16.4...v1.16.5)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-02 15:52:39 -04:00
dependabot[bot] 28ad91a9d6 Bump github.com/Masterminds/squirrel from 1.5.0 to 1.5.1
Bumps [github.com/Masterminds/squirrel](https://github.com/Masterminds/squirrel) from 1.5.0 to 1.5.1.
- [Release notes](https://github.com/Masterminds/squirrel/releases)
- [Commits](https://github.com/Masterminds/squirrel/compare/v1.5.0...v1.5.1)

---
updated-dependencies:
- dependency-name: github.com/Masterminds/squirrel
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-02 15:52:15 -04:00
dependabot[bot] e40e86590c Bump github.com/microcosm-cc/bluemonday from 1.0.15 to 1.0.16
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.15 to 1.0.16.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.15...v1.0.16)

---
updated-dependencies:
- dependency-name: github.com/microcosm-cc/bluemonday
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-02 15:49:33 -04:00
Deluan 12818fb590 Make song/album/artist endpoints read-only 2021-11-02 14:44:50 -04:00
Deluan aaeaa3c590 Fix playlist filter 2021-11-02 12:56:43 -04:00
Deluan 053909196c More info in scrobbling logs 2021-11-02 12:25:29 -04:00
Deluan 6a87fc88f7 Ignores invalid timestamps in requests (use current time)
Fix this issue: https://www.reddit.com/r/navidrome/comments/ql3imf/scrobbling_fails_when_using_substreamer/
2021-11-02 10:33:40 -04:00
Deluan 975986ab16 Add bulk action to make playlists private/public
Better responsiveness
2021-11-01 21:27:36 -04:00
Deluan 778f474d26 Use new `rest` lib (Update receives all columns that need to be updated) 2021-11-01 21:27:36 -04:00
Deluan b2acec0a09 When externals services are disabled, only disable UILoginBackgroundURL if it is not set by the user 2021-11-01 09:11:32 -04:00
Deluan e7202339af Ignore empty lines in M3U files 2021-10-31 20:43:30 -04:00
Deluan 8c2e4da396 Fix `dateLoved` in criteria. Now log invalid field names in criteria 2021-10-31 20:31:11 -04:00
Deluan a4d3bf42a7 Remove some duplicated code 2021-10-31 15:08:06 -04:00
Deluan 765557d739 Remove "Show" button from PlaylistEdit view 2021-10-31 15:08:06 -04:00
Deluan 86afd16cc8 Allow changing playlist's owner. Relates to #698 2021-10-31 15:08:06 -04:00
Deluan 133fed344f Add `owner_id` to `playlist` 2021-10-31 15:08:06 -04:00
Deluan 84bbcdbfc2 Add artist image lightbox 2021-10-30 20:05:01 -04:00
Deluan 1823159b25 New config to disable all external integrations. Closes #102 2021-10-30 18:03:46 -04:00
Deluan 0b5ed9eb80 Update ListenBrainz Portuguese translations 2021-10-30 16:19:39 -04:00
Steve Richter a56d5bc850
Listenbrainz scrobbling (#1424)
* Refactor session_keys to its own package

* Adjust play_tracker

- Don't send external NowPlaying/Scrobble for tracks with unknown artist
- Continue to the next agent on error

* Implement ListenBrainz Agent and Auth Router

* Implement frontend for ListenBrainz linking

* Update listenBrainzRequest

- Don't marshal Player to json
- Rename Track to Title

* Return ErrRetryLater on ListenBrainz server errors

* Add tests for listenBrainzAgent

* Add tests for ListenBrainz Client

* Adjust ListenBrainzTokenDialog to handle errors better

* Refactor listenbrainz.formatListen and listenBrainzRequest structs

* Refactor agent auth_routers

* Refactor session_keys to agents package

* Add test for listenBrainzResponse

* Add tests for ListenBrainz auth_router

* Update ListenBrainzTokenDialog and auth_router

* Adjust player scrobble toggle
2021-10-30 12:17:42 -04:00
Steve Richter ccc871d1f7
Only reset player scrobbled state on track change or end (#1432)
* Only reset player scrobbled state on track change or end

* Only reset player start time on track change or end
2021-10-30 12:09:40 -04:00
Deluan d3e142233b Fix TypeError: Cannot read properties of undefined (reading 'length') 2021-10-29 18:10:17 -04:00
Deluan a42aeff88d Optimize queries by path, should speed up the scanner a bit 2021-10-29 13:11:51 -04:00
Deluan 7cdbc04c5e Update caniuse-lite 2021-10-29 11:49:10 -04:00
Deluan f3fae7e233 Optimize basic media_file query, avoiding adding "group by" or joining with genres if not required 2021-10-29 09:50:22 -04:00
Deluan 074732b1dc Filter playlists by names and comments 2021-10-28 13:58:06 -04:00
Deluan 66a9cbb7d9 Remove temp folders after tests 2021-10-28 10:40:31 -04:00
Deluan fa3471f527 Simplify `resources` code, enabling any resource to be overridden (not just translations) 2021-10-28 10:25:25 -04:00
Deluan 9072412812 Fix translations on Windows 2021-10-28 09:41:37 -04:00
Deluan cca32360db Use `refetch` when changing the playlist (as opposed to a full `refresh`) 2021-10-27 20:53:58 -04:00
Deluan 85d48478e8 Add `.mka` file format. Only works with `ffmpeg` extractor 2021-10-27 15:00:32 -04:00
Deluan 2183eb6498 Should not allow changing sort order in Album songs view 2021-10-27 14:35:58 -04:00
Deluan ea435d0f60 Fix error on empty playlists. Simplify code for some operations 2021-10-27 09:50:24 -04:00
Deluan f645c4769c Fix double escaped lyrics and comments 2021-10-26 19:33:21 -04:00
Deluan 5e87280750 Load playlist track genres 2021-10-26 18:46:08 -04:00
Deluan 526b6597c8 Remove duplication for loading tracks 2021-10-26 18:34:21 -04:00
Deluan 5dce499d6d Fix/Optimized Playlist tracks deletion 2021-10-26 14:05:28 -04:00
Deluan fbd87ba577 Fix console error "Cannot convert undefined or null to object PlaylistsSubMenu" 2021-10-26 14:05:05 -04:00
Deluan 63b5191ea7 Fix lint 2021-10-26 10:57:59 -04:00
Deluan af00503b77 Optimize playlist updates 2021-10-26 10:45:14 -04:00
Steve Richter 85185e3b98
Misc small changes (#1433)
* Fix React key warning in HelpDialog

* Change "lyric" to "lyrics" in en.json
2021-10-26 08:57:20 -04:00
Deluan 83eaafcbfb Add `dateLoved` Criteria field 2021-10-25 16:44:59 -04:00
Deluan 93ce0b5683 Fix Genre field and Contains/NotContains/StartsWith/EndsWith in Criteria (Smart Playlists) 2021-10-25 16:17:03 -04:00
Deluan 47549ecfc1 Increase updatePlaylist chunk to 100 tracks 2021-10-25 13:00:46 -04:00
Deluan ed1ca65ad5 Show hotkeys as chips, for easier reading 2021-10-25 11:14:43 -04:00
Deluan 8d6b5f9d02 Speed up Subsonic `GetPlaylist` (by optimizing `loadTracks`) 2021-10-25 11:14:20 -04:00
Deluan 76fdcd112b Tweak SimilarSongs algorithm to prioritize the requested main artist 2021-10-24 18:04:40 -04:00
Deluan 18e1c169f9 Don't read the whole smart playlist file in memory 2021-10-24 14:41:08 -04:00
whorfin 4bc4daa68f
Improve git-vs-tarball detection (#1423)
* Extract version from directory name if .git dir is missing

* Avoid using shell

* Remove .gitinfo build from pipeline

* Fix git-detecting rule to be robust in presence of setup-git
2021-10-23 21:27:19 -04:00
Deluan cc1659aa73 Better way to match top songs from external sources (Last.fm) 2021-10-23 20:26:30 -04:00
Deluan 31c598de07 Fix drag-n-drop from a playlist, also fix useDrag memoization 2021-10-23 20:25:28 -04:00
Deluan 2e2a647e67 Make SmartPlaylists read-only 2021-10-23 20:25:28 -04:00
Deluan d169f54e7d Rename hasCoverArt field in criteria 2021-10-23 20:25:28 -04:00
Deluan 1494be9aaa Add playCount and playDate columns to album songs list 2021-10-23 20:25:28 -04:00
Deluan c73f64ee3a Removed unused code 2021-10-23 20:25:28 -04:00
Deluan 806b13cf42 Update stats of Smart Playlist when it is created
Also fix loadTracks
2021-10-23 20:25:28 -04:00
Deluan 2c860edeb5 Don't import invalid `.nsp` files 2021-10-23 20:25:28 -04:00
Deluan 6a550dab77 Use new Criteria and remove SmartPlaylist struct 2021-10-23 20:25:28 -04:00
Deluan 3972616585 New Criteria API 2021-10-23 20:25:28 -04:00
Deluan d0ce030386 Add PlayCount and PlayDate columns to PlaylistSongs 2021-10-23 20:25:28 -04:00
Deluan 947353610c Include never played songs in the "not in the last" operator 2021-10-23 20:25:28 -04:00
Deluan 2b57b98a4b Fix smart playlist refreshing only after the tracks were loaded 2021-10-23 20:25:28 -04:00
Deluan 1a96e9fe65 Import smart playlists (extension .nsp) 2021-10-23 20:25:28 -04:00
Deluan 21da1df4ea Cache smart playlist refreshes for 5 seconds 2021-10-23 20:25:28 -04:00
Deluan d21932bd1b First version of SmartPlaylists being generated on demand 2021-10-23 20:25:28 -04:00
Deluan c72add516a Add methods to Playlist model
Also, don't load genres for Playlists tracks (not necessary for now)
2021-10-23 20:25:28 -04:00
Deluan d200933b68 Reduce number of queries for some playlists operations.
Also allow admins to update/delete playlists from other users in the Subsonic API. Closes #1366
2021-10-23 20:25:28 -04:00
Deluan 943082ef4e Fix time-based tests (again) 2021-10-23 20:25:28 -04:00
Deluan c3fb4e1282 Fix rules serialization 2021-10-23 20:25:28 -04:00
Deluan 9c8f779f42 Fix time-based tests 2021-10-23 20:25:28 -04:00
Deluan 815623715e Load SmartPlaylists rules from DB 2021-10-23 20:25:28 -04:00
Deluan 7221b49b98 More tests 2021-10-23 20:25:28 -04:00
Deluan cf8d08ec26 Initial drafts for Smart Playlists 2021-10-23 20:25:28 -04:00
Deluan 2a756eab88 Show external links on all resolutions but mobile 2021-10-21 10:30:53 -04:00
Deluan 104679ca6e Guard against record being `undefined`. Fix error `Cannot read properties of undefined (reading 'id')` 2021-10-19 20:22:56 -04:00
Dheeraj Lalwani 5621551dd0
Adds Lyrics Support to Subsonic API (#1379)
* Add function 'isSynced' that identifies if lyrics are synced or not and add tests for the same

* implement 'getLyrics' which returns lyrics if they exist

Signed-off-by: Dheeraj Lalwani <lalwanidheeraj1234@gmail.com>

* remove timestamps frorom the the lyrics if they are synced, fix filters & clean up code

Signed-off-by: Dheeraj Lalwani <lalwanidheeraj1234@gmail.com>

* add snapshot tests for the 'Lyrics' response & add some clean up

Signed-off-by: Dheeraj Lalwani <lalwanidheeraj1234@gmail.com>

* add tests for 'GetLyrics' function

Signed-off-by: Dheeraj Lalwani <lalwanidheeraj1234@gmail.com>

* update the snapshot test & the test for 'GetLyrics' function

Signed-off-by: Dheeraj Lalwani <lalwanidheeraj1234@gmail.com>
2021-10-19 16:33:06 -04:00
Deluan 3214783ce9 Remove double-retching playlist's tracks 2021-10-19 12:54:21 -04:00
Chirag Lulla 34b01c2cbf
Display lyrics on UI if synced lyrics present in metadata (#1406)
Signed-off-by: Chirag Lulla <lullachirag239@gmail.com>
2021-10-19 10:21:20 -04:00
Deluan 0d2a8f5338 Enable new Artist Page by default 2021-10-17 11:07:59 -04:00
Deluan b7fedddfd8 Guard against record being `undefined`. Fix error `Cannot read properties of undefined (reading 'albumId')` 2021-10-16 20:29:31 -04:00
Dnouv 1d742cf8c7
Artist page improvements (#1391)
* Seperate mobile desktop components

* Fix err

* Rename classes and fix some styles

* Add lastFM button and remove console log

* Add Mbiz Icon

* render bio as dangerouslySetInnerHTML and remove unused css classes

* Add Fav and Stars

* Remove unstandardised class selector

* Remove ext link from m view

* Fix naming and simplify rounded styling

* Refactor ArtistShow:

- Extracted DesktopArtistDetails to its own file
- Removed album count as it was incorrect, it is not considering compilations
- Show bio and image from Native API, if it is available, before calling `getArtistInfo`

Co-authored-by: Deluan <deluan@navidrome.org>
2021-10-15 21:02:11 -04:00
Deluan 7505b5c554 Bump GoLang to 1.17.2 2021-10-13 09:53:44 -04:00
Deluan 174ad9e9da Fix ffmpeg bitrate parsing for flac files 2021-10-12 22:02:24 -04:00
certuna ba0ee6aba4
Rename manifest.json to manifest.webmanifest (#1399)
* Rename manifest.json to manifest.webmanifest

browser consoles keep complaining that the manifest doesn't have the `.webmanifest` extension.

* FIx manifest.webmanifest references

Co-authored-by: Deluan <deluan@navidrome.org>
2021-10-12 20:06:09 -04:00
Deluan Quintão 6b38acad49
Update README.md 2021-10-08 10:51:09 -04:00
Deluan ee8943f338 Fix semantic classes for currently playing song
Fix #1364
2021-10-07 19:49:27 -04:00
Serguey Parkhomovsky 86a87b4bb1
Fix default volume (#1395)
With the update in #1378, the default volume is now erroneously set to 25% instead of 50%. Remove the Math.pow and set it to 50% instead.
2021-10-07 17:21:08 -04:00
Deluan 8bbb878bb3 Add Finnish translation 2021-10-06 18:11:02 -04:00
Deluan Quintão 8591a9acdf
Update translations (#1383)
* Add Persian translation

* Update cs.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update ja.json (POEditor.com)

* Update pt.json (POEditor.com)

* Update ru.json (POEditor.com)

* Update sl.json (POEditor.com)

* Fix "Shared Playlists" pt translation

* Update de.json (POEditor.com)

* Update it.json (POEditor.com)

* Update sl.json (POEditor.com)

* Update es.json (POEditor.com)

* Update zh-Hant.json (POEditor.com)

* Update th.json (POEditor.com)
2021-10-06 18:08:29 -04:00
Deluan b6e30cd01f Return playlists sorted in `getPlaylists` Subsonic endpoint 2021-10-05 14:47:57 -04:00
Deluan Quintão 058e7e4374
Add logo to README 2021-10-05 12:15:44 -04:00
Deluan 152836a01c Bump @testing-library/react from 12.1.1 to 12.1.2 in /ui 2021-10-04 17:30:01 -04:00
Deluan 6139338e80 Bump react-icons from 4.2.0 to 4.3.1 in /ui 2021-10-04 17:29:27 -04:00
Deluan f0c11916ca Revert: Small optimization in genre mapping 2021-10-04 17:28:45 -04:00
Dnouv a6311259fd
Fix layout error in ArtistShow (#1387) 2021-10-04 17:14:38 -04:00
Deluan dbde0ffa0c Bump github.com/djherbis/atime to v1.1.0 2021-10-03 22:50:25 -04:00
Deluan fba733708c Sort songs by artist/album/disc/track_number before adding to playlist 2021-10-02 21:55:45 -04:00
Deluan e673360087 Limit number of playlists displayed in the sidebar, to avoid UI freezes 2021-10-02 21:39:33 -04:00
Deluan 2b105ca77b Enable DevSidebarPlaylists by default.
Closes #771
2021-10-02 13:56:42 -04:00
Deluan 9c29ee3651 Check permissions before adding songs to playlists 2021-10-02 13:23:17 -04:00
Deluan 6c3e45de41 Add songs to playlists with drag and drop 2021-10-02 13:14:33 -04:00
dependabot[bot] 2ab4647420
Bump golang.org/x/tools from 0.1.6 to 0.1.7 (#1382)
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.1.6 to 0.1.7.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.1.6...v0.1.7)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-01 16:38:04 -04:00
Deluan Quintão 91e36a2c18
Check goimports in the pipeline (#1381)
* Check goimports in the pipeline

* Check goimports in the pipeline

* Check goimports in the pipeline

* go mod tidy

* wip

* wip

* Fix goimports and go:build tags

* Run golangci-lint before goimports
2021-10-01 15:32:24 -04:00
dependabot[bot] 0cbba80284
Bump react-router-dom from 5.2.0 to 5.3.0 in /ui (#1330)
Bumps [react-router-dom](https://github.com/ReactTraining/react-router) from 5.2.0 to 5.3.0.
- [Release notes](https://github.com/ReactTraining/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ReactTraining/react-router/compare/v5.2.0...v5.3.0)

---
updated-dependencies:
- dependency-name: react-router-dom
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-10-01 10:34:37 -04:00
Deluan f9d910473f Bump react-admin to 3.18.3 2021-10-01 09:45:28 -04:00
Igor Rzegocki be3a6dc7a3
Use local copy of workbox service worker scripts (#1358)
* Use local copy of workbox service worker scripts

* Refactor workbox integration:

- Only add prod js, without maps. Reduces the size from 170k to 24k
- Removed it from build. As it is small now, we can add it to source, and have a script to just update it whenever it is required
- Fixed relative paths in navidrome-service-worker.js, should now work with BaseUrl != ''

Co-authored-by: Deluan <deluan@navidrome.org>
2021-10-01 09:14:15 -04:00
Deluan b1e7760996 Preload next song 2021-10-01 08:43:59 -04:00
Deluan ad45ab4a04 Fix genre update chunking 2021-10-01 08:43:59 -04:00
Serguey Parkhomovsky 24fef584ad
Bump react-jinke-music-player from 4.24.0 to 4.24.2 (#1378)
This should fix #1367.
2021-10-01 08:42:02 -04:00
Deluan e17d436902 Do not attach Genres to the "Various Artists" artist 2021-09-27 21:55:33 -04:00
Deluan 73659e5669 Change "Build" link to point to the latest build artifacts from master 2021-09-27 18:17:53 -04:00
Deluan 71b1e7f870 Bump github.com/lestrrat-go/jwx from 1.2.6 to 1.2.7 2021-09-27 13:29:31 -04:00
Deluan 694056010c Bump @testing-library/react from 12.1.0 to 12.1.1 in /ui 2021-09-27 13:27:59 -04:00
Deluan 4fda895a64 Bump blueimp-md5 from 2.18.0 to 2.19.0 in /ui 2021-09-27 13:26:31 -04:00
Deluan f664af5559 Bump react-admin from 3.18.1 to 3.18.2 in /ui 2021-09-27 13:26:31 -04:00
Deluan c6868ff8a0 Don't show Artist Page for "Various Artists" 2021-09-27 11:52:23 -04:00
Deluan 0b65a4e34e Fix comment word wrapping 2021-09-27 09:48:31 -04:00
Deluan 24872e6c2a Fix biography word wrapping and requests for `undefined` resource 2021-09-27 09:47:16 -04:00
Deluan b4e5c662dc Fix JS console warning 2021-09-26 17:36:30 -04:00
Deluan 6752e0a17d Fix harmless error message in logs when ScanSchedule set was "0"
Message:
`ERRO[0000] Error scheduling periodic scan                error="expected exactly 5 fields, found 1: [0]"`
2021-09-26 15:57:27 -04:00
Deluan 5680e53949 Update genres in chunks. Should fix #1368 2021-09-26 15:55:52 -04:00
Dnouv 482c2dec0c
Artist Detail Page (first cut) (#1287)
* Configure fetching from API and route

* pretty

* Remove errors

* Remove errors

* Remove errors

* Complete page for Desktop view

* Fix error

* Add xs Artist page

* Remove unused import

* Add styles for theme

* Change route path

* Remove artId useEffect array

* Remove array

* Fix cover load err

* Add redirect on err

* Remove route

* What's in a name? consistency :)

* Fix err

* Fix UI changes

* Fetch album from resource

* Renaming done

* Review changes

* Some touch-up

* Small refactor, to make naming and structure more consistent with AlbumShow

* Make artist's album list similar to original implementation

* Reuse AlbumGridView, to avoid duplication

* Add feature flag to enable new Artist Page, default false

* Better biography styling. Small refactorings,

* Don't encode quotes and other symbols

* Moved AlbumShow to correct folder

Co-authored-by: Deluan <deluan@navidrome.org>
2021-09-26 15:32:40 -04:00
caiocotts 210dc6b12e
Add x-total-count to Subsonic API getAlbumList (#1360)
* Add x-total-count to Subsonic API getAlbumList

* Rename variable

Co-authored-by: Deluan <deluan@navidrome.org>
2021-09-21 20:40:39 -04:00
Deluan 73a2271cdd Small optimization in genre mapping 2021-09-21 13:37:44 -04:00
Samarjeet 0c0bd2967d
Replace expanded with a dialog (#1258)
* Replace expanded with a dialog

* Change `info` label to "Get Info"

* Rename things for consistency

Co-authored-by: Deluan <deluan@navidrome.org>
2021-09-20 20:30:43 -04:00
Deluan 15ae3d47cf Only apply audioStreamRx once 2021-09-20 19:33:50 -04:00
Deluan 1365adbb39 Support 7.1 (8) channels 2021-09-20 19:33:50 -04:00
Miguel A. Arroyo e12a14a87d feat: Adds Audio Channel Metadata - #1036 2021-09-20 19:33:50 -04:00
Deluan 0079a9b938 Close Sidebar when going to Playlists list 2021-09-20 19:00:37 -04:00
Deluan 892c2bfd58 Revert "Disable mini-player (bubble) dragging. Should fix #1217"
This reverts commit abf6318a8b.
2021-09-20 18:24:18 -04:00
dependabot[bot] 5fb7328965 Bump golang.org/x/tools from 0.1.5 to 0.1.6
Bumps [golang.org/x/tools](https://github.com/golang/tools) from 0.1.5 to 0.1.6.
- [Release notes](https://github.com/golang/tools/releases)
- [Commits](https://github.com/golang/tools/compare/v0.1.5...v0.1.6)

---
updated-dependencies:
- dependency-name: golang.org/x/tools
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-20 18:18:45 -04:00
Deluan 86479a6d06 More info when recovering from panic 2021-09-20 18:16:22 -04:00
Deluan 76bd20e8ff Recover from any possible taglib panics. Fixes #1343 2021-09-20 18:09:39 -04:00
dependabot[bot] 7a15ed0740 Bump prettier from 2.4.0 to 2.4.1 in /ui
Bumps [prettier](https://github.com/prettier/prettier) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.4.0...2.4.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-20 17:45:50 -04:00
dependabot[bot] d574df5fd7 Bump github.com/go-chi/jwtauth/v5 from 5.0.1 to 5.0.2
Bumps [github.com/go-chi/jwtauth/v5](https://github.com/go-chi/jwtauth) from 5.0.1 to 5.0.2.
- [Release notes](https://github.com/go-chi/jwtauth/releases)
- [Commits](https://github.com/go-chi/jwtauth/compare/v5.0.1...v5.0.2)

---
updated-dependencies:
- dependency-name: github.com/go-chi/jwtauth/v5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-20 17:45:25 -04:00
dependabot[bot] a7ace48efb Bump github.com/ReneKroon/ttlcache/v2 from 2.8.0 to 2.8.1
Bumps [github.com/ReneKroon/ttlcache/v2](https://github.com/ReneKroon/ttlcache) from 2.8.0 to 2.8.1.
- [Release notes](https://github.com/ReneKroon/ttlcache/releases)
- [Changelog](https://github.com/ReneKroon/ttlcache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ReneKroon/ttlcache/compare/v2.8.0...v2.8.1)

---
updated-dependencies:
- dependency-name: github.com/ReneKroon/ttlcache/v2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-20 17:44:52 -04:00
dependabot[bot] 84d98010d3 Bump github.com/spf13/viper from 1.8.1 to 1.9.0
Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.8.1 to 1.9.0.
- [Release notes](https://github.com/spf13/viper/releases)
- [Commits](https://github.com/spf13/viper/compare/v1.8.1...v1.9.0)

---
updated-dependencies:
- dependency-name: github.com/spf13/viper
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-09-20 17:44:02 -04:00
Deluan e63804fbdd Use newer versions of node in the pipeline 2021-09-18 22:38:55 -04:00
Deluan a5101fa9b4 Use npm dependencies cache from setup-node@v2 2021-09-18 22:38:55 -04:00
dependabot[bot] f2ed3f2d86
Bump prettier from 2.3.2 to 2.4.0 in /ui (#1341)
Bumps [prettier](https://github.com/prettier/prettier) from 2.3.2 to 2.4.0.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.3.2...2.4.0)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-13 16:12:29 -04:00
dependabot[bot] b23ab1ccec
Bump @testing-library/react from 12.0.0 to 12.1.0 in /ui (#1342)
Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 12.0.0 to 12.1.0.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v12.0.0...v12.1.0)

---
updated-dependencies:
- dependency-name: "@testing-library/react"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-09-13 16:10:07 -04:00
Deluan abf6318a8b Disable mini-player (bubble) dragging. Should fix #1217 2021-09-13 11:17:06 -04:00
Deluan b55f3a6946 Add paddingBottom to the whole sidebar menu, to avoid playlists to be covered by the player 2021-09-12 21:24:07 -04:00
Deluan ab2912b4fa Only import playlists from configured paths in option `PlaylistsPath`. Closes #1181
Syntax is Ant-style Globs, with support for '**' (any subfolder). Default: '.:**' (or '.;**' in Windows`, meaning all folders and subfolders under `MusicFolder`
2021-09-12 21:07:51 -04:00
Deluan 9f00aad216 Upgrade to GoLang 1.17.1 2021-09-12 14:14:33 -04:00
Deluan Quintão 79363d6c07
Move Playlists to the sidebar menu (#1339)
* Show playlists in sidebar menu

* Fix menu

* Refresh playlist submenu when adding new playlist

* Group shared playlists below user's playlists

* Fix text overflow in menu options

* Add button in playlist menu to go to Playlists list

* Add config option `DevSidebarPlaylists` to enable this feature (default false)
2021-09-11 13:11:15 -04:00
Deluan a7017e4bb0 Fix JS console warning 2021-09-10 18:04:38 -04:00
Deluan dc0ec32dbf Fix menu items highlight 2021-09-10 16:17:08 -04:00
Salman Inayat 06b1a1a25c
Album size overflow fixed (#1071)
* Added back button

* Added back button

* Added back button

* Fixed Album size overflow

* Fixed Album size overflow

* Fixed album size overflowing

* Fixed album size overflowing

* Fixed album size overflowing

* Fixed album size overflow on small screen

* Changes reverted in PlayerEdit.js

* prettier formatting issue resolved

Co-authored-by: Deluan <deluan@navidrome.org>
2021-09-09 11:56:48 -04:00
Deluan 6ac2fefaf3 Make AppBar stick on scroll 2021-09-09 11:21:16 -04:00
Samarjeet 2e921cd793
Fix sidebar scroll height (#1338)
* Fix sidebar scroll height

* Prettier

Co-authored-by: Deluan <deluan@navidrome.org>
2021-09-09 09:33:38 -04:00
Deluan Quintão c2251e6ddc
Update GoLang to 1.17 (#1295)
* Update GoLang to 1.17

* Rename pipeline jobs
2021-09-09 00:26:56 -04:00
Deluan 25aab8bcb5 go mod tidy 2021-09-09 00:17:43 -04:00
Deluan 94083f85d2 Bump @testing-library/react-hooks version 2021-09-09 00:16:44 -04:00
Deluan f0ef5187b2 Bump react-redux version 2021-09-09 00:16:13 -04:00
Deluan 79e79b6366 Bump github.com/go-chi/chi 2021-09-09 00:15:16 -04:00
Deluan a961ffe2e1 Bump github.com/golangci/golangci-lint 2021-09-09 00:14:18 -04:00
Deluan 402aaaaf45 Bump github.com/lestrrat-go/jwx version 2021-09-09 00:13:39 -04:00
Deluan 5c7784f842 Bump github.com/cespare/reflex version 2021-09-09 00:13:06 -04:00
Deluan a4e96d25a8 Pin Node to 16.8 as a workaround to https://github.com/nodejs/node/issues/40030 2021-09-08 23:28:28 -04:00
Deluan 8444c28bed Upgrade react-admin to 3.18.1
This makes the sidebar fixed when users scroll vertically. This supersedes #1024, even though it also hides the hamburger menu...
2021-09-08 22:50:03 -04:00
Tucker Kern fb11080545
Improve performance of placeholder images (#1325)
* Don't include updatedAt field when fetching album art placeholder. This will allow browers to cache the place holder

* Apply resizing to placeholder image

* Fix issues discovered by CI linter and prettier

* Updates from PR review
2021-09-06 22:34:37 -04:00
whorfin 173b30cd59
Extract version from directory name if .git dir is missing (#1327)
* Extract version from directory name if .git dir is missing

* Avoid using shell

* Remove .gitinfo build from pipeline
2021-09-06 21:26:04 -04:00
Deluan af7c87dd7b Give a warning on commands that do not build the frontend.
This is to avoid confusions like this:
https://github.com/navidrome/navidrome/issues/1297#issuecomment-913007331
2021-09-05 21:39:19 -04:00
Artyom 8df056b797
ru.json update (#1320) 2021-09-05 10:56:54 -04:00
caiocotts 54f98497c2
Use wchar_t for TagLib filenames on Windows (#1310)
* Use wchar_t for tagLib filenames on Windows

* Make TagLib default extractor for all platforms.

* Organize imports

Co-authored-by: Deluan <deluan@navidrome.org>
2021-08-28 19:35:54 -04:00
dependabot[bot] c55e65902b
Bump github.com/onsi/gomega from 1.15.0 to 1.16.0 (#1300)
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.15.0 to 1.16.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.15.0...v1.16.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-23 20:50:46 -04:00
dependabot[bot] d37231f319
Bump github.com/ReneKroon/ttlcache/v2 from 2.7.0 to 2.8.0 (#1298)
Bumps [github.com/ReneKroon/ttlcache/v2](https://github.com/ReneKroon/ttlcache) from 2.7.0 to 2.8.0.
- [Release notes](https://github.com/ReneKroon/ttlcache/releases)
- [Changelog](https://github.com/ReneKroon/ttlcache/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ReneKroon/ttlcache/compare/v2.7.0...v2.8.0)

---
updated-dependencies:
- dependency-name: github.com/ReneKroon/ttlcache/v2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-23 20:47:36 -04:00
dependabot[bot] 47fa32ec93
Bump github.com/golangci/golangci-lint from 1.41.1 to 1.42.0 (#1299)
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.41.1 to 1.42.0.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.41.1...v1.42.0)

---
updated-dependencies:
- dependency-name: github.com/golangci/golangci-lint
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-23 17:41:55 -04:00
Deluan cf042ed83d Fix random volume changes 2021-08-22 12:18:26 -04:00
Deluan d481864035 Some small refactorings 2021-08-22 12:16:49 -04:00
Deluan c2927e105b Fix loadSimilar method, was causing "internal error". Fix #1293 2021-08-21 19:37:00 -04:00
Deluan 6983390ca3 Setup git hooks when running `make setup` 2021-08-21 13:45:34 -04:00
Deluan 143f5ba9d5 Import song duration with hundredths when using TagLib
This is how ffmpeg extractor currently works, and it makes album durations more precise.
2021-08-20 21:42:01 -04:00
Deluan 05e27095b2 Fix getTopSongs endpoint 2021-08-19 08:17:22 -04:00
Tucker Kern aa72d3d41b
Add missing song information to players and apply EnableCoverAnimation to mobile player. (#1268)
* Disable mobile player cover animation when EnableCoverAnimation is set to false. Also increase cover art size and remove rounded borders.

* Display song album and year in mobile player view

* Remove default singer element from mobile player and reduce vertical white space

* Only add song year if it exists

* Add song year to desktop player when present

* Increase non-animated cover size to 85% and set a limit on the width of 600px.

* Explain what what the styles impact

* Remove unused style for songArtist

* Apply prettier

* Adjust player styles to handle nonsquare album art better. Should probably push this upstream too

* Also fix desktop player's handling of non square cover art.
2021-08-17 13:57:48 -04:00
ToadCast a20bd5fe05
Correct some french translations (#1278) 2021-08-13 20:27:37 -04:00
Deluan Quintão 99aeaabf7d
Update translations (#1285)
* Update es.json (POEditor.com)

* Update zh-Hans.json (POEditor.com)

* Update zh-Hant.json (POEditor.com)

* Update cs.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update de.json (POEditor.com)

* Update it.json (POEditor.com)

* Update ja.json (POEditor.com)

* Update pl.json (POEditor.com)

* Update pt.json (POEditor.com)

* Update sl.json (POEditor.com)

* Update es.json (POEditor.com)

* Update th.json (POEditor.com)

* Update uk.json (POEditor.com)
2021-08-13 20:27:18 -04:00
dependabot[bot] 0a5f966047 Bump redux from 4.1.0 to 4.1.1 in /ui
Bumps [redux](https://github.com/reduxjs/redux) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/reduxjs/redux/releases)
- [Changelog](https://github.com/reduxjs/redux/blob/master/CHANGELOG.md)
- [Commits](https://github.com/reduxjs/redux/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: redux
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-13 20:11:05 -04:00
dependabot[bot] 5338567346 Bump github.com/onsi/gomega from 1.14.0 to 1.15.0
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.14.0...v1.15.0)

---
updated-dependencies:
- dependency-name: github.com/onsi/gomega
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-13 19:57:12 -04:00
dependabot[bot] 50526024b2 Bump github.com/lestrrat-go/jwx from 1.2.4 to 1.2.5
Bumps [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) from 1.2.4 to 1.2.5.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/main/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v1.2.4...v1.2.5)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-13 19:56:57 -04:00
Deluan db4165f27c Bum react-admin to 3.17.2. Fix SearchInput border 2021-08-13 19:45:18 -04:00
Deluan a8cf887e87 Adjust icon size 2021-08-03 22:01:57 -04:00
Deluan Quintão 63000ab5ae Update en.json (POEditor.com) 2021-08-03 21:41:32 -04:00
Deluan b44ef81c6f Allow `0` value to disable ScanSchedule.
Seems that Viper does not recognize empty environment vars as valid values

Closes #1274
2021-08-03 19:37:36 -04:00
caio e9d0abe0bc Support local paths as urls in playlists. 2021-08-02 23:53:47 -04:00
Deluan bcafe88ef9 Don't autoplay when reloading play queue from Redux store 2021-08-01 19:49:17 -04:00
dependabot[bot] 1710730b45
Bump github.com/kr/pretty from 0.2.1 to 0.3.0 (#1267)
Bumps [github.com/kr/pretty](https://github.com/kr/pretty) from 0.2.1 to 0.3.0.
- [Release notes](https://github.com/kr/pretty/releases)
- [Commits](https://github.com/kr/pretty/compare/v0.2.1...v0.3.0)

---
updated-dependencies:
- dependency-name: github.com/kr/pretty
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-08-01 12:56:55 -04:00
Deluan aa1571e074 Remove unused AnnotatedModel interface 2021-08-01 12:04:45 -04:00
Deluan c831dc4cdf Use `structs` lib to map models to DB. Fix #1266 2021-08-01 12:04:45 -04:00
Deluan 344d7a4392 Inject DB into DataStore, instead of hardcode the dependency 2021-07-31 20:15:20 -04:00
Deluan c0fc36da63 Make album genres clickable 2021-07-27 13:22:35 -04:00
Deluan e68b22ea5d Don't send invalid scrobbles when clearing the player's queue 2021-07-26 16:50:50 -04:00
Sitansh Rajput fb4eefced5
"Add to Playlist" on AlbumList actions (#1257)
* added a dependency npm was complaining about

added playlist to album actions

* removed chokidar dependency

Co-authored-by: Skrtansh Rajput <srajput@alienvault.com>
2021-07-26 15:00:38 -04:00
Deluan 615cac2ec4 Extract ExternalLinks into its own component 2021-07-26 13:25:31 -04:00
dependabot[bot] 72f9e3e80a Bump @testing-library/user-event from 13.2.0 to 13.2.1 in /ui
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 13.2.0 to 13.2.1.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v13.2.0...v13.2.1)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-07-26 13:25:19 -04:00
Deluan 763bcafdac Use Tooltip in links to external sites 2021-07-24 19:52:15 -04:00
Deluan 5b81f7a73a Fix get song by id 2021-07-24 18:54:22 -04:00
Deluan 7bd506accc Retrieve all options for Genre filters 2021-07-24 16:00:27 -04:00
Deluan b66c39451f Fix build 2021-07-24 11:45:46 -04:00
Deluan 4ed01bad86 Use `ffmpeg` extractor by default on Windows
This is to avoid issue with unicode chars in filenames. See #810
2021-07-24 11:35:49 -04:00
Deluan d3975d206a Reorganize metadata extractors code 2021-07-24 11:10:19 -04:00
Deluan Quintão 6175629bb4
Build and release Docker image for Linux 32 bits platform (#1260) 2021-07-24 07:51:12 -04:00
Deluan 6c550819fd Use TagLib to detect whether a media file has embedded cover or not 2021-07-24 01:59:53 -04:00
Deluan 91325071a6 Change fallback extractor to taglib, the default option 2021-07-22 23:08:47 -04:00
Deluan 876dda83f2 Reduce number of calls to `lstat`.
Should make the scanner a bit faster, specially in networked filesystems
2021-07-21 22:17:37 -04:00
Deluan 86c0b422f6 Small refactorings 2021-07-21 12:46:30 -04:00
Deluan 1cef44a543 Show in the logs which mbid will be used if multiple mbids are found for album/artist 2021-07-21 11:12:03 -04:00
Deluan 4fcb238295 Fix "too many SQL variables" error in `GetStarred` endpoint 2021-07-21 10:45:52 -04:00
Deluan 4f9d546da4 Abort startup if config file is invalid 2021-07-20 22:35:22 -04:00
Deluan eeb14f0243 Removed unused function 2021-07-20 20:50:59 -04:00
Deluan a89bdfbb8d Fix build 2021-07-20 20:22:15 -04:00
Deluan 8afa2cd833 Remove dependency of deprecated `ioutil` package 2021-07-20 20:12:28 -04:00
Deluan 774ad65155 Use fs.FS in MergeFS implementation 2021-07-20 19:54:44 -04:00
Deluan 7540881695 Small refactorings 2021-07-20 19:18:29 -04:00
Deluan 08840f6170 Simplify cover detection in roll-up code by left-joining synthesized table 2021-07-20 17:45:08 -04:00
Deluan cddd1b3f6b Simplify genre roll-up code by left-joining synthesized tables 2021-07-20 17:45:08 -04:00
Deluan bc6b175414 Make `getGenre` Subsonic endpoint returns genres sorted by counts 2021-07-20 17:45:08 -04:00
Deluan b6e9ec4db4 Optimize GetAll genres query 2021-07-20 17:45:08 -04:00
Deluan 1471e1240d Show songs' genres as text instead of Chips 2021-07-20 17:45:08 -04:00
Deluan 95181d748d Fix rollup of track genres to albums and artists.
See: https://github.com/navidrome/navidrome/pull/1251#issuecomment-882343022
2021-07-20 17:45:08 -04:00
Deluan 254e5673e1 Fix log message about artist with Various Artists' mbid 2021-07-20 17:45:08 -04:00
Deluan 00e418cb2a Fix log message about multiple MBIDs 2021-07-20 17:45:08 -04:00
Deluan 2742977c63 Fix multiple id3v2.4 genres appearing as one big concatenated genre 2021-07-20 17:45:08 -04:00
Deluan 69f71be98a Add more tests 2021-07-20 17:45:08 -04:00
Deluan 58ee4c60ca Add Links to external sites 2021-07-20 17:45:08 -04:00
Deluan 21cd50d81c Fix aggregated values (count, size, duration) in roll-up queries 2021-07-20 17:45:08 -04:00
Deluan 054b5eafdb Add Genres as "Chips" in Album details and Song details 2021-07-20 17:45:08 -04:00
Deluan e2233779f1 Force full rescan when adding multi-genres 2021-07-20 17:45:08 -04:00
Deluan 3a356499ae Fix lint error 2021-07-20 17:45:08 -04:00
Deluan a0cd585401 Fix Count methods 2021-07-20 17:45:08 -04:00
Deluan 20b7e5c49b Add Genre filters to UI 2021-07-20 17:45:08 -04:00
Deluan c56c7c865e Purge unused genres at the end of the scan 2021-07-20 17:45:08 -04:00
Deluan b56e034ce3 Add multiple genres to Artists 2021-07-20 17:45:08 -04:00
Deluan 1d8607ef6a Remove unnecessary repositories methods 2021-07-20 17:45:08 -04:00
Deluan 5e54925520 Add multiple genres to Albums 2021-07-20 17:45:08 -04:00
Deluan 39da741a80 Add multiple genres to MediaFile 2021-07-20 17:45:08 -04:00
Deluan 7cd3a8ba67 Add genre tables, read multiple-genres from tags 2021-07-20 17:45:08 -04:00
Deluan 1f0314021e Change initial scan message log level 2021-07-19 16:53:58 -04:00
Deluan 19c2ef3803 Enable buffered scrobbles by default 2021-07-19 16:10:37 -04:00
dependabot[bot] d886c63122
Bump react-image-lightbox from 5.1.1 to 5.1.4 in /ui (#1252)
Bumps [react-image-lightbox](https://github.com/frontend-collective/react-image-lightbox) from 5.1.1 to 5.1.4.
- [Release notes](https://github.com/frontend-collective/react-image-lightbox/releases)
- [Changelog](https://github.com/frontend-collective/react-image-lightbox/blob/master/CHANGELOG.md)
- [Commits](https://github.com/frontend-collective/react-image-lightbox/compare/v5.1.1...v5.1.4)

---
updated-dependencies:
- dependency-name: react-image-lightbox
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-19 13:20:44 -04:00
dependabot[bot] 5b828cd7ef
Bump @testing-library/user-event from 13.1.9 to 13.2.0 in /ui (#1253)
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 13.1.9 to 13.2.0.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v13.1.9...v13.2.0)

---
updated-dependencies:
- dependency-name: "@testing-library/user-event"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-19 13:20:28 -04:00
Deluan ef60db3a5f Bump github.com/lestrrat-go/jwx from 1.2.2 to 1.2.4 2021-07-19 13:17:55 -04:00
Deluan 882b02c747 Fix forceRescan not re-importing all tracks 2021-07-19 10:32:33 -04:00
Deluan 44e7502aef Log warning when artist has a MBID of Various Artists 2021-07-18 18:28:51 -04:00
Deluan e61cf3217d Reapply the fix from #1054, but without getting into an infinite look in case of SMB fs errors. See #1164 2021-07-17 21:06:53 -04:00
Deluan 03ad6e972a Removed unused attributes in Last.fm responses 2021-07-16 21:06:47 -04:00
Deluan eb8ffc6f76 Fix infinite loop when the fs fails. Closes #1164 2021-07-16 09:38:58 -04:00
Deluan b0fc684cb6 Fix small lint errors found by gocritic 2021-07-15 13:43:03 -04:00
Deluan 8d56ec898e Use AlbumArtist tag even for compilations, when it is specified.
If the tracks' AlbumArtists are different, then use "Various Artists"
2021-07-15 11:53:08 -04:00
Deluan Quintão 5064cb2a46
Add git version info to release source (#1250) 2021-07-15 09:49:34 -04:00
Deluan f78257235e Add option to have different loglevels per source folder/file 2021-07-14 18:44:14 -04:00
dependabot[bot] 1a6a284bc1
Bump github.com/google/uuid from 1.2.0 to 1.3.0 (#1249)
Bumps [github.com/google/uuid](https://github.com/google/uuid) from 1.2.0 to 1.3.0.
- [Release notes](https://github.com/google/uuid/releases)
- [Commits](https://github.com/google/uuid/compare/v1.2.0...v1.3.0)

---
updated-dependencies:
- dependency-name: github.com/google/uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-14 11:09:23 -04:00
dependabot[bot] 1d948beb1f
Bump github.com/go-chi/httprate from 0.5.0 to 0.5.1 (#1248)
Bumps [github.com/go-chi/httprate](https://github.com/go-chi/httprate) from 0.5.0 to 0.5.1.
- [Release notes](https://github.com/go-chi/httprate/releases)
- [Commits](https://github.com/go-chi/httprate/compare/v0.5.0...v0.5.1)

---
updated-dependencies:
- dependency-name: github.com/go-chi/httprate
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-14 11:07:11 -04:00
dependabot[bot] deefd7a2fd
Bump github.com/lestrrat-go/jwx from 1.2.1 to 1.2.2 (#1247)
Bumps [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/main/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v1.2.1...v1.2.2)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-14 11:06:51 -04:00
Deluan 97f87416a4 Bump @testing-library dependencies 2021-07-14 11:02:11 -04:00
Deluan 5d8b90b168 Bump Go dependencies 2021-07-14 10:55:40 -04:00
Deluan 8396b51a9c Upgrade React-Admin to 3.17.0 2021-07-14 10:39:48 -04:00
Deluan 4a25fa0920 Make the default volume 50% (compensate for logarithmic volume).
Closes #1052
2021-07-14 09:58:50 -04:00
dependabot[bot] 8e71f308c2
Bump prettier from 2.3.1 to 2.3.2 in /ui (#1210)
Bumps [prettier](https://github.com/prettier/prettier) from 2.3.1 to 2.3.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.3.1...2.3.2)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-07-09 21:29:51 -04:00
Deluan 53fe2e98c5 Remove `eject` script, hopefully it will never be used 2021-07-08 21:09:38 -04:00
Deluan 334068c8bf Refactor mime-types configuration 2021-07-08 11:38:29 -04:00
Deluan b34d77f85a Don't show "playing/paused" icon on the first song when calling "Play Now" 2021-07-05 21:45:20 -04:00
Deluan 24d4c81b34 Change default volume to 50%
Should fix #1052
2021-07-04 21:36:41 -04:00
Deluan 189d0c0ab3 Restore volume when playing a song...
... or continuing to play a paused one
2021-07-04 21:36:41 -04:00
Deluan 1922eaaab2 Make cover rectangular in player when cover animation is disabled 2021-07-04 12:52:59 -04:00
Deluan 5d9bea5087 Fix Album grid responsiveness on small screens.
Potentially fixes #647
2021-07-04 12:09:30 -04:00
Deluan 69afb69959 Fix Disc context menu not visible in mobile 2021-07-04 11:44:41 -04:00
Deluan 27ba267b38 Fix play single song action 2021-07-03 22:29:59 -04:00
Deluan 114fdce09e Fix Last.fm's artist.getInfo 2021-07-03 21:48:53 -04:00
Deluan fa8b4d40b4 Fix arranging songs in PlayQueue 2021-07-03 21:34:24 -04:00
Deluan ace5c905eb Made the Player behaviour more consistent 2021-07-02 23:36:33 -04:00
Deluan 26b5e6b1b4 Better scrobble log message when buffer is disabled 2021-07-02 10:19:16 -04:00
certuna 77f6bc83ac
Update SongList.js (#1219)
Genre and Comments columns in Songs listview (hidden by default)
2021-07-02 10:18:45 -04:00
Deluan 94e36d7f60 Remove old feature flag for cache layout 2021-07-02 10:04:41 -04:00
Deluan f49205733b Add feature flag for buffered scrobbling 2021-07-02 10:04:41 -04:00
Deluan cfb113bd33 Disable Last.FM features based on `LastFM.Enabled` config option 2021-07-02 10:04:41 -04:00
Deluan 289da56f64 Implement Scrobble buffering/retrying 2021-07-02 10:04:41 -04:00
Deluan fb183e58e9 Only encrypts NewPassword if it is not empty, when updating the user details. Fixes #1222 2021-07-01 16:09:49 -04:00
Deluan ed286c7103 Don't rely on goroutines to send keepalive events 2021-07-01 13:31:46 -04:00
Deluan 452c8dc44b Fixed the enduring nasty "too many files open" bug!! Fix #446 2021-07-01 12:07:32 -04:00
Deluan 0c2ca2a5e4 Assign event ids in the main loop, to avoid out-of-order events 2021-07-01 10:58:41 -04:00
Deluan 5bd33455a1 Fix deadlock situation when events are sent too fast to the broker 2021-07-01 10:42:00 -04:00
Deluan 4ea0f235e1 Fix scrollbar colour for Dark/ExtraDark theme. Fixes #1216 2021-06-29 12:29:00 -04:00
Deluan Quintão b16d473d4c Update es.json (POEditor.com) 2021-06-28 17:19:01 -04:00
Deluan fd82b8f2dc Default for `EnableCoverAnimation` in dev mode is true 2021-06-28 17:18:32 -04:00
Deluan a73f885afb Add option to disable album cover animation in the player. Closes #1185 2021-06-28 17:11:05 -04:00
Brian Schrameck 167fe46288
Addresses a bug that would prevent users from changing their own passwords, introduced as part of #1187. (#1214) 2021-06-28 16:36:14 -04:00
Deluan Quintão cb1827ccbf
Update translations (#1134)
* Update de.json (POEditor.com)

* Update ja.json (POEditor.com)

* Update cs.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update de.json (POEditor.com)

* Update es.json (POEditor.com)

* Update uk.json (POEditor.com)

* Update sl.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update it.json (POEditor.com)

* Update it.json (POEditor.com)

* Update cs.json (POEditor.com)

* Update sl.json (POEditor.com)

* Update de.json (POEditor.com)

* Update pt.json (POEditor.com)

* Update it.json (POEditor.com)

* Update pt.json (POEditor.com)

* Update zh-Hans.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update es.json (POEditor.com)

* Update cs.json (POEditor.com)

* Update zh-Hans.json (POEditor.com)

* Update de.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update sl.json (POEditor.com)

* Update ja.json (POEditor.com)

* Update uk.json (POEditor.com)

* Update cs.json (POEditor.com)

* Update nl.json (POEditor.com)
2021-06-28 09:55:46 -04:00
Deluan 25f0e11562 Add 'AlbumArtist' column to SongList 2021-06-28 09:54:17 -04:00
Deluan 292cf99f49 Add 'Year' column to Album and Playlists song list 2021-06-28 09:45:30 -04:00
Deluan d2fcab78a5 Fix ND_DEVFASTACCESSCOVERART flag not available as env var 2021-06-26 15:40:12 -04:00
Deluan 94533e585c Add tests to `/scrobble` endpoint 2021-06-26 13:52:29 -04:00
Deluan 6dd38376f7 Add referential integrity to remove user's props when user is deleted 2021-06-25 23:09:10 -04:00
Deluan 26bcf0b877 Enable Last.fm scrobbling by default (still requires user's authorization) 2021-06-25 23:09:09 -04:00
Deluan 92634a7408 Only show message after 2 seconds, giving time for the browser to close it first 2021-06-25 22:23:35 -04:00
Deluan ee21f3957e Pass userId explicitly to UserPropsRepository methods 2021-06-25 22:21:37 -04:00
Deluan a1551074bb Add a hacky way to style the react-player. 2021-06-25 18:19:57 -04:00
Deluan 823fef8e43 Fix JS console error 2021-06-25 14:11:58 -04:00
Deluan 82105c3a16 Remove React.Strict mode 2021-06-25 14:08:00 -04:00
Deluan b684a47f80 Show DiscSubtitle even if the album has only one disc.
Closes #947
2021-06-25 11:30:24 -04:00
Deluan da2334e10c Remove submenu "Library". Relates to #430 2021-06-25 00:01:38 -04:00
Deluan 4853760fb5 Suppress logs of successful DB migrations applied when running for the first time 2021-06-24 23:43:20 -04:00
Deluan 0cbb0acad3 Skip songs with less than 31 seconds, as per Last.fm specification
See https://www.last.fm/api/scrobbling#when-is-a-scrobble-a-scrobble
2021-06-23 21:08:01 -04:00
Deluan 5040f6fd97 Fix label 2021-06-23 18:09:05 -04:00
Deluan abe8015745 Add option to disable external scrobbling per player 2021-06-23 17:55:58 -04:00
Deluan 5001518260 Move user properties (like session keys) to their own table 2021-06-23 17:49:32 -04:00
certuna 265f33ed9d
Remove clearServiceWorkerCache, not needed anymore. (#1205)
remove clearServiceWorkerCache, not needed anymore.
2021-06-23 12:11:35 -04:00
Deluan 99be8444d3 Disable completely external scrobblers if feature is disabled (`DevEnableScrobble`) 2021-06-23 11:01:58 -04:00
Deluan f4ddd201f2 Send the time the track started playing when scrobbling 2021-06-23 11:01:58 -04:00
Deluan 056f0b944f Refactor: Consolidate scrobbling logic in play_tracker 2021-06-23 11:01:58 -04:00
Deluan 76acd7da89 Don't send scrobbles/nowPlaying updates to Last.fm if user has not authorized 2021-06-23 11:01:58 -04:00
Deluan 8af7dab23d Fix wrong warning about ignored NowPlaying 2021-06-23 11:01:58 -04:00
Deluan a7509c9ff7 Send NowPlaying and Scrobbles to Last.fm 2021-06-23 11:01:58 -04:00
Deluan d5461d0ae9 Refactor Agents to be singleton
Initial work for Last.fm scrobbler
2021-06-23 11:01:58 -04:00
Steve Richter f9fa9667a3 Show user-friendly message when error occurs in Last.fm callback endpoint 2021-06-23 11:01:58 -04:00
Steve Richter 5fbfd9c81e Implement Last.fm account linking UI 2021-06-23 11:01:58 -04:00
Deluan 8b62a58b4c Remove limitation of only scrobbling tracks longer than 30 seconds 2021-06-22 09:59:00 -04:00
Deluan 743e469795 Use singleton in other places as well 2021-06-21 18:59:26 -04:00
Deluan 1f997357a9 Expose Last.fm's ApiKey to UI 2021-06-21 18:14:01 -04:00
Deluan 143cde37e5 Implement Last.FM Web authentication flow 2021-06-21 18:14:01 -04:00
Deluan 502a719e96 Implement Last.FM Desktop Auth flow endpoints 2021-06-21 18:14:01 -04:00
Steve Richter 8ee5c1f245 Initial Last.fm UI implementation 2021-06-21 18:14:01 -04:00
Deluan 0495e421fe Fix Last.fm API method signature 2021-06-21 18:14:01 -04:00
Deluan ffa76bba6a Add flag to disable Scrobble config in the UI 2021-06-21 18:14:01 -04:00
Deluan a4f91b74d2 Add Last.FM Authentication methods 2021-06-21 18:14:01 -04:00
Deluan 73e1a8fa06 Remove false-positive on new version detection 2021-06-21 17:46:26 -04:00
Deluan 877f01bd38 Show notification if server is updated 2021-06-21 13:48:39 -04:00
Deluan 47bcf719f2 Fix cookie warning 2021-06-20 13:27:50 -04:00
Deluan 197d430d15 Fix lint error 2021-06-20 12:07:34 -04:00
Deluan 4e1957ca71 Update Go dependencies 2021-06-20 12:06:21 -04:00
Deluan 25db2cb075 Add concurrency test for singleton 2021-06-20 11:51:32 -04:00
Deluan 80b2c2f3cf Try to register all playing music in GetNowPlaying 2021-06-20 11:25:15 -04:00
Deluan 97434c1789 Fix GetNowPlaying endpoint showing only the last play 2021-06-20 10:39:19 -04:00
Deluan f8ee6db72a New implementation of NowPlaying 2021-06-20 10:39:16 -04:00
Deluan 0df0ac0715 Add logos to badges 2021-06-19 11:32:22 -04:00
Deluan c09468e135 Option to allow auto-login during development. 2021-06-19 10:56:39 -04:00
Deluan cf553ce812 Don't show "logout" when authenticated by Header 2021-06-18 19:08:25 -04:00
Deluan 31ea033880 Fix subsonic token when authenticating by Header 2021-06-18 19:00:13 -04:00
Deluan Quintão 66b74c81f1
Encrypt passwords in DB (#1187)
* Encode/Encrypt passwords in DB

* Only decrypts passwords if it is necessary

* Add tests for encryption functions
2021-06-18 18:38:38 -04:00
Deluan d42dfafad4 Add username to request.Context 2021-06-18 18:28:51 -04:00
dependabot[bot] 84413b542e
Bump @testing-library/jest-dom from 5.13.0 to 5.14.1 in /ui (#1176)
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.13.0 to 5.14.1.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v5.13.0...v5.14.1)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-18 09:39:23 -04:00
Deluan b590c31e4e Fix `stream` url, after changes to subsonic client api 2021-06-16 16:38:50 -04:00
Deluan c4623d7bc3 Don't show "empty" dates 2021-06-16 12:28:49 -04:00
Deluan e0fd1c6ad8 Add "Last Played" column to SongList 2021-06-16 11:57:02 -04:00
Deluan 86271f0412 Optimize refresh events for scrobble endpoint 2021-06-16 10:23:34 -04:00
Deluan fb7229a53e Refech using getMany, reducing the number of API calls 2021-06-16 10:01:09 -04:00
Deluan 521d1ff2bf Disable `realip` middleware when using the reverse proxy authentication feature
Should fix https://github.com/navidrome/navidrome/pull/1152#issuecomment-862306847
2021-06-16 10:01:09 -04:00
Deluan d3db41ae7d Bump `github.com/go-chi/httprate` version 2021-06-15 19:58:29 -04:00
Deluan 8bf0089abf Bump `github.com/ReneKroon/ttlcache/` and `github.com/microcosm-cc/bluemonday` versions 2021-06-15 19:54:18 -04:00
Deluan b65e76293a Only send events to clients who need it
- User events (star, rating, plays) only sent to same user
- Don't send to the client (browser window) that originated the event
2021-06-15 18:59:26 -04:00
Deluan 5f6f74ff2d Always use `httpClient` to call APIs 2021-06-15 17:29:01 -04:00
Deluan 8383527aab Only refetch changed resources when receive a "refreshResource" event 2021-06-15 16:12:13 -04:00
Deluan 8a56584aed Removed the `albumSong` workaround, as React-Admin's cache seems to behave better now 2021-06-15 11:31:41 -04:00
Deluan 667701be02 Less `warning` messages when first running it.
They are actually `info` messages
2021-06-13 19:27:01 -04:00
Deluan 59b99d2206 No need to check for first time when authenticating. One less SQL call per request 2021-06-13 19:26:25 -04:00
Deluan d54129ecd2 Rename `app` package to `nativeapi` 2021-06-13 19:15:41 -04:00
Deluan Quintão 03efc48137
Refactor routing, changes API URLs (#1171)
* Make authentication part of the server, so it can be reused outside the Native API

This commit has broken tests after a rebase

* Serve frontend assets from `server`, and not from Native API

* Change Native API URL

* Fix auth tests

* Refactor server authentication

* Simplify authProvider, now subsonic token+salt comes from the server

* Don't send JWT token to UI when authenticated via Request Header

* Enable ReverseProxyWhitelist to be read from environment
2021-06-13 12:46:36 -04:00
Deluan bed2f017af Fix index of songs in downloaded playlist 2021-06-12 23:02:34 -04:00
Igor Rzegocki 6bd4c0f6bf
Reverse proxy authentication support (#1152)
* feat(auth): reverse proxy authentication support - #176

* address PR remarks

* Fix redaction of UI appConfig

Co-authored-by: Deluan <deluan@navidrome.org>
2021-06-11 23:17:21 -04:00
Deluan b445cdd641 Use a dedicated api-key/secret pair for Last.FM 2021-06-10 15:07:06 -04:00
Deluan e31802d2d3 Only send "refresh" event if `SetRating` was successful 2021-06-10 15:03:30 -04:00
Deluan cefc939909 Trigger UI refresh on media annotation events: `star`, `setRating` and `scrobble` 2021-06-10 12:20:52 -04:00
Deluan 2afb2db7ef Refactor for readability 2021-06-09 22:35:20 -04:00
Deluan 7f85ecd515 Trigger a UI refresh when the scanner finds changes.
Closes #1025
2021-06-09 21:02:20 -04:00
dependabot[bot] cb6aa49439
Bump github.com/lestrrat-go/jwx from 1.2.0 to 1.2.1 (#1167)
Bumps [github.com/lestrrat-go/jwx](https://github.com/lestrrat-go/jwx) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/lestrrat-go/jwx/releases)
- [Changelog](https://github.com/lestrrat-go/jwx/blob/main/Changes)
- [Commits](https://github.com/lestrrat-go/jwx/compare/v1.2.0...v1.2.1)

---
updated-dependencies:
- dependency-name: github.com/lestrrat-go/jwx
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-09 15:33:04 -04:00
dependabot[bot] b7f47c8833
Bump github.com/onsi/ginkgo from 1.16.3 to 1.16.4 (#1163)
Bumps [github.com/onsi/ginkgo](https://github.com/onsi/ginkgo) from 1.16.3 to 1.16.4.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v1.16.3...v1.16.4)

---
updated-dependencies:
- dependency-name: github.com/onsi/ginkgo
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-09 15:18:31 -04:00
dependabot[bot] adb09c9c69
Bump @testing-library/jest-dom from 5.12.0 to 5.13.0 in /ui (#1162)
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.12.0 to 5.13.0.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v5.12.0...v5.13.0)

---
updated-dependencies:
- dependency-name: "@testing-library/jest-dom"
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-09 15:18:17 -04:00
dependabot[bot] 0c9e0ff886
Bump prettier from 2.3.0 to 2.3.1 in /ui (#1161)
Bumps [prettier](https://github.com/prettier/prettier) from 2.3.0 to 2.3.1.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.3.0...2.3.1)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-09 15:17:56 -04:00
Deluan f9eec5e4dc Refactored agents calling into its own struct 2021-06-08 17:00:02 -04:00
Deluan 6c1ba8f0d0 Add tests to `core.Share` 2021-06-08 16:32:08 -04:00
Deluan 110e17b004 Make MockRepo names more consistent 2021-06-08 16:30:19 -04:00
Deluan 779571a086 go mod tidy 2021-06-08 15:47:34 -04:00
Yash Jipkate af210c8903
Add Native Sharing REST API (#1150)
* Initial draft - UNTESTED

* changes to Save() and Update()

* apply col filter and limit nanoid

* remove columns to not update
2021-06-08 15:44:30 -04:00
Deluan e80cf80d05 Move all Spotify and LastFM code into only one folder for each 2021-06-08 11:25:46 -04:00
Ye61123 182e3ec78e
Update zh-Hans.json (#1160)
Complete untranslated items
2021-06-08 10:41:01 -04:00
Steve Richter 65ccd4c99d
Parse ParamBool case-insensitively (#1151) 2021-06-04 23:37:01 -04:00
Deluan bebfe296a5 Allow updating only specific columns 2021-06-02 18:40:29 -04:00
Deluan 9da9d73c1d Don't panic when taglib returns an error 2021-05-31 18:26:46 -04:00
Deluan cd242695ba Foundational work to enable multi-valued tags 2021-05-31 18:08:12 -04:00
Deluan 519c89345e Omit empty fields from Native API responses 2021-05-31 12:20:21 -04:00
Deluan 336d891e58 Bump github.com/ReneKroon/ttlcache/v2 from 2.5.0 to 2.6.0 2021-05-31 10:49:42 -04:00
Deluan 9b4b28f685 Bump ginkgo/gomega versions 2021-05-31 10:32:37 -04:00
Deluan 39c560a5c2 Remove unused web-vitals package 2021-05-31 10:21:24 -04:00
Deluan c5abdc19bc Fix recursive bug in Last.FM calls without mbid 2021-05-30 22:46:23 -04:00
Deluan ead2095dd0 Respect EnableLogRedacting config when pretty printing configuration 2021-05-30 16:02:23 -04:00
Yash Jipkate 7b05c49215
Add devEnableShare config option (#1141)
* add devEnableShare config option

* Toggle in config.js
2021-05-30 15:36:10 -04:00
Yash Jipkate 327c259a3d
Create share table and repository. (#930)
* Add share table and repository

* Add datastore mock

* Try fixing indent

* Try fixing indent - 2

* Try fixing indent - 3

* Implement rest.Repository and rest.Persistance

* Renew date

* Better error handling

* Improve field name

* Fix json name conventionally
2021-05-30 11:50:35 -04:00
Deluan 675cbe11b3 Fix `updatePlaylist` not updating fields `comment` and `public`.
Fix #1140
2021-05-29 17:16:56 -04:00
Deluan 91a91f7e06 GetCoverArt returns placeholder if `id` is missing
This mimics Subsonic behaviour, even if it contradicts the API documentation, which states `id` is required

Fixes #1139
2021-05-29 11:37:00 -04:00
Deluan 7bbb09e546 Add tests for WeightedRandomChooser 2021-05-28 23:51:56 -04:00
Deluan dd56a7798e Rename variable with conflicting name 2021-05-28 23:00:39 -04:00
Deluan a38e478a47 Better SimilarSongs algorithm 2021-05-28 22:55:34 -04:00
Deluan 1940267a18 Handle functions with params in sort order.
Related to #1023
2021-05-28 17:35:32 -04:00
Deluan 01f3ce0228 Add a timeout to background task 2021-05-28 11:37:53 -04:00
Deluan 48b6fa7feb Don't use request's context when refreshing artist info in background 2021-05-28 09:34:15 -04:00
Deluan 25d62cd751 Set retention time for uploaded artifacts to 7 days 2021-05-27 23:39:20 -04:00
Deluan ed01946ace Embed Last.FM error responses, making the tests faster 2021-05-27 21:04:03 -04:00
Deluan Quintão 89b12b34be
Retry calls to Last.FM without MBIDs when if returns artist invalid (#1138)
* Call Last.FM's getInfo again without mbid when artist is not found

* Call Last.FM's getSimilar again without mbid when artist is not found

* Call Last.FM's getTopTracks again without mbid when artist is not found
2021-05-27 20:53:24 -04:00
Deluan 4e0177ee53 Always update artist info, even if info is fresh 2021-05-27 20:32:26 -04:00
Deluan b398053223 Include a shared Last.FM api key, providing zero conf ArtistInfo (bio/top songs/similar artists) 2021-05-27 16:14:24 -04:00
Deluan db11b6b8f8 Remove decoration from `reflex` output 2021-05-26 12:24:02 -04:00
Deluan 60d50de8c9 Refactoring to make common components usage more uniform 2021-05-26 09:35:13 -04:00
Aldrin Jenson 0941fbc0cd
Fix lag on albumList toggling (#1136) 2021-05-26 08:42:39 -04:00
Deluan 4217c75c9f Upgrade to Node v16 2021-05-25 10:53:16 -04:00
Deluan 409020a502 Bump github.com/ReneKroon/ttlcache/v2 from 2.4.0 to 2.5.0 2021-05-25 10:24:40 -04:00
Deluan b4832c36b7 Bump github.com/golangci/golangci-lint from 1.40.0 to 1.40.1 2021-05-25 10:23:08 -04:00
Deluan 1de7366ece Bump @material-ui/lab from 4.0.0-alpha.57 to 4.0.0-alpha.58 in /ui 2021-05-25 10:16:44 -04:00
Deluan ab1bc6194a Bump @testing-library dependencies 2021-05-25 10:13:57 -04:00
dependabot[bot] ad4db122fb
Bump hosted-git-info from 2.8.8 to 2.8.9 in /ui (#1111)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-25 10:13:40 -04:00
dependabot[bot] 200b815c67
Bump url-parse from 1.4.7 to 1.5.1 in /ui (#1107)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.4.7 to 1.5.1.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.4.7...1.5.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-25 10:12:42 -04:00
Deluan Quintão 5631493cc4
Upgrade Web UI to Create-React-App 4 and React 17 (#1105)
* Upgrade to CRA 4.0.3

* Try to fix tests. No lucky

* Fix new ESLint errors

* Fix JS tests and remove unwanted dependency. (#1106)

* Fix tests

* Fix lint

* Remove React v16 workaround (fixed in v17)

* Force eslint to break on warnings

* Lint now needs to be called explicitly in the pipeline

Co-authored-by: Yash Jipkate <34203227+YashJipkate@users.noreply.github.com>
2021-05-25 09:58:06 -04:00
Deluan d9f268266c Rename List view mode to Table 2021-05-24 12:58:15 -04:00
Deluan 882519738f Change back mounting order, for better logs 2021-05-24 12:17:55 -04:00
Deluan 86d3a219a9 Show name of router in log 2021-05-24 11:55:39 -04:00
Deluan 1d0e75151a Update Portuguese translation 2021-05-24 11:24:28 -04:00
Deluan 107a11b445 Bump React-Admin to 3.15.2 2021-05-24 11:17:06 -04:00
Aldrin Jenson cf8ee251ee
Option to toggle fields in songs, albums & artists (#923)
* Add toggleColumns

- Add logic for toggling columns
- Add MenuComponent + useSelectedFields hook

* Refactoring

* eslint-fixes

* Typo

* skip menu in albumGridView

* add omittedFields

* Add toggling for playlists and albumSong

* Refactoring

* defaultProps - fix

* Add toggling for PlaylistSongs

* remove accidental console log

* Refactoring for future compatibility

* Hide ToggleMenu in albumGridView

* Add TopBarComponent in ToggleFieldsMenu

* Add defaultOff for useSelectedFields

* Fix edge case

* eslint fix

* Refactoring

* Add propType for forwardRef

* Fix issues

* add translation for grid and table

* add translation for grid and table

* Ignore menuBtn for spotify-ish and Ligera themes

* hide bpm by default in playlistSongs

* Add memoization

* Default album view must be Grid

Co-authored-by: Deluan <deluan@navidrome.org>
2021-05-24 11:09:06 -04:00
Deluan Quintão 6a17717e30
Update translations (#1130)
* Update zh-Hant.json (POEditor.com)

* Update cs.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update ja.json (POEditor.com)

* Update pl.json (POEditor.com)

* Update es.json (POEditor.com)

* Update th.json (POEditor.com)

* Update sl.json (POEditor.com)
2021-05-24 10:21:59 -04:00
Deluan b8a274e4e8 Move Swedish translation to right folder 2021-05-24 10:09:44 -04:00
Deluan 9800823015 Bump react-jinke-music-player from 4.24.0 to 4.24.1 in /ui 2021-05-24 10:04:37 -04:00
deeeeeebs 02606f43b8
Add Swedish translation (#1126)
* Swedish translation

* Updated and renamed to sv.json

Added further lines/translations from the english.json and corrected some of the previous translations

* Update sv.json

* Update sv.json

Ok now i'm done! :P
2021-05-24 10:03:06 -04:00
Deluan e529390034 Remove `md5-hex` wrapper and use `blueimp-md5` directly 2021-05-20 13:42:56 -04:00
Deluan 0ec7a305a2 Reorder Makefile dev targets 2021-05-20 13:42:29 -04:00
Deluan b6cb81c3a3 Update Portuguese translations 2021-05-16 13:31:04 -04:00
Steve Richter e60f2bfa3d
User management improvements (#1101)
* Show more descriptive success messages for User actions

* Check username uniqueness when creating/updating User

* Adjust translations

* Add tests for `validateUsernameUnique()`

Co-authored-by: Deluan <deluan@navidrome.org>
2021-05-16 13:25:38 -04:00
dependabot[bot] 666c006579
Bump lodash from 4.17.19 to 4.17.21 in /ui (#1110)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-15 14:33:29 -04:00
Deluan 6ad94548f3 Add explicit dependency for `inflection` 2021-05-15 11:59:56 -04:00
Deluan fa0e6dda5b Remove C++11 warning in macOS 2021-05-14 16:03:11 -04:00
Deluan e047008f7d Fix test 2021-05-14 15:38:28 -04:00
Deluan 3cac00ad13 Upgrade TagLib to 1.12 2021-05-14 14:31:07 -04:00
Deluan 39d68e8287 Restore pretty formatted config options in debug level 2021-05-12 14:43:09 -04:00
Deluan 751e2d6147 Make ScanInterval=0 disable the periodic scan 2021-05-12 14:08:38 -04:00
Dnouv 74300adbc8
Fix Ligera Error (#1117)
* Fix Ligera Error

* Run make setup
2021-05-12 10:21:56 -04:00
Deluan a484adfcfb Add Slovenian translation. Thanks @jernejml 2021-05-11 22:36:22 -04:00
Deluan 25bd36dbc5 Bump react-admin to 3.15.1 2021-05-11 22:24:24 -04:00
Deluan 87298f616f Add more explicit npm dependencies 2021-05-11 22:22:32 -04:00
Deluan 4699902369 Remove dependency on lodash.get 2021-05-11 22:08:07 -04:00
Deluan 978933aa48 Add explicit npm dependencies 2021-05-11 22:07:47 -04:00
Deluan 77e736ccfd Do not use `ra-core` directly 2021-05-11 21:39:53 -04:00
Deluan a77635e883 Only setup event stream when mounting the app 2021-05-11 20:27:12 -04:00
Dnouv 0c93db816c
Fix PWA notification toolbar color (#1083)
* Fix PWA notification color

* Add React hook

* Convert component into a hook

Co-authored-by: Deluan <deluan@navidrome.org>
2021-05-11 20:11:54 -04:00
Deluan c0243580c0 Integrate goose log with our own log system 2021-05-11 19:08:06 -04:00
Deluan 22ce5b6282 Removed unnecessary code 2021-05-11 18:55:58 -04:00
Deluan fa9083ddec Upgrade prettier to 2.3.0
Some reformatting was needed... :/
2021-05-11 18:13:03 -04:00
Deluan da684ff44c Bump github.com/lestrrat-go/jwx from 1.1.6 to 1.2.0 2021-05-11 17:44:00 -04:00
Deluan 7d96167abc Upgrade to go-chi 5 2021-05-11 17:21:18 -04:00
Deluan fb5840705e Bump github.com/golangci/golangci-lint from 1.39.0 to 1.40.0 2021-05-11 12:30:11 -04:00
Dnouv 089d4abab1
Replace Feature Policy with Permissions Policy (#1112)
* Add Permissions Policy

* Remove Display capture option
2021-05-11 11:29:55 -04:00
Paul TREHIOU 62ccbaad8b
Improve systemd unit security (#677)
Applied suggestions from `systemd-analyze` and also using StateDirectory to ensure /var/lib/navidrome exists and is writeable
2021-05-09 11:59:08 -04:00
Deluan 8419a2a5d1 Schedule periodic scan before starting initial scan 2021-05-08 19:19:31 -04:00
Aniket Biswas 71c2ed9922
Restart Current Song on previous (#1104)
* fixed on previous song behaviour

* rebased with master
2021-05-08 14:27:33 -04:00
Deluan 72ec808a2c Bump react-jinke-music-player from 4.21.2 to 4.24.0 in /ui 2021-05-08 13:15:39 -04:00
Deluan 702a65059f Fix redaction for query parameters. Fix #1103 2021-05-07 21:42:35 -04:00
Deluan 3e8d3e78c2 Fix Bookmarks Subsonic support (#1099)
JSON responses were incorrect
2021-05-07 09:47:13 -04:00
Deluan 47f4e0a4de Refactor to remove some nesting 2021-05-06 20:49:26 -04:00
Deluan 1f8949929d Fix(?) possible TypeError 2021-05-06 20:46:01 -04:00
Deluan c92a24b3e2 Bump github.com/onsi/gomega from 1.11.0 to 1.12.0 2021-05-06 18:54:45 -04:00
dependabot[bot] cbe0d9763b
Bump github.com/robfig/cron/v3 from 3.0.0 to 3.0.1 (#1098)
Bumps [github.com/robfig/cron/v3](https://github.com/robfig/cron) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/robfig/cron/releases)
- [Commits](https://github.com/robfig/cron/compare/v3.0.0...v3.0.1)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-06 18:46:12 -04:00
dependabot[bot] 44dd414d25
Bump github.com/microcosm-cc/bluemonday from 1.0.8 to 1.0.9 (#1056)
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.8 to 1.0.9.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.8...v1.0.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-06 18:45:10 -04:00
Samarjeet d85db8ffff
Fix Spotify-ish playlist title is cut off (#1094) 2021-05-06 18:33:54 -04:00
dependabot[bot] c7378c0fa5
Bump @testing-library/user-event from 13.1.5 to 13.1.8 in /ui (#1082)
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 13.1.5 to 13.1.8.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v13.1.5...v13.1.8)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-06 18:27:02 -04:00
plr20 18696c5517
Update Czech translation (#1095)
* Update Czech translation

* Adjust translations
2021-05-06 18:22:47 -04:00
dependabot[bot] 5a5d763c19
Bump github.com/onsi/ginkgo from 1.16.1 to 1.16.2 (#1096)
Bumps [github.com/onsi/ginkgo](https://github.com/onsi/ginkgo) from 1.16.1 to 1.16.2.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v1.16.1...v1.16.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-06 18:19:40 -04:00
Deluan f8dbc41b6d Breaking change: Add `ScanSchedule`, allows interval and cron based configurations.
See https://pkg.go.dev/github.com/robfig/cron#hdr-CRON_Expression_Format for expression syntax.

`ScanInterval` will still work for the time being. The only situation it does not work is when you want to disable periodic scanning by setting `ScanInterval=0`. If you want to disable it, please set `ScanSchedule=""`

Closes #1085
2021-05-06 17:56:10 -04:00
Deluan 1d6aa70033 Fix possible TypeError 2021-05-06 09:49:25 -04:00
Brian Schrameck 30bb3f7b43
BPM metadata enhancement (#1087)
* BPM metadata enhancement

Related to #1036.

Adds BPM to the stored metadata about MediaFiles.

Displays BPM in the following locations:
- Listing songs in the song list (desktop, sortable)
- Listing songs in playlists (desktop, sortable)
- Listing songs in albums (desktop)
- Expanding song details

When listing, shows a blank field if no BPM is present. When showing song details, shows a question mark.

Updates test MP3 file to have BPM tag. Updated test to ensure tag is read correctly.

Updated localization files. Most languages just use "BPM" as discovered during research on Wikipedia. However, a couple use some different nomenclature. Spanish uses PPM and Japanese uses M.M.

* Enhances support for BPM metadata extraction

- Supports reading floating point BPM (still storing it as an integer) and FFmpeg as the extractor
- Replaces existing .ogg test file with one that shouldn't fail randomly
- Adds supporting tests for both FFmpeg and TagLib

* Addresses various issues with PR #1087.

- Adds index for BPM. Removes drop column as it's not supported by SQLite (duh).
- Removes localizations for BPM as those will be done in POEditor.
- Moves BPM before Comment in Song Details and removes BPM altogether if it's empty.
- Omits empty BPM in JSON responses, eliminating need for FunctionField.
- Fixes copy/paste error in ffmpeg_test.
2021-05-05 21:35:01 -04:00
Deluan fb33aa4496 Fix possible TypeError 2021-05-05 21:14:36 -04:00
Deluan 9e559311ad Fix Album Grid flickering 2021-05-05 16:18:08 -04:00
Deluan a5fc5f0ff6 Revert "Better way to invoke `make single`"
This reverts commit 73efbd90
2021-05-04 17:05:41 -04:00
Deluan 73efbd90ab Better way to invoke `make single` 2021-05-04 16:47:47 -04:00
Deluan cbc4cb483d Fix QuickFilter by favourites in Album/All view 2021-05-04 16:28:19 -04:00
Deluan 986473393f Fix missing translation error in console. Closes #1038 2021-05-04 16:01:26 -04:00
Deluan 66b31644fa Upgrade React-Admin to 3.15.0 2021-05-03 22:32:30 -04:00
Deluan b478b0af02 FIx ffmpeg output regex too rigid 2021-05-03 21:26:44 -04:00
Deluan c3316e201e Fix cover art detection with ffmpeg 4.4 2021-05-03 20:25:31 -04:00
Deluan 874b17b8f6 Require user to provide current password to be able to change it
Admins can change other users' password without providing the current one, but not when changing their own
2021-05-03 15:03:34 -04:00
Deluan 5808b9fb71 Fix Transcodings menu 2021-05-03 13:54:08 -04:00
Deluan c33ebabde8 Fix warning about promise being ignored 2021-05-03 13:38:34 -04:00
Deluan 7feda4bea4 Add `EnableUserEditing`, to control whether a regular user can change their own details (default `true`) 2021-05-02 17:11:12 -04:00
Deluan 2ff1c79b64 Fix `EnableLogRedacting` case 2021-05-02 16:49:20 -04:00
Deluan cfbc39fb7f Add log redacting, controlled by the new `EnableLogRedacting` config option (default `true`)
Imported redacting code from https://github.com/whuang8/redactrus (thanks William Huang)
Didn't use it as a dependency as it was too small and we want to keep dependencies at a minimum
2021-05-02 16:45:16 -04:00
Deluan 2372f1d12b Change visibility of helper function 2021-05-02 15:23:51 -04:00
Deluan 490a7fcf52 Add test to Login function 2021-05-02 15:19:21 -04:00
Deluan ad153f5f63 Fix User delete button not showing 2021-05-02 15:03:15 -04:00
Deluan b8138ebad6 Fix create first login 2021-05-02 14:13:17 -04:00
Deluan e3fe8399c8 Fix DevAutoCreateAdminPassword 2021-05-01 18:40:02 -04:00
Deluan 88105d5c30 Clean-up Makefile, add help 2021-05-01 11:14:24 -04:00
Deluan b180386d23 Simplify build targets 2021-05-01 09:16:45 -04:00
Deluan 70e7bf6b5b Clean up some `make` targets 2021-05-01 08:51:29 -04:00
Samarjeet d41137ad8e
[Spotify-ish] Login consistent with other themes (#1073) 2021-05-01 08:48:12 -04:00
Deluan 88f2fc35cd Fix regular users not able to edit their info before logging in again 2021-04-30 17:53:17 -04:00
Deluan bc62efb059 More auth tests 2021-04-30 10:00:03 -04:00
Deluan eaf40efdf4 Never send passwords to the UI 2021-04-29 20:04:01 -04:00
Deluan 71dc0dddaf Show Person icon for non admin users 2021-04-29 18:26:53 -04:00
Deluan bcda53f115 Less waiting for cache to be ready 2021-04-29 13:58:01 -04:00
Deluan 8a07bac2a2 Fix SIGUSR1 work when ScanInterval=0 2021-04-29 13:10:10 -04:00
Deluan a35de2bfd1 Allow regular users to change their info, including password.
Should fix #199
2021-04-28 22:35:25 -04:00
Deluan 22582392a0 Fix "Failed prop type: Invalid prop `variant`" in console 2021-04-28 22:07:16 -04:00
Deluan 932c108e82 Fix "SharedArrayBuffer will require cross-origin isolation"
This is a workaround for React v16 while we don't upgrade to v17

See https://github.com/facebook/create-react-app/issues/10474
2021-04-28 21:42:17 -04:00
whorfin 20d2726faa
Improve scanner (#1054)
* Handle subdirectories without rx permission correctly
Allow ogg files w/o metadata, having taglib behave more like ffmpeg

* Fix test for walk_dir_tree, fix full reading of files in permission-
constrained directories, allow directories with leading ellipses

* Sorted directory traversal is preferred, and cleanup tests

* Small refactoring to clean-up `loadDir` function and to remove some "warnings" from IntelliJ

Co-authored-by: Deluan <deluan@navidrome.org>
2021-04-28 19:51:02 -04:00
Samarjeet 771c91d2dd
[Spotify-ish] Indicate active page number (#1068) 2021-04-28 08:57:08 -04:00
Deluan Quintão b8173124f4 Update ja.json (POEditor.com) 2021-04-27 18:39:45 -04:00
Deluan d1605dcfbe Replace `godirwalk` with standard Go 1.16 `filepath.WalkDir`
Should fix https://github.com/navidrome/navidrome/issues/1048
2021-04-27 11:28:47 -04:00
Deluan 10cfaad95c Bump react-redux version to 7.2.4 2021-04-26 23:22:53 -04:00
Deluan 07f6a7cc9f Bump @testing-library dependencies 2021-04-26 23:19:30 -04:00
Deluan 6e73c23704 Keepalive must return an ID to be used with `dataProvider.getOne` 2021-04-26 23:19:30 -04:00
Deluan 862c6d3c73 Upgrade React-Admin to 3.14.5 2021-04-26 23:19:28 -04:00
Deluan 692663680b Uses GoLang 1.16.3
Also add a target to build snapshots for a single platform
2021-04-26 17:21:59 -04:00
Deluan 0d409e37e2 Fix aspect ratio of login icon 2021-04-26 12:35:49 -04:00
dependabot[bot] f1bd736b20 Bump jest-environment-jsdom-sixteen from 1.0.3 to 2.0.0 in /ui
Bumps [jest-environment-jsdom-sixteen](https://github.com/SimenB/jest-environment-jsdom-sixteen) from 1.0.3 to 2.0.0.
- [Release notes](https://github.com/SimenB/jest-environment-jsdom-sixteen/releases)
- [Commits](https://github.com/SimenB/jest-environment-jsdom-sixteen/compare/v1.0.3...v2.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-26 09:11:34 -04:00
Deluan cde6626016 Fix logo aspect ratio in Safari 2021-04-25 21:16:45 -04:00
Deluan 1c7d4c5630 Improve Logo resolution in login dialog 2021-04-25 21:16:24 -04:00
Dnouv c75314c605
Enhanced Mobile Login Screen (#953)
* Enhanced Mobile Login Screen

* Removed duplicate line of code

* Add support for desktop

* Remove conflict

* Reset button style

* Change Login
2021-04-25 21:09:23 -04:00
Deluan b10f491de8 Fix Song details row height 2021-04-24 23:06:14 -04:00
caiocotts b671d0ff7b
Better handling of album comments (#1013)
* Change album comment behaviour

* Don't check first item

* Fix previously imported album comments.

* Remove song comments if album comment is present
2021-04-24 21:40:55 -04:00
Deluan 4b5a5abe1b Fix Web Scroller compatibility
This fixes https://github.com/web-scrobbler/web-scrobbler/issues/2828
2021-04-24 21:13:14 -04:00
Deluan 3cede28161 Reorganize AudioTitle classes.
Should fix https://github.com/web-scrobbler/web-scrobbler/issues/2828
2021-04-24 18:06:24 -04:00
Deluan 79bbff0e98 Make Playlist grid more responsive 2021-04-24 11:29:12 -04:00
Deluan 0142352280 Fix build tag 2021-04-23 21:42:22 -04:00
Deluan d5c7a81888 Disable SIGUSR1 handler for Windows (not available) 2021-04-23 21:22:04 -04:00
Deluan 1e539f4e54 Add trigger scan when receiving SIGUSR1 signal 2021-04-23 20:40:28 -04:00
Dnouv e83a0b23a3
Hide volume bar in lower resolutions (#889)
This gives more space for the song and artist names in the player

* fix min-width of AlbumDetails

* Fix song play time display

* Song duration display fix#2

* Removed important

* Resolve conflicts

* Update Player.js

* Change breakdown and hide volume

Co-authored-by: Deluan <deluan@navidrome.org>
2021-04-23 19:04:37 -04:00
dependabot[bot] 9f39f062d8
Bump @testing-library/user-event from 13.1.2 to 13.1.5 in /ui (#1051)
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 13.1.2 to 13.1.5.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v13.1.2...v13.1.5)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-23 18:44:32 -04:00
Yash Jipkate df57cd6bb5
Allow adding songs to multiple playlists at once. (#995)
* Add support for multiple playlists

* Fix lint

* Remove console log comment

* Disable 'check' when loading

* Fix lint

* reset playlists on closeAddToPlaylist

* new playlist: accomodate string type on enter

* Fix lint

* multiple new playlists are added correctly

* use makestyle()

* Add tests

* Fix lint
2021-04-23 18:37:08 -04:00
Arbaz Ahmed d829a63686
fix: refactored some styles in jinkie player and removed br tag - #865 (#1047)
* refactored some styles in jinkieplayer

* fix: refactored some styles in jinkie player and removed br tag - #865

* fix: refactored some styles in jinkie player and removed br tag - #865

Signed-off-by: armedev <epiratesdev@gmail.com>
2021-04-23 18:06:39 -04:00
Deluan Quintão 4b061427ad
Update translations (#1002)
* Update cs.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update ja.json (POEditor.com)

* Update pt.json (POEditor.com)

* Update es.json (POEditor.com)

* Update uk.json (POEditor.com)

* Update zh-Hans.json (POEditor.com)

* Update zh-Hant.json (POEditor.com)

* Update eo.json (POEditor.com)

* Update de.json (POEditor.com)

* Update zh-Hant.json (POEditor.com)

* Update cs.json (POEditor.com)

* Update cs.json (POEditor.com)
2021-04-22 23:12:02 -04:00
Deluan aa9cf8ef17 Add a cleanup to tests 2021-04-22 14:04:48 -04:00
Deluan 240de70026 Add tests for SpreadFS 2021-04-22 14:02:42 -04:00
Shishir A S 6da9dee7d3
Fade in QualityInfo while hovering on Song title (#1041)
* feat(Player/QualityInfo) : Animate Quality Info + Increased audio player dimensions

Signed-off-by: Shishir <shishir.srik@gmail.com>

* fix(Player.js) : Converted JS hover functionality to pure CSS

Signed-off-by: Shishir <shishir.srik@gmail.com>

* Removed unused useState

* fix(Player) : Reverted player height adjustment

Signed-off-by: Shishir <shishir.srik@gmail.com>
2021-04-22 09:53:33 -04:00
Deluan 467eb345ad Don't panic if fscache could not be initialized due to a FS error 2021-04-21 23:39:23 -04:00
Deluan 31b553e972 Add missing error log message in fscache initialization 2021-04-21 14:15:42 -04:00
Deluan da30923a95 Replace default Login backgrounds with Navidrome's collection 2021-04-20 15:26:24 -04:00
dependabot[bot] e7be2f6f9c
Bump ssri from 6.0.1 to 6.0.2 in /ui (#1045)
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-04-20 15:06:46 -04:00
Deluan 9a509c749a Workaround for https://github.com/lijinke666/react-music-player/issues/351 2021-04-20 11:01:31 -04:00
Deluan abaecf2b88 Add Nginx header to not buffer SSE connection
This should allow the Activity Panel, that uses a Server-Side Events/ Event Source connection, to work with default Nginx reverse proxy configuration.
2021-04-20 10:16:20 -04:00
Deluan f63a912341 Add config option to set default theme 2021-04-18 13:51:00 -04:00
Deluan b6f525bda5 Fix exception when running in Firefox over insecure http. Fix #1039 2021-04-18 02:21:58 -04:00
Deluan 0063720cc2 Change size and position of QualityInfo in the Player 2021-04-17 22:49:36 -04:00
Ruchi Kushwaha b441260186
Change icon on active menu item (#903)
* add icons

* add logic to change the icon

* make the active menu bold

* Encapsulate the dynamic icon behaviour into a self-contained component

Co-authored-by: Deluan <deluan@navidrome.org>
2021-04-17 00:40:07 -04:00
Deluan 16a5ac323b Fix migration error caused by #868 2021-04-15 21:25:02 -04:00
Praveen Kumar 749f5d45c6
Fix welcome message styles (#1015)
* style(login): welcome-message-wrapping - #1014

Signed-off-by: Praveen Kumar <pkspyder007@gmail.com>

* style(login): welcome-message-wrapping - #1014

Signed-off-by: Praveen Kumar <pkspyder007@gmail.com>

* chore(makefile): Removed-lint-timeout

Signed-off-by: Praveen Kumar <pkspyder007@gmail.com>
2021-04-15 20:18:35 -04:00
Deluan a81ef0923b Fix cover art not showing in notification when using a `BaseURL` 2021-04-14 13:30:14 -04:00
Samarjeet c86d2a93b1
Fix transparent background in Spotify-ish (#1030) 2021-04-13 21:05:25 -04:00
Deluan b55d582882 go mod tidy 2021-04-13 16:55:26 -04:00
Deluan 6635149f3c Fix pre-commit hook 2021-04-13 16:54:31 -04:00
Deluan 01b34f4f14 Bumps @testing-library/user-event from 13.1.1 to 13.1.2 2021-04-13 16:52:11 -04:00
Deluan cb9cabe0ec Bumps github.com/ReneKroon/ttlcache/v2 from 2.3.0 to 2.4.0 2021-04-13 16:48:41 -04:00
Deluan 29aff05f70 Bump github.com/onsi/ginkgo from 1.16.0 to 1.16.1 2021-04-13 16:48:05 -04:00
Deluan f48bfb6ad1 Bump github.com/microcosm-cc/bluemonday version 2021-04-13 16:47:31 -04:00
Deluan ef9a16ac9f Change order of themes 2021-04-10 19:52:01 -04:00
Dnouv ca9d42714f
New Ligera (light) Theme (#990)
* Enhanced Light Theme

* New Login Screen

* Fix Appbar for sm screen

* Reverse Gradient

* Fix test error

* Fix color

* Fix Gradient

* Theme color change

* Fix playlist autocomp popup

* Rename theme

* Fix hover icon color
2021-04-10 19:49:39 -04:00
Deluan efe8240c0a Remove inline `style` in favour of MUI's styling solution 2021-04-09 17:38:08 -04:00
Ayush Naidu f7dfabaa40
Replaced literal 302 with http constant (#1006) 2021-04-08 14:17:14 -04:00
Deluan d8a1773d50 Increase golangci-lint timeout. Fix #1001 2021-04-08 10:19:58 -04:00
Aries e105e2d2a2
Update Japanese translation (#997) 2021-04-07 23:18:49 -04:00
Deluan f41bc31ba8 Fix layout when album comment is visible 2021-04-07 22:16:38 -04:00
Deluan 96a14ec484 Hide QualityInfo on small screens 2021-04-07 16:10:31 -04:00
Neil Chauhan 48ae4f7479
Add 5-star rating system(#986)
* Added Star Rating functionality for Songs

* Added Star Rating functionality for Artists

* Added Star Rating functionality for AlbumListView

* Added Star Rating functionality for AlbumDetails and improved typography for title

* Added functionality to turn on/off Star Rating functionality for Songs

* Added functionality to turn on/off Star Rating functionality for Artists

* Added functionality to turn on/off Star Rating functionality for Albums

* Added enableStarRating to server config

* Resolved the bugs and improved the ratings functionality.

* synced repo and removed duplicate key

* changed the default rating size to small, and changed the color to match the theme.

* Added translations for ratings, and Top Rated tab in side menu.

* Changed rating translation to topRated in albumLists, and added has_rating filter to topRated.

* Added empty stars icon to RatingField.

* Added sortable=false in AlbumSongs and added sortByOrder=DESC in all List components.

* Added translations for rating, for artists and albums, and removed the sortByOrder=DESC from SimpleLists.
2021-04-07 16:02:52 -04:00
Deluan 840521ffe2 Fix console errors for QualityInfo component 2021-04-07 13:08:41 -04:00
Deluan 5178f44094 Add `has_rating` filter to albums 2021-04-07 11:04:36 -04:00
Deluan 10dcc3fb37 Remove unnecessary export mapping (bad refactoring) 2021-04-07 10:57:14 -04:00
Aries 49b1c40fbd
Update Japanese translation (#992) 2021-04-07 10:14:45 -04:00
Deluan 156a53c2ac Add support for artist 5-star rating in Subsonic API 2021-04-06 23:06:31 -04:00
Deluan 9913b92905 Get lossless format list from server 2021-04-06 22:18:48 -04:00
Himanshu maurya 52812fa48b
Added quality info (#918)
* added quality info

* fixed formatting

* implemented various suggestions

* npm run prettier

* applied suggestions

* npm run prettier

* corrected lossless formats and other suggestions

* moved losslessformats into consts.js

* added some test

* typo while resolving conflicts

* fetch

* removed a bug causing component (as suggested)

* Update PlayerToolbar.js

* implemented suggestions

* added few more tests

* npm run prettier

* added size

* updated qualityInfo

* implemented suggestions

* added test for when no record is recieved

* Update QualityInfo.js
2021-04-06 21:30:17 -04:00
Shishir A S c57fa7a8fc
Fixes play icon color in "Light" theme (#972)
* fix(ui/src/album): White play button on hover for all themes - #960

* fix(PlayButton) : White play button for light theme - #960

* fix(PlayButton) : White play button for light theme - #960

* bug(AlbumGridView.js) - Album play button defaults to white, can be overridden - #960

Signed-off-by: Shishir <shishir.srik@gmail.com>

* bug(AlbumGridView.js) - Album play button defaults to white, can be overridden - #960

* Reverted package.json and package-lock.json - #960

Signed-off-by: Shishir <shishir.srik@gmail.com>

* Missing lint script added - #960

Signed-off-by: Shishir <shishir.srik@gmail.com>

* Removed color, added className and made record required in PlayButton.propTypes - #960
2021-04-06 10:02:44 -04:00
Deluan dbda8712f2 npm audit fix 2021-04-05 22:33:13 -04:00
Deluan 5f685bca30 Hide ❤️ in Playlists 2021-04-05 22:32:25 -04:00
Deluan 37f6ff02cf Do not disable eslint rule 2021-04-05 22:21:05 -04:00
dependabot[bot] 01ef4d2f21 Bump @testing-library/jest-dom from 5.11.9 to 5.11.10 in /ui
Bumps [@testing-library/jest-dom](https://github.com/testing-library/jest-dom) from 5.11.9 to 5.11.10.
- [Release notes](https://github.com/testing-library/jest-dom/releases)
- [Changelog](https://github.com/testing-library/jest-dom/blob/main/CHANGELOG.md)
- [Commits](https://github.com/testing-library/jest-dom/compare/v5.11.9...v5.11.10)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 21:21:14 -04:00
dependabot[bot] 32ad982b11 Bump github.com/golangci/golangci-lint from 1.38.0 to 1.39.0
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.38.0 to 1.39.0.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.38.0...v1.39.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 21:20:57 -04:00
dependabot[bot] 87b04607ad Bump @testing-library/react-hooks from 5.1.0 to 5.1.1 in /ui
Bumps [@testing-library/react-hooks](https://github.com/testing-library/react-hooks-testing-library) from 5.1.0 to 5.1.1.
- [Release notes](https://github.com/testing-library/react-hooks-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-hooks-testing-library/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-hooks-testing-library/compare/v5.1.0...v5.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 21:17:18 -04:00
dependabot[bot] 4e41ef7542 Bump github.com/microcosm-cc/bluemonday from 1.0.4 to 1.0.6
Bumps [github.com/microcosm-cc/bluemonday](https://github.com/microcosm-cc/bluemonday) from 1.0.4 to 1.0.6.
- [Release notes](https://github.com/microcosm-cc/bluemonday/releases)
- [Commits](https://github.com/microcosm-cc/bluemonday/compare/v1.0.4...v1.0.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 21:14:41 -04:00
dependabot[bot] 6af45d6eea Bump @testing-library/user-event from 13.0.7 to 13.1.1 in /ui
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 13.0.7 to 13.1.1.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v13.0.7...v13.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 21:14:13 -04:00
dependabot[bot] 69fe771819 Bump @testing-library/react from 11.2.5 to 11.2.6 in /ui
Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 11.2.5 to 11.2.6.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v11.2.5...v11.2.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 21:10:30 -04:00
dependabot[bot] ce675d478a Bump github.com/onsi/ginkgo from 1.15.2 to 1.16.0
Bumps [github.com/onsi/ginkgo](https://github.com/onsi/ginkgo) from 1.15.2 to 1.16.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v1.15.2...v1.16.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-05 21:10:18 -04:00
Balaguru Ragupathi 6988b9a086
Improved Header Readability for Songs List (#985)
* style(SongDataGrid): Table Header Definition - #943

Signed-off-by: Balaguru4580 <balaguru4580@gmail.com>

* style(SongDataGrid): Improved Header Readability - #943

Signed-off-by: Balaguru4580 <balaguru4580@gmail.com>

* Shadow Effect

* Shadow Effect Opacity Adjustment

* Fixed Songs Context Menu

* Fixed the Songs Context Menu
2021-04-05 20:32:57 -04:00
Aldrin Jenson 55c2431b17
Fix undefined variant prop in DateField (#987) 2021-04-05 20:27:40 -04:00
Ritik Pandey 69ee17402f
Add pagination to playlists (#969)
* add pagination

* prettier applied

* perPage_bug_fixed

* pagination_component_changed

* getAllSongs function added

* pagination component updated

* catch_error from data provider

* getAllSongsAndDispatch added

* remove ids from action function
2021-04-05 18:21:47 -04:00
Deluan cdfdf78c73 Revert "style(SongDataGrid): Improved Header Readability (#954)"
This reverts commit 3d58c5ab. It broke the SongContextMenu
2021-04-04 21:46:44 -04:00
sobhanbera ca51372d8d
Add Extra Dark theme (#955)
* added new theme - night

* removed a unused field

* fixed a typo from previous change

* night theme in login window

* changed name

changed the theme name from "Night" to "Extra Dark"

* changed the theme name

* Update index.js

* Rename night.js to extradark.js

* trying something

* formatted

the JS build was failing because I haven't formatted the index.js file with prettier. I got to know about this now.
I think now it will be resolved.
2021-04-04 21:25:54 -04:00
Éric Gaspar a4d07734cd
Update fr.json (#975) 2021-04-04 20:53:47 -04:00
Balaguru Ragupathi 3d58c5ab54
style(SongDataGrid): Improved Header Readability (#954)
* style(SongDataGrid): Table Header Definition - #943

Signed-off-by: Balaguru4580 <balaguru4580@gmail.com>

* style(SongDataGrid): Improved Header Readability - #943

Signed-off-by: Balaguru4580 <balaguru4580@gmail.com>

* Shadow Effect

* Shadow Effect Opacity Adjustment
2021-04-02 22:15:59 -04:00
Aldrin Jenson 12223b2a83
Fix extra multiline Prop error (#966)
* Fix extra multiline Prop error

* Remove multiline prop from MultiLineTextField
2021-04-02 22:09:28 -04:00
Samarjeet c7dc3628e2
Fix transparent bg in suggestions [Spotify-ish] (#964) 2021-04-02 13:30:27 -04:00
certuna 9871919fae
New service worker (#952)
* Add files via upload

* Update serviceWorker.js
2021-04-02 11:58:45 -04:00
Deluan 0cb7d3853f Add required prop `order` in random album list. Fix #957 2021-04-01 23:14:59 -04:00
rochakjain361 d0d18e8512
Album details UI fix (#922)
* Fix the Album Details UI to look similar to Song Details UI

* Remove the unused components

* Fix the gap between row and the first field in the details view

* Fix the width of the row of Album Details UI
2021-04-01 23:03:05 -04:00
Deluan 0d95c4bb9d Fix Code of Conduct link 2021-04-01 17:38:55 -04:00
Samarjeet ea65da484b
Make spotify-ish more spotify-ish (#914)
* [Theme] Allow customising album and player parts

* [Theme] Allow customizing song lists view

* Make spotify-ish more spotify-ish

* Fix responsive issues in spotify-ish

* Spotify-ish login page

* Add back the previous "Spotify-ish" theme as "Green"

Co-authored-by: Deluan <deluan@navidrome.org>
2021-03-31 16:58:47 -04:00
harshavardhanpb 5128c049d7
Rename diodo_test.go to diode_test.go (#956)
Simple typo fix
2021-03-31 15:41:38 -04:00
Deluan 16f6d9466f Remove redundant `backgroundColor` from Login icon 2021-03-31 13:31:03 -04:00
Samarjeet cf72bbfad4
Fix login page UI contrast in dark,spotify (#946) 2021-03-31 01:11:05 -04:00
Aldrin Jenson 20f5778694
Fix prop undefined bug #925 (#942)
* fix(albumListView)  prop undefined bug  #925

* Fix undefinedProp bug
2021-03-31 01:07:07 -04:00
Deluan 46d4c48d44 Login backgrounds from unsplash collection (#936) 2021-03-31 00:49:12 -04:00
Samarjeet 166410eb50
Login backgrounds from unsplash collection (#936) 2021-03-31 00:24:37 -04:00
Deluan 43cbde97ad Remove "minimize" button from Player when in Desktop resolution 2021-03-30 22:43:39 -04:00
Deluan 13e80e651c Fix issue with classes being removed from DOM. Fix #864 2021-03-30 22:42:46 -04:00
Deluan 16e495a80f Revert: Fix theme not being applied to PlayerToolbar
It was causing issues with classes being removed from DOM
2021-03-30 22:21:24 -04:00
Samarjeet 1f2b5294c3
Allow theme customizing Login Page (#940) 2021-03-29 23:25:32 -04:00
Aldrin Jenson a36a8c2372
Fix LinkWrapping Error in the console #921 (#924) 2021-03-29 21:45:48 -04:00
Aldrin Jenson 5245b4c62b
Fix cacheUndefined bug - #901 (#915)
- add check to see if cache is defined before deleting
2021-03-29 20:43:51 -04:00
Deluan 3b0defefec Fix UI loading redirections. Should fix #906 2021-03-29 20:13:04 -04:00
Neil Chauhan 404253a881
Enable turn on/off Favorites/Loved feature. (#917)
* Added option to enable/disable favorites in Song

* Added option to enable/disable favorites in Artist

* Added option to enable/disable favorites in Albums and Favorites tab in sidebar

* Added option to enable/disable favorites in Player

* Set default value of enableFavourites as true

* Improved the functionality to enable/disable Favorites

* Add `EnableFavourites` config option

Co-authored-by: Deluan <deluan@navidrome.org>
2021-03-28 20:35:49 -04:00
Adrian Edwards 5dfcb316cf
Remove unused prop from ArtistList (#926)
* remove syncWithLocation prop from ArtistList to fix #925

* run prettier
2021-03-28 19:53:25 -04:00
Deluan 90cf118349 Fix context menu/heart column header alignment in SongList 2021-03-27 22:09:43 -04:00
Deluan b42532dd7c Fix theme not being applied to PlayerToolbar 2021-03-27 14:24:59 -04:00
Neil Chauhan ac37bf3631
Refactored the current ️/Star feature to ❤️/Love/Favourite feature. (#908)
* Added setRating feature to AlbumListView

* Refactored the iconography from  to ❤️

* Refactored the current component from StarButton to LoveButton

* Refactored all translations from Starred to Loved, and all props from showStar to showLove

* Refactored useToggleStar hook to useToggleLove

* rebased repository from master and removed stray commmits

* Refactored handler name from TOGGLE_STAR to TOGGLE_LOVE in PlayerToolbar.js

* Change "starred" translation to "Favorite"

Co-authored-by: Deluan <deluan@navidrome.org>
2021-03-26 23:56:19 -04:00
Deluan db208600e4 Fix theme not being applied to Player's audioTitle 2021-03-26 22:22:36 -04:00
Arbaz Ahmed 01ba00ccdd
New component for mobile Artist List (#891)
Fixes #890
2021-03-25 22:45:21 -04:00
Yash Jipkate e575825c33
Add `/` to `_` mapping for paths based on tags. (#888)
Closes  #592
2021-03-25 21:48:28 -04:00
Ritik Pandey 5abc215270
Hide BulkActionsToolbar after removing songs from playlist (#898) 2021-03-25 21:40:31 -04:00
Deluan Quintão 210f34bbbe
Update CONTRIBUTING.md 2021-03-24 23:36:48 -04:00
Kushal Kumar 5e70e0702c
docs(contributing.md): Contributing guidelines added (#831)
* docs(contributing.md): Contributing guidelines added - I827

Signed-off-by: k-kumar-01 <kushalkumargupta4@gmail.com>

* docs(contributing.md): Contributing guidelines added - I827

Updated the issue number to match the existing issue
Changed channel from IRC to Discord

Signed-off-by: k-kumar-01 <kushalkumargupta4@gmail.com>

* Fix typos, remove issue template

Co-authored-by: Deluan <deluan@navidrome.org>
2021-03-24 23:33:48 -04:00
Danshil Kokil Mungur a85b70e9db
feat(github): add issue templates (#892) 2021-03-24 23:21:48 -04:00
Josep Mª Domingo 515aa7108b
Move logger middleware to capture routing errors (ex: 405). (#877)
* Fix #836

* Remove requestLogger middleware from MountRouter
2021-03-24 23:17:36 -04:00
rohitgeddam cdb387b22f
Properly break long comment lines. Fix #855 (#856)
Make ui responsive on smaller screen when the comment block is longer
2021-03-24 23:14:10 -04:00
Deluan d5434d4169 Add `'lint` to pre-push git hook 2021-03-24 12:21:20 -04:00
Deluan c46aa72ede Add `lint` script to UI project 2021-03-24 12:05:58 -04:00
Deluan 4b68260b83 Move constant to `consts.go` file 2021-03-24 10:21:31 -04:00
Deluan Quintão ba922bbfce
Update translations (#894)
* Add Ukrainian translation

* Update cs.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update eo.json (POEditor.com)

* Update de.json (POEditor.com)
2021-03-23 22:28:22 -04:00
dependabot[bot] b4a2683e65 Bump @testing-library/user-event from 12.6.2 to 13.0.7 in /ui
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 12.6.2 to 13.0.7.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v12.6.2...v13.0.7)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-23 22:26:09 -04:00
ImgBotApp 7e225c0f47 [ImgBot] Optimize images
*Total -- 8,303.62kb -> 6,130.18kb (26.17%)

/ui/src/icons/paused-light.png -- 0.81kb -> 0.29kb (63.6%)
/.github/screenshots/ss-mobile-login.png -- 1,189.03kb -> 736.30kb (38.08%)
/ui/src/icons/playing-dark.gif -- 2.29kb -> 1.58kb (31.29%)
/ui/src/icons/playing-light.gif -- 2.29kb -> 1.58kb (31.29%)
/.github/screenshots/ss-mobile-player.png -- 1,257.85kb -> 886.30kb (29.54%)
/ui/public/apple-touch-icon-152x152.png -- 9.51kb -> 6.74kb (29.21%)
/ui/public/mstile-144x144.png -- 9.63kb -> 6.88kb (28.55%)
/ui/public/mstile-70x70.png -- 6.59kb -> 4.73kb (28.22%)
/ui/public/apple-touch-icon-180x180.png -- 11.34kb -> 8.18kb (27.89%)
/ui/public/apple-touch-icon.png -- 11.34kb -> 8.18kb (27.89%)
/ui/public/apple-touch-icon-76x76.png -- 4.52kb -> 3.27kb (27.68%)
/ui/public/apple-touch-icon-120x120.png -- 7.27kb -> 5.30kb (27.2%)
/ui/public/android-chrome-192x192.png -- 13.14kb -> 9.78kb (25.59%)
/ui/public/mstile-150x150.png -- 9.57kb -> 7.20kb (24.8%)
/ui/public/apple-touch-icon-60x60.png -- 3.40kb -> 2.57kb (24.51%)
/ui/src/icons/android-icon-72x72.png -- 6.25kb -> 4.75kb (24%)
/.github/screenshots/ss-desktop-player.png -- 5,016.75kb -> 3,833.46kb (23.59%)
/resources/logo-192x192.png -- 17.10kb -> 13.21kb (22.78%)
/ui/public/android-chrome-512x512.png -- 38.32kb -> 29.78kb (22.3%)
/ui/public/mstile-310x310.png -- 21.04kb -> 16.49kb (21.61%)
/ui/public/mstile-310x150.png -- 10.35kb -> 8.12kb (21.56%)
/resources/navidrome-600x600.png -- 369.74kb -> 297.43kb (19.56%)
/ui/public/favicon-32x32.png -- 2.18kb -> 1.80kb (17.52%)
/.github/screenshots/ss-mobile-album-view.png -- 282.95kb -> 236.01kb (16.59%)
/ui/src/icons/paused-dark.png -- 0.34kb -> 0.29kb (14.25%)

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
2021-03-23 10:29:13 -04:00
Arbaz Ahmed 4e44d841dd
New component for song display in song list (#833)
* added new component SONGSIMPLELIST for smaller displays

* added new component SONGSIMPLELIST for smaller displays

* added new component SONGSIMPLELIST for smaller displays

* Updated songsimplelist

Removed truncation

* removed garbage code

* refactored some issues of overlapping

* refactored some issues of overlapping

* changed the song ui design

* refactored some bugs in artist display

* refactored some bugs in artist display

* removed garbage dependencies

* removed div bugs

* added all the logic to the component itself
2021-03-23 00:12:19 -04:00
rochakjain361 b552eb15b3
Make the version number clickable for the SNAPSHOT version in development docker build (#843)
* Make the version number clickable for the SNAPSHOT version while using development docker build

* Update the snapshot version link in to view list of commits since the release

* Create a new component for the link to the version

* Add tests and refactored a bit

Co-authored-by: Deluan <deluan@navidrome.org>
2021-03-22 23:34:34 -04:00
Deluan 190bcd836e Bump @testing-library's dependencies 2021-03-22 15:40:06 -04:00
dependabot-preview[bot] 488a05bc5a Create Dependabot config file 2021-03-22 15:12:29 -04:00
Deluan 2daefb851a Bump golangci-lint to 1.38.0 2021-03-22 14:52:16 -04:00
Deluan 7414216094 Bump gomega and ginkgo versions 2021-03-22 14:49:57 -04:00
Deluan 300a0292ba Add timestamp indexes 2021-03-22 13:38:20 -04:00
Deluan a4abe6ea2b Rename `migration` package to `migrations` 2021-03-22 13:38:04 -04:00
Nelyah 2671933791 Update Spanish translation
This also fixes some minor typos
2021-03-22 10:23:26 -04:00
Nelyah da6556bb78 Update French translation 2021-03-22 10:23:26 -04:00
Deluan c33c71ae6d Comment out flaky tests 2021-03-22 09:52:29 -04:00
certuna 1ea3d005e8
clear the ServiceWorker cache on logout (#854)
* Update authProvider.js

This fixes https://github.com/navidrome/navidrome/issues/613 : clears the ServiceWorker cache on logout. Based on this code: https://developer.mozilla.org/en-US/docs/Web/API/CacheStorage/keys . Seems to work on macOS and iOS, but please test on other platforms.

* Format code with `prettier`

Co-authored-by: Deluan <deluan@navidrome.org>
2021-03-21 14:34:10 -04:00
Ritik Pandey 9fb55d4025
Add duplicate song warning. Fix #554
* duplicate_song_warning added

* dialog_for_multiple_songs

* skip button updated

* duplicate_song_skip import removed

* duplicate_song msg updated

* handleSkip and checkDuplicateSong func modified

* Update AddToPlaylistDialog.js

* prettier applied

* go.sum file added

* duplicated songs bug fixed
2021-03-21 13:29:35 -04:00
Yash Jipkate 3e0e11c01e
Fix #260: Add `Auto` theme preference to set theme automatically. (#835)
* Auto theme preference added

* Fix lint

* Add and use AUTO from consts

* Add shared custom hook to get current theme

* Moved up 'Auto' choice

* AUTO -> AUTO_THEME_ID & extract useCurrentTheme to file

* Liberalise theme setting

* Add tests
2021-03-21 13:19:43 -04:00
Deluan fa479f0a9a Show `go mod download` commands 2021-03-17 10:33:40 -04:00
Ketan Gupta 8b6e32588c improved makefile message to developer 2021-03-17 10:27:55 -04:00
Dnouv b62c270b7f fix min-width of AlbumDetails 2021-03-16 23:19:11 -04:00
Ye61123 5488a829c7 Update zh-Hans.json
Improve the simplified Chinese translation accuracy and fix the parts that are not translated.
2021-03-16 23:13:40 -04:00
Daniel Morante 9d87eefd6a Create rc.d startup script for FreeBSD
Same startup script used by the port multimedia/navidrome
2021-03-16 23:11:42 -04:00
Deluan 03afb4ff0e Call `go mod tidy` after `go mod download` to undo any changes to `go.sum` 2021-03-16 12:49:20 -04:00
Deluan 63b9353452 Lowering the expectations caused by the name :) 2021-03-14 12:51:08 -04:00
k-kumar-01 72d6df15c6 style: New Theme Added - Spotify
Signed-off-by: k-kumar-01 <kushalkumargupta4@gmail.com>
2021-03-14 12:51:08 -04:00
Deluan 18fda0d954 Update DevContainer to GoLang 1.16 2021-03-12 18:43:03 -05:00
Deluan 34516ccf97 Bump github.com/sirupsen/logrus from 1.7.0 to 1.8.1 2021-03-12 18:19:39 -05:00
Deluan 720e2357b7 Add option to sort Recently Added by file's mtime instead of time of import. 2021-03-12 18:18:35 -05:00
Deluan 1ec105a245 Invalidate cached images when album changes 2021-03-12 15:41:11 -05:00
Deluan 0049d8d311 Bump GoLang to 1.16.2 for releases 2021-03-12 15:14:12 -05:00
Deluan Quintão 2d528bbc87
Remove dependency of go-bindata (#818)
* Use new embed functionality for serving UI assets

* Use new embed functionality for serving resources. Remove dependency on go-bindata

* Remove Go 1.15
2021-03-12 11:06:51 -05:00
rochakjain361 5a259ef3ff
Make the version number in the about dialog clickable (#817)
* Make the version number in the about dialog clickable

* Fix prettier errors

* Fix build errors
2021-03-12 10:26:41 -05:00
Nelyah 43f2d82956 Accept more recent node and Go versions when building dev or server
This will allow developers to experiment with different versions of Go.
2021-03-11 23:02:13 -05:00
Nelyah dff18101bb Bump Go version number in go.mod to 1.16
This will allow to use 'embed' and 'fs' packages.
This also makes the check for the Go environment when running the
Makefile fail if Golang version isn't 1.16.x
2021-03-11 23:02:13 -05:00
dpirad007 6cf15748c4 SelectPlaylist Input mobile view fix 2021-03-11 22:53:55 -05:00
Deluan 54a3394559 Upgrade to GoLang 1.16.0 2021-02-19 21:00:38 -05:00
Deluan 8436e18175 Update pipeline tests to Go 1.16.0 2021-02-19 19:52:14 -05:00
Deluan a140c222c2 Fix race condition in test 2021-02-19 19:36:55 -05:00
Deluan 64ceb5371b Revert "Bump github.com/go-chi/chi from 1.5.1 to 1.5.2"
This caused a panic and needs more investigation:

http: superfluous response.WriteHeader call from github.com/go-chi/chi/middleware.Recoverer.func1.1 (recoverer.go:33)

 panic: interface conversion: *middleware.compressResponseWriter is not io.ReaderFrom: missing method ReadFrom

 -> github.com/go-chi/chi/middleware.(*httpFancyWriter).ReadFrom
 ->   /Users/deluan/go/pkg/mod/github.com/go-chi/chi@v1.5.2/middleware/wrap_writer.go:135
2021-02-15 14:21:43 -05:00
Deluan 7eb99d0b8d Remove duplicated call to `split` 2021-02-15 14:18:57 -05:00
Deluan 07f815edfd go mod tidy 2021-02-15 14:08:49 -05:00
Deluan 781155ff39 Replace cursor with pointer when hovering over an expandable comment.
Fixes https://github.com/navidrome/navidrome/issues/637#issuecomment-778599670
2021-02-15 14:05:34 -05:00
dependabot-preview[bot] 0d6717ce69 Bump github.com/spf13/cobra from 1.1.1 to 1.1.3
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.1.1 to 1.1.3.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Changelog](https://github.com/spf13/cobra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spf13/cobra/compare/v1.1.1...v1.1.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-15 13:21:16 -05:00
dependabot-preview[bot] e0198f741f Bump github.com/go-chi/chi from 1.5.1 to 1.5.2
Bumps [github.com/go-chi/chi](https://github.com/go-chi/chi) from 1.5.1 to 1.5.2.
- [Release notes](https://github.com/go-chi/chi/releases)
- [Changelog](https://github.com/go-chi/chi/blob/master/CHANGELOG.md)
- [Commits](https://github.com/go-chi/chi/compare/v1.5.1...v1.5.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-15 13:19:53 -05:00
Deluan cfc9162729 Use a Waiter diode, to avoid constant CPU usage. Fixes #777 2021-02-13 12:08:32 -05:00
Deluan 48847ae479 Don't break if it tries to render ContextMenu without data. Fix #776 2021-02-13 12:04:02 -05:00
CgX fbe6ecea95 Update fr.json 2021-02-11 09:13:24 -05:00
Nelyah 9d098e2302 Update French translation 2021-02-10 10:00:33 -05:00
Deluan 107803e037 Update list of Not Implemented / Gone Subsonic API endpoints 2021-02-09 21:25:14 -05:00
Gosz 1c285439ca Update spanish translation 2021-02-09 16:47:01 -05:00
Deluan bde8692add Update Dutch translation 2021-02-09 16:46:29 -05:00
Deluan 1d681d92d3 Better explanation of NewSpreadFS 2021-02-09 15:33:34 -05:00
Deluan 157faad028 Rename ExternalInfo to ExternalMetadata 2021-02-09 15:33:33 -05:00
Deluan 5fdd8b32d7 Move utilitarian/generic packages to utils: lastfm, spotify, gravatar, cache, and pool 2021-02-09 15:33:33 -05:00
Deluan b855fe865e Add artist ID to agent's interfaces 2021-02-09 11:19:32 -05:00
Andy Klimczak 501af14186 Upgrade pipeline to use docker/setup-buildx-action
Issue #563
2021-02-08 21:05:27 -05:00
Deluan 29465d92a7 Add Chinese Traditional translation (thanks @Leitftw) 2021-02-08 19:09:42 -05:00
Deluan 7cc026ac35 Add some info about how to create new agents 2021-02-08 17:18:43 -05:00
Deluan fefbe0b117 Cleanup, add Placeholder agent 2021-02-08 16:54:51 -05:00
Deluan e5cbfac483 Implement TopSongs 2021-02-08 16:54:51 -05:00
Deluan e1cb52689e Implement SimilarSongs 2021-02-08 16:54:51 -05:00
Deluan 84a50d5dce Use MBID in calls to Last.FM, if it is available 2021-02-08 16:54:51 -05:00
Deluan 6c1fc5f836 Clean names before calling agents 2021-02-08 16:54:51 -05:00
Deluan a76a52e99a Get MBID first, if it is not yet available 2021-02-08 16:54:51 -05:00
Deluan 52a407b84b Clean up, comments and logs 2021-02-08 16:54:51 -05:00
Deluan 365dff6435 Fix lint errors 2021-02-08 16:54:51 -05:00
Deluan 877cdf1d5c Get images 2021-02-08 16:54:51 -05:00
Deluan 28cdf1e693 Add a cached http client 2021-02-08 16:54:51 -05:00
Deluan 9d24106066 Incomplete implementation of agents 2021-02-08 16:54:51 -05:00
dependabot-preview[bot] d8a4db36ef Bump react-icons from 4.1.0 to 4.2.0 in /ui
Bumps [react-icons](https://github.com/react-icons/react-icons) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v4.1.0...v4.2.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-08 11:29:27 -05:00
Deluan 8799358a04 go mod tidy 2021-02-07 14:26:32 -05:00
dependabot-preview[bot] 58fd5326f5 Bump github.com/onsi/gomega from 1.10.4 to 1.10.5
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.10.4 to 1.10.5.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.10.4...v1.10.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-06 22:21:02 -05:00
dependabot-preview[bot] e9066690fd Bump github.com/onsi/ginkgo from 1.14.2 to 1.15.0
Bumps [github.com/onsi/ginkgo](https://github.com/onsi/ginkgo) from 1.14.2 to 1.15.0.
- [Release notes](https://github.com/onsi/ginkgo/releases)
- [Changelog](https://github.com/onsi/ginkgo/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/ginkgo/compare/v1.14.2...v1.15.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-02-06 22:09:41 -05:00
Deluan a427d6c940 Fix Docker pulls badge 2021-02-06 22:04:01 -05:00
Deluan 949bcde9f5 Move project to Navidrome GitHub organization 2021-02-06 21:47:19 -05:00
Deluan 6ee45a9ccc Move project to Navidrome GitHub organization 2021-02-06 21:46:35 -05:00
Deluan bc609be3c9 Fix space hotkey 2021-02-05 13:10:58 -05:00
Deluan 4b373560c6 Do not trigger next/prev event handlers when Cmd (meta) is pressed 2021-02-05 13:03:36 -05:00
Deluan bb6197360b Upgrade to latest go-chi 2021-02-05 12:13:35 -05:00
Deluan 2f4a5fd9ae Fix test suite name 2021-02-04 15:44:44 -05:00
Deluan 26f838167e Add tests to diode 2021-02-04 15:42:08 -05:00
Deluan c7af3b8256 Add test to Event 2021-02-04 12:43:46 -05:00
Deluan 7adacbac0d Removed event.type from SSE, as it was causing the browser to hang.
Needs more investigation, but for now, back to the simple message format
2021-02-04 12:37:06 -05:00
Deluan 77fc4841e4 Remove option to download discs of a set 2021-02-03 23:05:15 -05:00
Aries f43999842f
Update Japanese translation (#757)
* Fix ja translation

* Fix japanese translation
2021-02-03 22:08:11 -05:00
Deluan 64b22688ba Fix Portuguese transaltion 2021-02-03 19:27:14 -05:00
Deluan e9dad3dd67 Update Portuguese transaltion 2021-02-03 19:13:44 -05:00
Deluan 847531391d Help dialog with available hotkeys 2021-02-03 19:08:03 -05:00
Deluan a168f46b95 Better hotkey organization 2021-02-03 18:29:33 -05:00
Deluan 22145e070f Replace custom chunking logic with a `utils.BreakUpStringSlice` call 2021-02-03 17:26:03 -05:00
Deluan Quintão 9a3e75be00
Add tests with GoLang 1.16-RC to the pipeline 2021-02-03 14:39:59 -05:00
Deluan 618d5fc81f Better duration formatting in logs 2021-02-03 13:04:20 -05:00
Deluan 9668263235 Logging when triggering manual scan 2021-02-03 00:27:59 -05:00
Deluan 9959862791 Replace react-hotkeys-hook with react-hotkeys 2021-02-02 23:13:18 -05:00
Deluan 8e02659441 Do not sanitize Album comments. This was already handled in the backend, when importing. Fix #715 2021-02-02 19:08:00 -05:00
Deluan 905c685696 Use diodes instead of channels in SSE broker 2021-02-02 18:55:08 -05:00
Deluan 591a5344ac Workaround to remember logarithmic volume 2021-02-02 18:29:20 -05:00
Deluan e79922def1 Fix React "unique key prop" warning 2021-02-02 18:00:06 -05:00
Deluan a3eb14d7f4 Fix displaying album year when viewing an artist's albums 2021-02-02 17:49:11 -05:00
Deluan 3b52df5efd Update golangci-lint in the pipeline 2021-02-01 16:54:40 -05:00
Deluan 031756caf6 Update canisue-lite 2021-02-01 16:45:33 -05:00
Deluan 70470e4c81 Increase heap memory for JS job 2021-02-01 16:40:55 -05:00
Deluan 58a52c31c2 Turn off memory profiling, saving a couple of megabytes 2021-02-01 16:30:06 -05:00
Deluan 1f3bc4d202 Use tools.go commands without installing 2021-02-01 16:16:30 -05:00
Deluan 950ba9f77e Bump github.com/google/wire from 0.4.0 to 0.5.0 2021-02-01 08:54:35 -05:00
Deluan 861b406575 Use new simplified `uuid.NewString()` syntax 2021-02-01 01:22:31 -05:00
Deluan b47ec02f02 Reenable ImageCache and ActivityPanel as default 2021-02-01 00:31:02 -05:00
Deluan 7cc9fbaaf9 Revert: Use modified time as updated_at and created_at when refreshing/creating albums 2021-02-01 00:30:45 -05:00
Deluan 9807b0b6c0 Use modified time as updated_at and created_at when refreshing/creating albums. Closes #717 2021-01-31 22:17:40 -05:00
Deluan 1af78e9503 Build and publish Docker image for armv6 (closes #747) 2021-01-31 19:36:18 -05:00
Deluan Quintão 02c228da1b
Update Translations (#751)
* Update zn.json (POEditor.com)

* Update cs.json (POEditor.com)

* Update da.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update eo.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update de.json (POEditor.com)

* Update it.json (POEditor.com)

* Update ja.json (POEditor.com)

* Update pl.json (POEditor.com)

* Update pt.json (POEditor.com)

* Update ru.json (POEditor.com)

* Update es.json (POEditor.com)

* Update th.json (POEditor.com)

* Update tr.json (POEditor.com)

* Update pt.json (POEditor.com)
2021-01-31 19:04:48 -05:00
Deluan f7aa5c452c Add translation to skip_nav 2021-01-31 19:01:56 -05:00
Deluan Quintão 6c53ce4bb3 Update en.json (POEditor.com) 2021-01-31 18:57:07 -05:00
Deluan f325907da4 Upgrade react-admin to 3.12.0 2021-01-31 18:50:27 -05:00
Deluan 2c89e0dc13 Bump react-drag-listview from 0.1.7 to 0.1.8 in /ui 2021-01-31 18:30:26 -05:00
Deluan 7d23eca721 Bump @testing-library dependencies 2021-01-31 18:26:25 -05:00
Deluan 77705e4d79 Upgrade react-jinke-music-player to 4.21.2
Enable fadeIn/fadeOut when pausing/playing and logarithmic volume (fix #668)
2021-01-31 18:23:32 -05:00
Deluan afbd9a3b37 Bump github.com/google/uuid from 1.1.2 to 1.2.0 2021-01-31 17:56:06 -05:00
Deluan 41ec44ae1b Bump github.com/pressly/goose from 2.6.0+incompatible to 2.7.0+incompatible 2021-01-31 17:48:08 -05:00
Deluan dc8051ed53 Bump github.com/golangci/golangci-lint from 1.33.0 to 1.36.0 2021-01-31 17:43:03 -05:00
Deluan c5686c4884 Replace periodic scanner cancellation channel with a context 2021-01-31 17:37:54 -05:00
Deluan 9520c30c32 Fix "failed" Subsonic response. Fix #716 2021-01-07 08:24:13 -05:00
Deluan 069199b2d8 Removed invalid comment 2021-01-03 18:16:37 -05:00
Deluan 05ffdede56 Bump react-hotkeys-hook from 2.4.0 to 3.0.0 in /ui 2021-01-03 18:05:31 -05:00
Deluan b12b3c49bd Bump @material-ui/lab from 4.0.0-alpha.56 to 4.0.0-alpha.57 2021-01-03 18:00:39 -05:00
Deluan 0f29da966e Bump @testing-library/user-event from 12.5.0 to 12.6.0 in /ui 2021-01-03 17:56:25 -05:00
Deluan 81d9750fa6 Bump react-icons from 3.11.0 to 4.1.0 in /ui 2021-01-03 17:52:53 -05:00
dependabot-preview[bot] 0af54e43dc Bump uuid from 8.3.1 to 8.3.2 in /ui
Bumps [uuid](https://github.com/uuidjs/uuid) from 8.3.1 to 8.3.2.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v8.3.1...v8.3.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2021-01-02 13:26:27 -05:00
Steve Richter 0d35148152 Check for window.isSecureContext when determining Notification support 2020-12-28 20:46:11 -05:00
Deluan 7c23bd0890 Fix log message, as it is also used for `taglib` 2020-12-25 12:45:38 -05:00
Deluan 10e52bdd3f Use order_* fields for sorting by album and artist 2020-12-25 12:37:16 -05:00
Deluan 9e84ce42b5 Use same album songs order for UI and Subsonic API 2020-12-25 12:37:16 -05:00
lbonn 15b289201a Fall back to media file path when sorting
If files cannot be sorted by disc and track id, try by artist then
title.

One use case is a loose compilation of files with same album, album
artist, and no track numbers. File order was then undetermined, in
practice depended on insertion order in the database.
2020-12-25 12:37:16 -05:00
Deluan cd1c693a23 Remove superfluous argument 2020-12-24 10:39:10 -05:00
Deluan 2073871fa1 Use netgo (instead of C bindings). Fix #700 2020-12-23 15:29:18 -05:00
Deluan dab83c4f6a Disambiguate tracks by AlbumArtist when sorting by album 2020-12-23 11:38:40 -05:00
Deluan db5b9246dd Handle more sort/order cases 2020-12-23 11:37:38 -05:00
Deluan cdae4347a6 Make ServerStart variable global 2020-12-21 11:39:38 -05:00
Deluan 8c063c4f0c Removed unused variable 2020-12-21 10:01:37 -05:00
Deluan 14b060a42a Only close connection if the write times out 2020-12-20 15:21:46 -05:00
Deluan 1804fb3e50 Fix duration formatting, add days and don't show 60 seconds 2020-12-20 13:29:09 -05:00
Deluan ea2f94658a Error should always be nil 2020-12-20 13:28:33 -05:00
Deluan 4b38a13243 Make event handlers naming consistent (camelCase) 2020-12-20 13:28:11 -05:00
Deluan 29817db9f2 Simplify worker pool 2020-12-15 20:48:06 -05:00
dependabot-preview[bot] fc4ddee122 Bump github.com/onsi/gomega from 1.10.3 to 1.10.4
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.10.3 to 1.10.4.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.10.3...v1.10.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-14 08:06:22 -05:00
Deluan a241865209 Add elapsed time when scanner does not detect any new changes 2020-12-13 20:48:16 -05:00
Deluan ea09629803 Fix another possible race condition 2020-12-13 20:17:20 -05:00
Deluan f2fd7ed016 Fix race condition that could cause multiple EventSource connections from the same client 2020-12-13 14:44:57 -05:00
Deluan 4f90fa9924 Add denormalized list of artist_ids to album, to speed-up artist's albums queries
This will be removed once we have a proper many-to-many relationship between album and artist
2020-12-13 14:05:48 -05:00
Deluan f86bc070de Don't break on login when activity panel is disabled 2020-12-13 12:16:02 -05:00
Deluan 1d338417e9 Make `done` channel buffered 2020-12-13 11:58:00 -05:00
Deluan d685aefab3 Don't ever stop the `listen` go routine 2020-12-12 23:04:50 -05:00
Deluan e27d917bd4 Forgot to allocate `done` channel 2020-12-12 21:10:43 -05:00
Deluan 8b92796a5c Disconnect the client if the output buffer fills up 2020-12-12 18:26:30 -05:00
Deluan 17833cd9d2 Make names more consistent 2020-12-12 13:46:36 -05:00
Deluan e2969aa34c Use non-blocking event sending 2020-12-12 13:35:49 -05:00
Deluan 500da8bc7b Bump react-icons from 3.11.0 to 4.1.0 2020-12-11 12:21:53 -05:00
Deluan db3b53ff43 Bump prettier from 2.1.2 to 2.2.1 2020-12-11 12:13:21 -05:00
Deluan 291d28887b Bump @testing-library dependencies 2020-12-11 12:10:46 -05:00
Deluan 3c6b8d18cd Bump golangci-lint from 1.32.2 to 1.33.0 2020-12-11 11:40:06 -05:00
Deluan 0111d3ae60 Bump react-admin from 3.10.2 to 3.10.4 2020-12-11 11:34:25 -05:00
Deluan 0cde8cbf2e Fix logging field case 2020-12-11 11:26:06 -05:00
dependabot-preview[bot] b08eac54fd Bump github.com/Masterminds/squirrel from 1.4.0 to 1.5.0
Bumps [github.com/Masterminds/squirrel](https://github.com/Masterminds/squirrel) from 1.4.0 to 1.5.0.
- [Release notes](https://github.com/Masterminds/squirrel/releases)
- [Commits](https://github.com/Masterminds/squirrel/compare/v1.4.0...v1.5.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-12-10 16:04:29 -05:00
dependabot-preview[bot] f7411558e3
[Security] Bump ini from 1.3.5 to 1.3.7 in /ui (#686)
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.7. **This update includes a security fix.**
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-12-10 15:38:44 -05:00
Deluan a74b365feb Only adds route to `/events` if Activity Panel is enabled 2020-12-09 15:33:37 -05:00
Deluan 350f1dc951 Docker run does not need to be interactive for building snapshots 2020-12-07 13:37:22 -05:00
Deluan 25ae1c6cdd Return album art as a Reader 2020-12-02 09:13:36 -05:00
Deluan 0aaa261a71 Don't show warning about image cache disabled if pre-cache warmer is disabled 2020-12-02 08:52:35 -05:00
Deluan Quintão f2a8308925
Update translations (#673)
* Update zn.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update de.json (POEditor.com)

* Update ja.json (POEditor.com)
2020-12-01 16:34:50 -05:00
Deluan 240149cda4 DIsable Image Cache by default.
See: https://github.com/deluan/navidrome/issues/446#issuecomment-736574447
2020-12-01 18:01:16 +00:00
Deluan ae58ac6a6c Add configuration for VSCode's Remote Container development 2020-12-01 17:57:29 +00:00
Deluan a8c5fa6d49 Fix file descriptor leak in SSE implementation.master
See https://github.com/deluan/navidrome/issues/446#issuecomment-736296465
2020-12-01 09:24:44 -05:00
Deluan 9414ce6549 Bump react-admin to 3.10.2 2020-11-29 22:55:07 -05:00
Deluan 7bd31da0d5 Fix console warning about required property 2020-11-29 20:31:07 -05:00
Deluan 975579ab26 Add option for player to report real paths in Subsonic API. Closes #625 2020-11-28 10:25:23 -05:00
Deluan 7becc18da9 Don't explode when `record` is not loaded yet 2020-11-28 09:44:07 -05:00
Deluan 4ca98fb827 Add hotkey `s` to toggle star.
Refers to #585
2020-11-28 00:52:38 -05:00
Deluan aae66cfcf0 Always show context menu if not in desktop 2020-11-27 23:52:23 -05:00
Deluan 2010fcf4ca Remove possible `undefined` error 2020-11-27 18:53:25 -05:00
Deluan 2ffb28fc2d Replace `classnames` with `clsx` 2020-11-27 18:27:32 -05:00
Deluan 0b729e1cf9 Hide star completely if in Playlist view 2020-11-27 16:24:22 -05:00
Deluan ab856e3dd1 Wrap comment text. Fixes #637 2020-11-27 16:02:02 -05:00
Deluan 90c407b7f6 Also use PureDatagridRow to speed up SongDatagrid 2020-11-27 14:24:22 -05:00
Deluan f7d1b80b69 Simplify AudioTitle on mobile view 2020-11-27 13:30:51 -05:00
Deluan 2b95422e88 Make "star" column aligned with context menu in Album List view 2020-11-27 13:13:51 -05:00
Deluan 7d075b1882 Make SongDatagrid faster by using PureDatagridBody 2020-11-27 13:13:51 -05:00
Deluan 0e9b0d466c Hide row when reordering playlist 2020-11-27 13:13:51 -05:00
Deluan e5c7819586 Fix playlists 2020-11-27 13:13:51 -05:00
Deluan a42fb024be Fix song context menu "on hover" visibility 2020-11-27 13:13:51 -05:00
Deluan f5808288ab Fix Album View 2020-11-27 13:13:51 -05:00
Deluan 3209430ebd Fix artists 2020-11-27 13:13:51 -05:00
Deluan d9893cf84d Bump React-Admin to 3.10.1 2020-11-27 13:13:51 -05:00
Deluan 9064697123 Remove stray console.log 2020-11-27 13:13:51 -05:00
Deluan b6c578e3a2 Change format of events sent by server, leveraging event type and id 2020-11-25 20:46:21 -05:00
Deluan cc5eaf4caf go mod tidy 2020-11-25 19:09:08 -05:00
Deluan f29bb211d1 Better termination handling in Scanner's progress 2020-11-25 19:05:36 -05:00
Deluan Quintão 3ad36ebd2a
Update translations (#644)
* Update eo.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update pt.json (POEditor.com)
2020-11-25 15:58:45 -05:00
Deluan 31d6508b1d Remove reactjkplayer's audioTitle, as it is not used and only causes warnings in the console 2020-11-25 15:37:10 -05:00
Steve Richter bc72f41180 Adjust AudioTitle in Player
- Show info on 2 lines
- Show album
2020-11-25 15:37:10 -05:00
Deluan 63171368ed Disable Activity Panel by default.
You'll need to set `DevActivityPanel` (or `ND_DEVACTIVITYPANEL`) to `true` to re-enable it
2020-11-25 15:29:46 -05:00
Deluan 5137407377 Add "keepalive" resource. It was causing issues in Firefox when using the dataProvider 2020-11-23 21:28:09 -05:00
Deluan 92ba658606 Don't panic if log parameters are invalid 2020-11-22 17:12:53 -05:00
Zane van Iperen 763a3ef267 Fix startup failure when image cache is disabled.
Fixes #655
2020-11-22 17:03:09 -05:00
Deluan a89afb5fcf Fix aspect ratio in Album show view 2020-11-22 15:03:41 -05:00
Jason Walton 69b2fe92f5 Fix aspect ratio for non-square album art. 2020-11-22 15:03:41 -05:00
stefanomarty 3996764486 Update it.json
Just a correction to a few mistypings.
2020-11-21 18:14:44 -05:00
Deluan a288e7e858 Allow the NotificationToggle to be in sync with the selected option in the browser 2020-11-21 02:03:54 -05:00
Steve Richter 14525cd056 Fix formatting 2020-11-21 02:03:54 -05:00
Steve Richter 2397a7e464 Add Desktop Notifications 2020-11-21 02:03:54 -05:00
Steve Richter b8d47d1db4 Fix default getPerPage for 'md' widths 2020-11-21 01:34:36 -05:00
Deluan 48a6ba2956 Bump GoLang to 1.15.5 2020-11-20 21:55:03 -05:00
Deluan 3e8bee4f65 Make eventStream connection/reconnection more reliable
Also more logs on the server
2020-11-20 20:27:30 -05:00
Deluan c8c95bfb47 Remove React console warning 2020-11-20 19:59:54 -05:00
Deluan 666b058ce4 Request album covers when DevFastAccessCoverArt is true 2020-11-18 16:59:06 -05:00
JG d6066c514d
Updated spanish translation nov 2020 (#642)
* Updatind translation

* Updatind translation

* Update spanish translation

Co-authored-by: Gosz <gosh@4geeksmx.com>
2020-11-18 16:58:57 -05:00
Deluan 3c4903bc4e No need to create a new instance of the Artwork service 2020-11-17 12:16:13 -05:00
Deluan af4609727c Goto album page when clicking on player's album cover 2020-11-17 10:33:53 -05:00
Deluan 53b2cdd33d Update Thai language 2020-11-16 16:49:13 -05:00
Deluan 088af9004a Only try to check cover art file for lastUpdated if fast access is not set 2020-11-16 16:39:31 -05:00
Deluan 1ee39835dd Retry connecting to the events endpoint more frequently on first load 2020-11-16 15:38:03 -05:00
Deluan Quintão 972a94dbf0
Update translations (#623)
* Update fr.json (POEditor.com)

* Update de.json (POEditor.com)

* Update es.json (POEditor.com)

* Update tr.json (POEditor.com)

* Update ru.json (POEditor.com)

* Add Thai translation, thanks to AZ11244

* Update cs.json (POEditor.com)

* Update zn.json (POEditor.com)

* Update da.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update it.json (POEditor.com)

* Update th.json (POEditor.com)
2020-11-16 15:22:37 -05:00
Deluan b87f7b6126 Bump react-redux from 7.2.1 to 7.2.2 2020-11-16 15:09:25 -05:00
Deluan f09a6423f7 Bump react-dom from 16.13.1 to 16.14.0 2020-11-16 15:08:02 -05:00
Deluan 49d28d34b4 Bump jwt-decode from 3.0.0 to 3.1.2 2020-11-16 15:06:57 -05:00
Deluan 8b09f0369c Bump react-ga from 3.1.2 to 3.2.1 2020-11-16 15:03:16 -05:00
Deluan 41cbd3aba7 Bump @testing-library dependencies 2020-11-16 15:01:52 -05:00
Deluan a1dcb9a4e3 Show folders scanned instead of files scanned 2020-11-16 00:36:12 -05:00
Deluan be715c3696 Disable scan buttons when there's a scan in progress 2020-11-15 23:13:59 -05:00
Deluan fddded3260 Move star to end of album title. Use flex for album details 2020-11-15 22:00:40 -05:00
Deluan cf90f0a245 Update sizes with SQL, instead of a full rescan 2020-11-15 19:26:16 -05:00
Deluan 8bfaa0ad9d Better detection of ID fields, to use `=` instead of `LIKE` 2020-11-15 18:24:13 -05:00
Deluan 15697a6fa2 Bump github.com/golangci/golangci-lint from 1.32.1 to 1.32.2 2020-11-14 21:37:08 -05:00
Deluan bcb3e1479f Bump github.com/astaxie/beego from 1.12.2 to 1.12.3 2020-11-14 21:35:39 -05:00
Deluan 44d13bd37c Remove stray Printf 2020-11-14 13:38:31 -05:00
Aries 9629c26537
Fix ja translation (#624) 2020-11-14 11:15:21 -05:00
Deluan 1c7f859b5e Add more broker log 2020-11-14 00:44:58 -05:00
Deluan 8b2a550368 Fix test 2020-11-14 00:25:25 -05:00
Deluan b0ea517fdd Add Album comment to Album details 2020-11-14 00:13:43 -05:00
Deluan 08f96639f4 Add Uptime to Activity Panel 2020-11-13 20:09:23 -05:00
Deluan b64bb706f7 Use Gravatar in GetAvatar Subsonic API 2020-11-13 14:57:49 -05:00
Deluan a4ef31251d Enable activity panel by default 2020-11-13 13:44:38 -05:00
Deluan 84cd6b7f34 Add Esperanto translation, thanks to @ebanDev 2020-11-13 12:56:30 -05:00
Deluan df86a8153e New translated terms 2020-11-13 12:51:32 -05:00
Deluan b38be69b14 Make AppBar back to original height 2020-11-13 10:14:01 -05:00
Deluan 48e0d2c99e Trunc long names 2020-11-13 09:33:56 -05:00
Deluan 3dac9ae666 Fix linting error 2020-11-13 00:44:26 -05:00
Deluan 9d7995fd4d Redesign UserMenu, now with support for Gravatar 2020-11-13 00:40:30 -05:00
Deluan 7efc32d136 Ignore "Cover (front)" tag when using `ffmpeg` extractor 2020-11-12 23:17:06 -05:00
Deluan 153cf8f5af Don't display "Comment" field in details if it is empty 2020-11-12 22:01:59 -05:00
Deluan b3f373cdb4 Better Activity panel layout 2020-11-12 21:57:28 -05:00
Deluan 08399c4854 Fix some JS console errors 2020-11-12 20:51:26 -05:00
Deluan 25db696c06 Detect different discs, even when missing the first track of the disc. Fix #620. 2020-11-12 20:33:20 -05:00
Deluan bdad927f11 Fix color of activity icon on light themes 2020-11-12 18:19:54 -05:00
Deluan b1a9dfee13 Replace <hr/> with Material-UI's <Divider/> 2020-11-12 17:08:20 -05:00
Deluan c09ba509b2 Fine tune scan status behaviour 2020-11-12 16:12:31 -05:00
Deluan 0e7163eb2c Sanitize comments and lyrics on import, as they are rendered as HTML on the UI 2020-11-11 12:26:47 -05:00
Deluan 5111cf8c33 Display comments in SongDetails and AlbumList's details 2020-11-11 11:58:03 -05:00
Deluan 98af68ac99 Import comments and lyrics 2020-11-11 10:43:17 -05:00
Deluan aee4eb71c4 Add support for multi-line tags 2020-11-11 09:45:46 -05:00
Deluan 99d454d8b0 Fix import 2020-11-10 20:51:43 -05:00
Deluan 11012302fd Add tests for formatters 2020-11-10 20:45:04 -05:00
Deluan 9d2426a601 Use a better notation for exporting JS components and functions 2020-11-10 19:27:28 -05:00
Deluan 8a44f61189 Fix setting up Event Stream message handler on first login 2020-11-10 16:53:09 -05:00
Deluan 7afad2c96e Fix download single track from Playlist 2020-11-10 16:24:34 -05:00
Deluan 08e63c867b Add config option to globally enable/disable downloads 2020-11-10 16:14:43 -05:00
Deluan bf69c5589f Fix log message 2020-11-10 14:46:12 -05:00
Deluan 714100e24b Remove old TODO 2020-11-09 19:50:14 -05:00
Deluan 8f2fe6f9fa Add buffer to broker SendMessage 2020-11-09 19:24:27 -05:00
Deluan 08dbf44529 Better broker logging 2020-11-09 19:24:04 -05:00
Deluan 84080a0e44 Hide activity menu from non-admin users 2020-11-09 16:12:50 -05:00
Deluan 1b624b2505 Do not create the EventStream if unauthenticated 2020-11-09 16:12:50 -05:00
Deluan a2e76d6898 Add flag to enable activity menu 2020-11-09 16:12:50 -05:00
Deluan 56803d0151 Auto-reconnect to event stream after 20secs timeout 2020-11-09 16:12:50 -05:00
Deluan 2b1a5f579a Adding a communication channel between server and clients using SSE 2020-11-09 16:12:50 -05:00
Deluan 3fc81638c7 Moved all reducers and actions to their own folders 2020-11-08 13:19:38 -05:00
Deluan 24b040adf9 Add more keyboard shortcuts
- : volume down
= : volume up
m : toggle sidebar menu

Refers to #585
2020-11-07 23:11:57 -05:00
Deluan 8d608ac5b2 Faster display of cover in album detail view 2020-11-07 22:45:04 -05:00
Deluan 02160465a5 Remove unused file 2020-11-07 12:46:52 -05:00
Deluan b5abd80927 Update react-jinke-music-player to 4.9.1. Fix #568 2020-11-07 12:20:42 -05:00
Deluan 6542842938 Make sure we don't get durations with decimals 2020-11-05 18:27:46 -05:00
Deluan 8d7931b3bc Fix "seekable" log message (was always "false") 2020-11-05 18:11:12 -05:00
Deluan 9224a67a7b Add <- and -> hotkeys, to jump to previous or next song
Refers to #585
2020-11-05 17:38:53 -05:00
Deluan 873cea4046 Fix "Something went wrong" error when deleting a playlist 2020-11-05 14:06:21 -05:00
JorisL 0b977df8dd
Fixed duration formatter to properly count lengths longer than 24 hours (#612)
* Fixed durationfield formatter to properly count lengths longer than
24 hours.

* formatted DurationField.js with npm prettier
2020-11-05 14:02:09 -05:00
Deluan fb1461fd0b Fix reading dirs from a MergeFS 2020-11-05 13:36:10 -05:00
Deluan 9cbeddae8f Avoid cross-site scripting
See: https://lgtm.com/rules/1510377426397/
2020-11-05 12:32:39 -05:00
Deluan c9b119f0a4 Make scrobble submits compatible with Last.FM specification
See https://github.com/deluan/navidrome/issues/18#issuecomment-656977060
2020-11-04 23:51:48 -05:00
Deluan a6bd9f627e Make new cache layout the default 2020-11-04 23:25:38 -05:00
Deluan 861c742b3e Move notifications to the top
This avoids notifications getting covered by the player
2020-11-04 19:29:55 -05:00
Deluan 36596d4fdb Don't send the transcoded file if it is a HEAD request 2020-11-03 16:06:02 -05:00
Deluan 94f28f6216 Generate a better salt for Subsonic token authentication 2020-11-03 15:13:40 -05:00
Deluan 2f56f1b178 Use new fscache's SetKeyMapper
See a0daa9e527
2020-11-03 12:52:44 -05:00
Deluan f4a88b8319 Update screenshots 2020-11-03 09:23:04 -05:00
Deluan f50aeb0b21 Bump golangci-lint version in pipeline 2020-11-02 20:56:52 -05:00
Deluan fd1604b1d2 Add user's name to UserMenu 2020-11-02 17:13:12 -05:00
Deluan 7fbdcf8ddc Upgrade react-admin to 3.9.6 2020-11-02 17:12:52 -05:00
Deluan 7f7b0c1f0d Move Settings options to UserMenu 2020-11-02 16:57:21 -05:00
Deluan 68e0fe574f Bump github.com/golangci/golangci-lint from 1.32.0 to 1.32.1 2020-11-02 11:45:58 -05:00
Deluan Quintão 8ddf4d62af Update README.md 2020-11-02 11:43:05 -05:00
Deluan 9bcd606fe8 Fix Artist full_text refresh 2020-11-02 10:27:01 -05:00
Deluan 7819e834c8 Fix Artist filtering 2020-11-02 09:58:51 -05:00
Deluan 779d4a1c85 Revert "Process empty folders as changed folders"
This reverts commit e07152b695.
2020-11-02 07:57:47 -05:00
Deluan e07152b695 Process empty folders as changed folders
This is a workaround for rclone not changing the directory modtime when you delete all folders from it (happens when you are moveing things around with beets)
2020-11-01 23:25:34 -05:00
Deluan ee5a0698c0 Simplify scanner utilization 2020-11-01 18:37:17 -05:00
Deluan 71b77cba2b Bump Subsonic API to 1.16.1 2020-11-01 17:04:53 -05:00
Deluan 8e584ee020 Update count on `getScanStatus` 2020-11-01 16:54:33 -05:00
Deluan 3ea5b85b36 go mod tidy 2020-11-01 14:40:48 -05:00
Deluan cfad35544b Add artistImageUrl available in getArtists endpoint
Also cache artist info in the DB for 1 hour
2020-11-01 14:37:29 -05:00
dependabot-preview[bot] 7583ddac65 Bump github.com/golangci/golangci-lint from 1.31.0 to 1.32.0
Bumps [github.com/golangci/golangci-lint](https://github.com/golangci/golangci-lint) from 1.31.0 to 1.32.0.
- [Release notes](https://github.com/golangci/golangci-lint/releases)
- [Changelog](https://github.com/golangci/golangci-lint/blob/master/CHANGELOG.md)
- [Commits](https://github.com/golangci/golangci-lint/compare/v1.31.0...v1.32.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-11-01 14:37:08 -05:00
Deluan 6b89679e08 Remove hardcoded color 2020-10-31 19:40:25 -04:00
Deluan 3535fba9dd Fix BulkActions contrast once and for all(?) 2020-10-31 18:46:14 -04:00
Deluan 488db26675 Improve bulk actions color contrast 2020-10-31 11:05:33 -04:00
Deluan 1f842b08e2 Remove duplicated code for SongBulkActions 2020-10-31 10:46:38 -04:00
Deluan aabef62b11 Add "PlayNow" button to bulk actions 2020-10-31 10:14:12 -04:00
Deluan 6c0778a867 Add log to pool 2020-10-31 01:13:36 -04:00
Deluan 58d6b0a84f Cache Warmer now waits for Cache to be available 2020-10-31 00:40:21 -04:00
Deluan 145a5708ca Stop tag_scanner when waltDirTree is interrupted by errors
Otherwise, tag_scanner remove tracks from folders that would come after the error
2020-10-31 00:06:28 -04:00
Deluan 6ccdc2e068 Fine tune colors, remove PlayButton from AlbumDetail 2020-10-30 19:39:47 -04:00
Chris Newton 6da2f1ba92 feat: ran prettier over changes 2020-10-30 19:39:47 -04:00
Chris Newton 28bcd3f99e feat: fixed linting error 2020-10-30 19:39:47 -04:00
Chris Newton 1076dda011 feat: changed hvoer state for album list, added play icon to album details 2020-10-30 19:39:47 -04:00
Chris Newton e30704fe0f feat: altered grid layout to be more like itunes 2020-10-30 19:39:47 -04:00
Deluan 84384da8d1 Better naming for function 2020-10-30 13:14:53 -04:00
Victorhck 62fe1cdc43 improve spanish translation 2020-10-30 10:05:16 -04:00
Deluan 4d6c9482ff Recover from panic when reading invalid id2 tags
Workaround for #596
2020-10-30 09:53:38 -04:00
Deluan cdd44a2830 Abort scan when media folder is empty
This is to prevent all data being deleted in the case where a mount is not available
2020-10-30 09:39:36 -04:00
Deluan ba8d2f5da8 Log when a cache has finished loading 2020-10-30 00:33:39 -04:00
Deluan 00ec6cf042 Process changed folders as they are discovered 2020-10-29 23:47:43 -04:00
Deluan 2f394623c8 WIP 2020-10-29 23:19:26 -04:00
Deluan f1a24b971a Use timestamp of artwork file instead of album's UpdatedAt in the cache key 2020-10-29 23:19:26 -04:00
Deluan d913108de2 Add option to disable track cover art. Should help with cloud mounting (rclone) 2020-10-29 10:57:33 -04:00
Deluan 32bac11b61 Make CreatePlaylist response compatible with API >1.14.0 2020-10-28 12:46:06 -04:00
Deluan 78630d427d Limit startScan to admins only 2020-10-27 20:22:05 -04:00
Deluan 1e57852eff Make pool's queue buffered. Workaround while we don't put the queue in disk 2020-10-27 20:12:27 -04:00
Deluan 464e251d19 Only start the cache warming after all folders were scanned 2020-10-27 20:11:25 -04:00
Deluan d9f7a154cf Implements library scanning endpoints. Also:
- Bumped Subsonic API version to 1.15:
- Better User/Users Subsonic endpoint implementations, not final though
2020-10-27 18:20:50 -04:00
Deluan 9b756faef5 Make caches singletons 2020-10-27 18:20:50 -04:00
Deluan 515528ee6d Disable flaky test 2020-10-27 16:07:53 -04:00
Deluan 4bd6012f11 Fix job dependencies in pipeline 2020-10-27 15:58:27 -04:00
Deluan 216491815c Increased pool test timeout (hate time based tests...) 2020-10-27 15:58:13 -04:00
Deluan 4777cf0aba Simplify error responses 2020-10-27 15:33:28 -04:00
Deluan 0f418a93cd Completely removed engine package, fewer abstraction layers \o/ 2020-10-27 15:27:37 -04:00
Deluan d0bf37a8a9 Move mock datastore to tests package 2020-10-27 15:23:49 -04:00
Deluan 313a088f86 Make mocks strongly typed 2020-10-27 15:23:49 -04:00
Deluan 6152fadd92 Removed list_generator completely 2020-10-27 15:23:48 -04:00
Deluan 3037ea01e2 Removed more layers of indirection from the engine package 2020-10-27 15:23:48 -04:00
Deluan acba4b16ee Add test for pool 2020-10-27 15:23:48 -04:00
dependabot-preview[bot] 8dfa929666 Bump github.com/kr/pretty from 0.2.0 to 0.2.1
Bumps [github.com/kr/pretty](https://github.com/kr/pretty) from 0.2.0 to 0.2.1.
- [Release notes](https://github.com/kr/pretty/releases)
- [Commits](https://github.com/kr/pretty/compare/v0.2.0...v0.2.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-27 00:15:32 -04:00
Deluan c1fb32cedb Replace unicode quotes and dash with simple ascii chars
External services do not use these unicode chars. Ex:
- “Weird Al” Yankovic
- Bachman–Turner Overdrive
- The Go‐Go’s
are not found in Last.FM and Spotify
2020-10-27 00:13:39 -04:00
Deluan b6a6422fac Upgrade GoLang to 1.15.3 2020-10-26 13:04:14 -04:00
Deluan 21ed7348c6 Remove invalid migration 2020-10-26 10:57:21 -04:00
Deluan 95cc211659 Revert "Make caches singletons" 2020-10-26 10:11:47 -04:00
Deluan bf5318d776 Add flag to enable new cache layout 2020-10-26 09:54:36 -04:00
Deluan 81d7556cdf Make caches singletons 2020-10-25 23:22:52 -04:00
Deluan 1e56f4da76 Add simple cache warmer, disabled by default 2020-10-25 23:22:52 -04:00
Deluan f3bb51f01b Add formatting to config dump 2020-10-25 23:22:52 -04:00
Deluan 197d4024f7 Add dedicated Item interface for cache items 2020-10-25 23:22:52 -04:00
Deluan 7eaa42797a Uses cached original image when requesting a resized image 2020-10-25 23:22:52 -04:00
Deluan d39bd0219a Fix cache key-mapping 2020-10-25 23:22:52 -04:00
Deluan 9f533b2108 New Cache FileSystem implementation 2020-10-25 23:22:52 -04:00
Deluan 1cfa7b2272 Change MediaFolder.ID type to int32 2020-10-25 23:22:52 -04:00
Deluan d24709b521 Add getScanStatus Subsonic response 2020-10-25 23:22:52 -04:00
Deluan af7eaa2b7a Add scanner status 2020-10-25 23:22:52 -04:00
Deluan c0ec0b28b9 Add better process lifecycle management 2020-10-24 22:43:59 -04:00
Deluan 6d08a9446d Fix test suite name 2020-10-23 21:43:33 -04:00
Deluan 04fd72e1fa Change avatar placeholder to new logo 2020-10-23 21:37:53 -04:00
Deluan fc19199fbe Add Russian translation. Thanks @lun4r 2020-10-23 09:54:25 -04:00
Deluan 4514a54744 Fix ignoring hidden folders when scanning 2020-10-22 13:59:54 -04:00
Deluan f9e0de31b8 Fix missing last.fm and spotify config keys. Closes #589 2020-10-22 08:31:47 -04:00
Deluan 1cd2f015c2 Get Similar Artists in parallel
Also don't fail `GetArtistInfo` when Last.FM is not configured
2020-10-21 21:44:03 -04:00
Deluan ed84c5a0a3 Bump @testing-library/react from 11.0.4 to 11.1.0 in /ui 2020-10-21 18:24:20 -04:00
Deluan b88f9013dc Fix `getAlbumList.byYear`. See https://github.com/daneren2005/Subsonic/issues/967 2020-10-21 17:32:10 -04:00
dependabot-preview[bot] 62ed30afed Bump @testing-library/user-event from 12.1.7 to 12.1.8 in /ui
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 12.1.7 to 12.1.8.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v12.1.7...v12.1.8)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-21 17:10:39 -04:00
Deluan 6dc21d0595 Check for Last.FM and Spotify configuration at startup 2020-10-21 17:10:06 -04:00
Deluan 79710fbee0 go mod tidy 2020-10-21 16:58:55 -04:00
dependabot-preview[bot] c89b89cd92 Bump react from 16.13.1 to 16.14.0 in /ui
Bumps [react](https://github.com/facebook/react/tree/HEAD/packages/react) from 16.13.1 to 16.14.0.
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/facebook/react/blob/master/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v16.14.0/packages/react)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-21 16:58:07 -04:00
dependabot-preview[bot] dcea5eb449 Bump github.com/spf13/cobra from 1.1.0 to 1.1.1
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Changelog](https://github.com/spf13/cobra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spf13/cobra/compare/v1.1.0...v1.1.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-21 16:51:55 -04:00
Deluan Quintão b5c68c971d Update pl.json (POEditor.com) 2020-10-21 16:50:31 -04:00
Deluan Quintão fe38f99739 Update nl.json (POEditor.com) 2020-10-21 16:50:31 -04:00
Deluan Quintão ff3a89b15a Update cs.json (POEditor.com) 2020-10-21 16:50:31 -04:00
Deluan 078a7c24e6 Add userRating to Subsonic API's Artist 2020-10-21 15:51:12 -04:00
Deluan 69e1059705 Prefer starred and high rated versions for Top Songs 2020-10-21 15:11:26 -04:00
Deluan 075c28d2e5 Fix performance and precision of TopSongs 2020-10-21 14:07:01 -04:00
Deluan a45b5a037f Match Top Songs by mbid, add indexes to media_file 2020-10-21 10:13:03 -04:00
Deluan 3cf8b8e97d Fix migration that adds MBIDs 2020-10-21 09:02:51 -04:00
Deluan b93a3db267 Fix sort order for TopSongs 2020-10-21 00:10:46 -04:00
Deluan 53c1e9ec35 Include tracks in TopSongs where the requested artist is the album artist 2020-10-20 23:52:45 -04:00
Deluan 12cedee867 Prefer older versions on GetTopSongs 2020-10-20 23:46:45 -04:00
Deluan 2f11c2dc8f Bump Subsonic API compatibility to 1.13 2020-10-20 22:54:37 -04:00
Deluan 049ac70b2b Add "real" TopSongs 2020-10-20 22:53:52 -04:00
Deluan b5e20c1934 Ignore invalid MBIDs (ex: discogs IDs) 2020-10-20 17:45:32 -04:00
Deluan 173dd52fe1 Use MBID with most occurrences 2020-10-20 17:16:24 -04:00
Deluan 6663c079e0 Add MBIDs to media_file, album and artist 2020-10-20 16:27:22 -04:00
Deluan 64ccb4d188 Add SimilarSongs functionality 2020-10-20 16:07:31 -04:00
Deluan a289a1945f Remove redundant interfaces 2020-10-20 16:07:31 -04:00
Deluan a257891b46 Get better artist images results 2020-10-20 16:07:31 -04:00
Deluan 40fd5bab34 Search for artists case-insensitive 2020-10-20 16:07:31 -04:00
Deluan e9e09a7480 Add dedicated SimilarArtists call 2020-10-20 16:07:31 -04:00
Deluan 29d8950e5b Better ArtistInfo field names 2020-10-20 16:07:31 -04:00
Deluan 00b6f895bb Fix lint errors 2020-10-20 16:07:31 -04:00
Deluan 07d96f8308 Add missing fields to ArtistInfo 2020-10-20 16:07:31 -04:00
Deluan 07535e1518 Add ExternalInformation core service (not a great name, I know) 2020-10-20 16:07:31 -04:00
Deluan 19ead8f7e8 Add initial spotify client implementation 2020-10-20 16:07:31 -04:00
Deluan eb74dad7cd Add initial last.fm client implementation 2020-10-20 16:07:31 -04:00
Deluan 61d0bd4729 Add support for WavPack files 2020-10-20 10:47:29 -04:00
Deluan def5db9729 Update dependencies (go mod tidy) 2020-10-16 16:24:10 -04:00
dependabot-preview[bot] 3d11bdcfd1 Bump github.com/spf13/cobra from 1.0.0 to 1.1.0
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Changelog](https://github.com/spf13/cobra/blob/master/CHANGELOG.md)
- [Commits](https://github.com/spf13/cobra/compare/v1.0.0...v1.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-15 09:21:26 -04:00
Deluan 0ff89679ba Use new `renderAudioTitle` (to avoid the [Object object] song title on iOS) 2020-10-13 11:20:30 -04:00
Deluan 0c095f6d5d Upgrade ginkgo/gomega 2020-10-13 09:35:31 -04:00
Deluan 2f8dc794de Add and show Playlists sizes 2020-10-12 22:31:01 -04:00
Deluan 68a9be5e86 Add Artist (discography) size, and show sizes in Download caption 2020-10-12 22:31:01 -04:00
Deluan 1ffc8d619e Log `ffmpeg` detection as Info 2020-10-12 21:59:03 -04:00
Deluan 2de0a40c6f Fix Album size should be int64 2020-10-12 21:04:12 -04:00
Deluan 5417031d79 Update some GH actions 2020-10-12 12:21:01 -04:00
Deluan ae817da223 Upgrade golangci-lint
- Fix a SQL string concatenation
- Install golangci-lint using `tools.go`
2020-10-12 12:21:01 -04:00
Jay R. Wren fd6edf967f
Add size to album details (#561)
* add size to album details

for #534

* addressing review comments:

* create index album(size)
* remove unneeded Size field from refresh struct
* add whitespace to album details
* add size to album list view

* prettier
2020-10-12 11:10:07 -04:00
Deluan c60e56828b Fix `ffmpeg` detection 2020-10-12 10:59:42 -04:00
Deluan edc9344327 Only link from current playing song title to album view if not in iOS.
Ideally the react-player should accept a Link as the audioTitle
2020-10-11 15:04:15 -04:00
Deluan fea5d23fc7 Add `ffmpeg` detection at start-up 2020-10-06 17:24:16 -04:00
Deluan 26d2af17a3 Fix read `DISCNUMBER` as a DiscNumber tag in ffmpeg extractor 2020-10-06 17:06:47 -04:00
Gosz f373f5f83e Updating spanish translation 2020-10-06 11:38:54 -04:00
Deluan 92b7ef40af Disable CSP for now 2020-10-06 11:24:59 -04:00
Deluan 39cb3455db Prepare for release: go mod tidy 2020-10-06 09:55:40 -04:00
Deluan Quintão 4ac4806bf8 Update fr.json (POEditor.com) 2020-10-06 09:33:59 -04:00
Deluan Quintão a282f62395 Update zn.json (POEditor.com) 2020-10-06 09:33:59 -04:00
dependabot-preview[bot] 3aac03d253 Bump @testing-library/user-event from 12.1.6 to 12.1.7 in /ui
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 12.1.6 to 12.1.7.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v12.1.6...v12.1.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-06 09:29:11 -04:00
Deluan cd171c40cb Add `secure` middleware, with sensible values 2020-10-06 08:46:58 -04:00
dependabot-preview[bot] 78c40ab6b4 Bump jwt-decode from 2.2.0 to 3.0.0 in /ui
Bumps [jwt-decode](https://github.com/auth0/jwt-decode) from 2.2.0 to 3.0.0.
- [Release notes](https://github.com/auth0/jwt-decode/releases)
- [Changelog](https://github.com/auth0/jwt-decode/blob/master/CHANGELOG.md)
- [Commits](https://github.com/auth0/jwt-decode/compare/v2.2.0...v3.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-06 08:41:08 -04:00
Deluan 21f7c1906d Fix ByPath queries should not match partial filenames 2020-10-06 08:13:25 -04:00
dependabot-preview[bot] 23fe8cdee6 Bump uuid from 8.3.0 to 8.3.1 in /ui
Bumps [uuid](https://github.com/uuidjs/uuid) from 8.3.0 to 8.3.1.
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/master/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v8.3.0...v8.3.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-10-06 08:12:57 -04:00
Deluan af55b93ac8 Make taglib the default metadata extractor 2020-10-05 21:01:03 -04:00
Deluan 665b1f6898 Fix auto-imported playlists losing the "Public" status. Fix #479 2020-10-05 12:40:44 -04:00
Deluan 35f748e0fb Use logo on signup page 2020-10-04 18:29:46 -04:00
Deluan fc7a027d59 Update mobile login screenshot 2020-10-04 18:23:48 -04:00
Deluan 38c1999fcd Use logo on login page. Closes #247 2020-10-04 11:47:25 -04:00
Deluan 180f1354fc Make package name compatible with version installed by `make setup` 2020-10-03 20:13:47 -04:00
Deluan abd51b2156 Use Subsonic API to star/unstar
This removes the need to update the annotations on Put(model), removing complexity and making it less buggy
2020-10-03 20:08:51 -04:00
Deluan 47976e13b1 Create index to make sort by starred faster 2020-10-03 20:08:51 -04:00
Deluan bbd4503ac8 Move tools installation to tools.go 2020-10-03 11:14:19 -04:00
Aries b40df6380e
Update Japanese translation (#544) 2020-10-03 09:56:10 -04:00
Deluan 2d036b5966 Small refactoring, simplify function 2020-10-02 22:36:46 -04:00
Deluan f859772723 Remove dangling tracks after changing MusicFolder. Fix #445 2020-10-02 16:18:45 -04:00
Deluan 1be79fa945 Reload translations when reloading the app 2020-10-02 14:28:55 -04:00
Deluan 1825b29737 Better PT translation 2020-10-02 14:17:34 -04:00
Deluan Quintão 13f08d3eae
Update translations (#543)
* Update zn.json (POEditor.com)

* Update cs.json (POEditor.com)

* Update nl.json (POEditor.com)

* Update fr.json (POEditor.com)

* Update de.json (POEditor.com)

* Update it.json (POEditor.com)

* Update ja.json (POEditor.com)

* Update pl.json (POEditor.com)

* Update pt.json (POEditor.com)

* Update tr.json (POEditor.com)

* Update es.json (POEditor.com)

* Fix translations
2020-10-02 12:18:00 -04:00
Deluan 52d8aaa865 Add about dialog, with version and helpful links 2020-10-02 12:03:19 -04:00
Deluan 8dfc259857 Serve `robots.txt` from root (http://server/robots.txt) 2020-10-02 10:15:19 -04:00
Deluan deef8e162d Hide the player when queue is empty, instead of removing it from the DOM 2020-10-01 13:40:44 -04:00
Deluan b18e3289fb Add StarButton to player 2020-10-01 13:40:44 -04:00
certuna 4d60f72b7e Update index.js
registers the default serverworker, required to be installable on desktop as a PWA - as far as I can test, it doesn't seem to break anything
2020-10-01 12:12:18 -04:00
Deluan e0fa85be28 Avoid Bing bot to automatically add Navidrome to the MS Store
See: https://docs.microsoft.com/en-us/microsoft-edge/progressive-web-apps-edgehtml/microsoft-store#criteria-for-automatic-submission
2020-10-01 12:09:56 -04:00
Deluan 5b167031d2 Enable PWA's when setting BaseURL 2020-10-01 12:04:38 -04:00
Deluan cf8756b14b Unexport private function 2020-10-01 09:56:09 -04:00
certuna 03867bd8b2 Update manifest.json
added start_url (required)
fixed icon paths
2020-10-01 08:56:27 -04:00
Deluan 943f35f7a5 Update to GoLang 1.15 2020-10-01 08:41:11 -04:00
Deluan ca283f45ea Update serviceWorker to the latest from create-react-app 2020-09-29 17:10:06 -04:00
Deluan bf93b5614c Bump react-admin to 3.8.5 2020-09-29 16:33:36 -04:00
Deluan 377d8f6b87 Fix continuous loop when showing an album or playlist 2020-09-29 16:29:34 -04:00
dependabot-preview[bot] 1eb62ee671 Bump github.com/sirupsen/logrus from 1.6.0 to 1.7.0
Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.6.0 to 1.7.0.
- [Release notes](https://github.com/sirupsen/logrus/releases)
- [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md)
- [Commits](https://github.com/sirupsen/logrus/compare/v1.6.0...v1.7.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-29 16:28:58 -04:00
Deluan 39c94d3cd9 Play/Pause current song with <Space> key 2020-09-28 19:05:19 -04:00
Deluan 3fa4ef0166 Fix low severity vulnerabilities (npm audit fix) 2020-09-28 18:36:25 -04:00
Deluan 9116529b6d Change favicon to new logo 2020-09-28 16:48:35 -04:00
Deluan bd8b573743 Update react-jinke-music-player 2020-09-28 12:22:05 -04:00
Deluan a65318a00a Fix 'Play Next' icon in AlbumSongBulkActions 2020-09-27 12:50:58 -04:00
Deluan a817701ee8 Use new ci-goreleaser. Fix pipeline 2020-09-26 20:30:48 -04:00
Deluan 4a4a8aff34 Build ARM with armel instead of armhf. Fixes #525 2020-09-26 13:08:45 -04:00
Fernando Rios 80b8b69cee Fix compilation of C++ code on certain linux systems 2020-09-26 13:08:28 -04:00
Deluan ab0e091736 Fix link to Artist's albums in mobile view 2020-09-25 16:48:31 -04:00
Deluan e6d1e67297 Add more padding tertiary info and the star icon, in Mobile simple list views. Fixes #466 2020-09-24 21:29:26 -04:00
Deluan a99924ea20 Converted pre-push hook into a make target, avoid calling tests twice when releasing 2020-09-24 17:24:31 -04:00
Deluan 27adb84177 Add "Close" icon to player 2020-09-24 13:34:17 -04:00
Deluan 7a3bd935c2 Change default Opus transcoding format name to `opus`. Closes #521 2020-09-24 12:27:13 -04:00
Deluan cff5c1ee53 Start player in pause mode if windows is reloaded/refreshed. Fixes #457 2020-09-24 11:12:47 -04:00
Deluan fd32a28788 Fix JS linting error 2020-09-24 09:58:01 -04:00
Deluan Quintão 4f25e9ebf4 Update pl.json (POEditor.com) 2020-09-24 09:51:11 -04:00
Deluan Quintão 514117a477 Update ja.json (POEditor.com) 2020-09-24 09:51:11 -04:00
Deluan Quintão 07e8f41849 Update it.json (POEditor.com) 2020-09-24 09:51:11 -04:00
Deluan Quintão 133626dcd0 Update fr.json (POEditor.com) 2020-09-24 09:51:11 -04:00
Deluan Quintão 56a6fb91ab Update cs.json (POEditor.com) 2020-09-24 09:51:11 -04:00
Deluan Quintão 6e518d90d5 Update zn.json (POEditor.com) 2020-09-24 09:51:11 -04:00
Deluan Quintão 96b94106e6 Update en.json (POEditor.com) 2020-09-24 09:49:42 -04:00
Deluan a1c670b40d Start player in pause mode if windows is reloaded/refreshed. Fixes #457 2020-09-24 09:40:04 -04:00
certuna 2230a9052f Update manifest.json
Updated name/description/colours in the manifest
2020-09-24 08:51:35 -04:00
Gosz 9b1be35c14 Updatind translation 2020-09-24 08:50:05 -04:00
Deluan afe5a5b32a Fix extracting tags with spaces in the tagname ("Ex: Album Artist") 2020-09-22 14:42:36 -04:00
dependabot-preview[bot] 9edd7e9025 Bump @testing-library/user-event from 12.1.5 to 12.1.6 in /ui
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 12.1.5 to 12.1.6.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v12.1.5...v12.1.6)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-22 09:36:13 -04:00
dependabot-preview[bot] 2be9a7dbec Bump prettier from 2.1.1 to 2.1.2 in /ui
Bumps [prettier](https://github.com/prettier/prettier) from 2.1.1 to 2.1.2.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/master/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.1.1...2.1.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-22 09:32:25 -04:00
Deluan 7305e3aa17 Add "Play Next" action (finally) 2020-09-21 20:10:52 -04:00
Deluan aa133e6b00 Upgrade react-music-player to 4.18.2 2020-09-21 15:24:15 -04:00
JG 1f72399f44
Add Spanish translation
Spanish translation
2020-09-18 09:46:12 -04:00
KITblue 3aef62f201
Update Chinese translation
* Update zn.json

Already formatted

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Update zn.json

* Add files via upload
2020-09-17 13:43:51 -04:00
dependabot-preview[bot] e5535f6aff Bump @testing-library/user-event from 12.1.3 to 12.1.5 in /ui
Bumps [@testing-library/user-event](https://github.com/testing-library/user-event) from 12.1.3 to 12.1.5.
- [Release notes](https://github.com/testing-library/user-event/releases)
- [Changelog](https://github.com/testing-library/user-event/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/user-event/compare/v12.1.3...v12.1.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-16 20:20:57 -04:00
dependabot-preview[bot] 76fc5b1425 Bump @testing-library/react from 11.0.2 to 11.0.4 in /ui
Bumps [@testing-library/react](https://github.com/testing-library/react-testing-library) from 11.0.2 to 11.0.4.
- [Release notes](https://github.com/testing-library/react-testing-library/releases)
- [Changelog](https://github.com/testing-library/react-testing-library/blob/master/CHANGELOG.md)
- [Commits](https://github.com/testing-library/react-testing-library/compare/v11.0.2...v11.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-16 20:09:00 -04:00
dependabot-preview[bot] a38f205c0b Bump react-measure from 2.5.0 to 2.5.2 in /ui
Bumps [react-measure](https://github.com/souporserious/react-measure) from 2.5.0 to 2.5.2.
- [Release notes](https://github.com/souporserious/react-measure/releases)
- [Changelog](https://github.com/souporserious/react-measure/blob/main/CHANGELOG.md)
- [Commits](https://github.com/souporserious/react-measure/compare/v2.5.0...v2.5.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-16 15:54:53 -04:00
Deluan 944107cb3d Update Italian translation (thanks @matteoipri) 2020-09-14 20:00:30 -04:00
Deluan 94fd0a10b5 Fix titles in Playlist create/edit views 2020-09-14 19:56:05 -04:00
Deluan 669f293f1f Fix ci-goreleaser 2020-09-10 17:49:25 -04:00
Deluan 532833ac7c Fix ci-goreleaser 2020-09-10 17:02:52 -04:00
Deluan 59f1d7e88a Use new ci-goreleaser, to fix generating Linux binaries for old kernels 2020-09-10 16:24:39 -04:00
Deluan caeff2862a Remove dependency on C++17 2020-09-10 15:16:47 -04:00
Deluan 841c1129ff Break-up album/artist refresh in chunks 2020-09-09 08:57:59 -04:00
Deluan ba30f7f8be Fix label for items per page (not always rows) 2020-09-08 14:55:41 -04:00
Deluan Quintão 6026638c03 Update fr.json (POEditor.com) 2020-09-08 14:54:02 -04:00
Deluan cbab2e4eec go mod tidy 2020-09-08 13:33:07 -04:00
Deluan a3ecc41e47 Change taglib extractor log level to trace 2020-09-08 13:33:07 -04:00
Deluan 4d18212f5d Extract all id3 frames from file 2020-09-08 13:33:07 -04:00
Deluan 5dea258058 Extract basic tags, as a fallback 2020-09-08 13:33:07 -04:00
Deluan 0802ab73d7 Trim tag value, not tag key 2020-09-08 13:33:07 -04:00
Deluan 865b9cd545 Trim spaces from tags 2020-09-08 13:33:07 -04:00
Deluan e70ec53983 Rewrite taglib integration, now with TCMP 2020-09-08 13:33:07 -04:00
Deluan 2d0031f709 Parse more date formats 2020-09-08 13:33:07 -04:00
Deluan 78ecda5239 Get the first occurrence of multi-valued tags 2020-09-08 13:33:07 -04:00
Deluan a1879ff871 Reorganize tests 2020-09-08 13:33:07 -04:00
Deluan 34eda3c8fc Add config option to select tag extractor (taglib, ffmpeg) 2020-09-08 13:33:07 -04:00
Deluan 506899b083 Add more fallback options for main tags 2020-09-08 13:33:07 -04:00
Deluan 3a4e2523dd Fix possible concurrency issue 2020-09-08 13:33:07 -04:00
Deluan 674b56a53d Install taglib in `lint` and `go` jobs 2020-09-08 13:33:07 -04:00
Deluan 58a0c44600 Embed audiotags lib, to make it static compilable 2020-09-08 13:33:07 -04:00
Deluan df4328819d Initial implementation of `taglib` MetadataExtractor 2020-09-08 13:33:07 -04:00
Deluan b6aa6eb7b2 Disable some jobs for now, as taglib is not available 2020-09-08 13:33:07 -04:00
Deluan 1187ee7cc1 Moved Metadata Extraction to its own package 2020-09-08 13:33:07 -04:00
Deluan 0beec552b1 Introduce Metadata and MetadataExtractor interfaces 2020-09-08 13:33:07 -04:00
Deluan 6a6d4c3f87 Use new ci-releaser image, that contains static `taglib` library 2020-09-08 13:33:07 -04:00
Deluan 1216c9bdb8 Bump react-measure version to 2.5.0 2020-09-08 13:12:00 -04:00
Deluan 2a888395fa Bump prettier version to 2.1.1 2020-09-08 13:06:34 -04:00
Deluan 56772f5c62 Bump @testing libraries 2020-09-08 13:05:04 -04:00
Deluan 07b5469b4c Bump uuid to v.1.1.2 2020-09-08 13:00:04 -04:00
Deluan 58324b411f Bump ginkgo to v1.14.1 2020-09-08 12:59:06 -04:00
dependabot-preview[bot] c0e5b445cf Bump github.com/onsi/gomega from 1.10.1 to 1.10.2
Bumps [github.com/onsi/gomega](https://github.com/onsi/gomega) from 1.10.1 to 1.10.2.
- [Release notes](https://github.com/onsi/gomega/releases)
- [Changelog](https://github.com/onsi/gomega/blob/master/CHANGELOG.md)
- [Commits](https://github.com/onsi/gomega/compare/v1.10.1...v1.10.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-09-08 12:55:59 -04:00
Deluan 6820e120cb Test for accented article sanitization 2020-09-08 09:40:41 -04:00
Deluan 28aefb4858 Fix sanitizing accented articles 2020-09-08 09:36:08 -04:00
Deluan e50a720818 Sort by album name, then artist name 2020-09-07 16:21:29 -04:00
Deluan 900337081b Upgrade React-Player to 4.18.0 2020-09-06 12:11:02 -04:00
Deluan 34af6fc671 Clean up code a bit 2020-09-06 11:54:30 -04:00
Deluan a25044bdf6 Reorder action buttons 2020-09-06 11:44:15 -04:00
Anders Moberg 30e98843ed Adding playlist button to Album Actions 2020-09-06 11:35:33 -04:00
Anders Moberg 8fe335ed97 Adding playlist button to Playlist actions 2020-09-06 11:35:33 -04:00
Deluan 8549451ee7 Fix potential undefined property
Not sure the reason, but I got this error:

```
Cannot read property 'id' of undefined
    at tn (SongTitleField.js:35)
    at Ka (react-dom.production.min.js:153)
    at vl (react-dom.production.min.js:261)
    at sc (react-dom.production.min.js:246)
    at lc (react-dom.production.min.js:246)
```
2020-09-02 12:41:21 -04:00
Deluan 596a4897a3 Do not force username to always be lowercase in the DB 2020-09-01 18:00:19 -04:00
Deluan 95eea0e9f8 Update ja.json (POEditor.com) (+2 squashed commits)
Squashed commits:
[e9c4218] Update ja.json (POEditor.com)
[10d3992] Add initial Japanese translation
2020-09-01 12:43:59 -04:00
Deluan Quintão 61c286a77e Update pl.json (POEditor.com) (+1 squashed commit)
Squashed commits:
[5c45ca0] Create pl.json
2020-09-01 12:43:59 -04:00
Deluan Quintão 15d11a9519 Update fr.json (POEditor.com) 2020-09-01 12:43:59 -04:00
Deluan Quintão 35625020e2 Update cs.json (POEditor.com) 2020-09-01 12:43:59 -04:00
Deluan 76e522710a New option: SearchFullString, to match query strings anywhere in searchable fields, not only in word boundaries
Based on feedback from @orlea, in https://github.com/deluan/navidrome/issues/255#issuecomment-683427754
2020-08-30 13:08:10 -04:00
Deluan Quintão aae9d89e8c
Update README.md 2020-08-26 13:54:23 -04:00
Deluan 0eae6d2a61 Hide "star" from disc subtitle rows 2020-08-25 22:48:05 -04:00
Deluan f6982fd8ae Remove unused prop 2020-08-25 19:07:51 -04:00
Deluan b364170d4f Remove duplicated star code from SongContextMenu 2020-08-24 19:51:41 -04:00
Deluan 0aceda9b89 Add star button to album detail view 2020-08-22 23:41:25 -04:00
Deluan 9df405a8ce Add export as m3u button to playlist 2020-08-22 13:23:50 -04:00
Deluan 366054e8cc Handle exporting playlists as m3u files 2020-08-22 12:15:26 -04:00
Deluan 8fa5544af7 Add option to download playlist 2020-08-21 13:28:20 -04:00
Deluan 073e40dc87 Add album cover lightbox 2020-08-21 12:41:23 -04:00
Deluan a45c08f217 Ignore "hidden" files when importing a folder 2020-08-21 11:50:18 -04:00
Deluan 6c8535c54a Add support for reading webp artwork 2020-08-21 11:33:23 -04:00
Deluan e2e79d6471 Fix `getTopSongs` endpoint mapping 2020-08-20 11:27:38 -04:00
Deluan b5567090ed Remove `-e` option from grep, make the command more portable 2020-08-19 15:40:31 -04:00
Deluan b836871161 Handle CR, LF and CRLF line endings when importing Playlists 2020-08-19 12:22:41 -04:00
Deluan 45e708f591 Loosen up constraints for `email`. Fixes #362 2020-08-19 12:22:41 -04:00
Deluan 608129963f Fix `migration` target 2020-08-19 11:18:30 -04:00
Deluan f3d8222ddb Fix color of star in Album grid when using Light theme 2020-08-19 11:12:12 -04:00
Deluan c83808a445 Revert "Use `outlined` Material-UI variant for login inputs as well"
This reverts commit c23e5c291c.
2020-08-18 09:58:09 -04:00
Deluan c23e5c291c Use `outlined` Material-UI variant for login inputs as well 2020-08-17 16:10:49 -04:00
Deluan bd1c3d9229 Use `outlined` Material-UI variant for all inputs 2020-08-17 11:19:39 -04:00
ericgaspar 48c0e1ca4b correct french translations 2020-08-17 09:22:20 -04:00
Deluan 16397e08fc Close cache reader. Should fix #446 2020-08-17 09:14:08 -04:00
Deluan 15a06fcd27 Removed support for Jamstash in dev mode. Not needed anymore :) 2020-08-15 23:11:31 -04:00
1017 changed files with 100163 additions and 23647 deletions

20
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.148.1/containers/go/.devcontainer/base.Dockerfile
# [Choice] Go version: 1, 1.15, 1.14
ARG VARIANT="1"
FROM mcr.microsoft.com/vscode/devcontainers/go:${VARIANT}
# [Option] Install Node.js
ARG INSTALL_NODE="true"
ARG NODE_VERSION="lts/*"
RUN if [ "${INSTALL_NODE}" = "true" ]; then su vscode -c "source /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# [Optional] Uncomment this section to install additional OS packages.
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends libtag1-dev ffmpeg
# [Optional] Uncomment the next line to use go get to install anything else you need
# RUN go get -x <your-dependency-or-tool>
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

View File

@ -0,0 +1,61 @@
{
"name": "Go",
"build": {
"dockerfile": "Dockerfile",
"args": {
// Update the VARIANT arg to pick a version of Go: 1, 1.15, 1.14
"VARIANT": "1.22",
// Options
"INSTALL_NODE": "true",
"NODE_VERSION": "v20"
}
},
"workspaceMount": "",
"runArgs": [
"--cap-add=SYS_PTRACE",
"--security-opt",
"seccomp=unconfined",
"--volume=${localWorkspaceFolder}:/workspaces/${localWorkspaceFolderBasename}:Z"
],
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"go.useGoProxyToCheckForToolUpdates": false,
"go.useLanguageServer": true,
"go.gopath": "/go",
"go.goroot": "/usr/local/go",
"go.toolsGopath": "/go/bin",
"go.formatTool": "goimports",
"go.lintOnSave": "package",
"go.lintTool": "golangci-lint",
"editor.formatOnSave": true,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.defaultFormatter": "vscode.json-language-features"
}
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"golang.Go",
"esbenp.prettier-vscode",
"tamasfe.even-better-toml"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
4533,
4633
],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "make setup-dev",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode",
"remoteEnv": {
"ND_MUSICFOLDER": "./music",
"ND_DATAFOLDER": "./data"
}
}

View File

@ -1,6 +1,5 @@
.DS_Store
ui/node_modules
Jamstash-master
Dockerfile
docker-compose*.yml
data
@ -9,5 +8,3 @@ testDB
navidrome
navidrome.db
navidrome.toml
assets/*gen.go

View File

@ -1,2 +1,4 @@
# Upgrade Prettier to 2.0.4. Reformatted all JS files
b3f70538a9138bc279a451f4f358605097210d41
# Move project to Navidrome GitHub organization
6ee45a9ccc5e7ea4290c89030e67c99c0514bd25

2
.github/FUNDING.yml vendored
View File

@ -3,8 +3,8 @@
github: deluan
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
liberapay: deluan
ko_fi: deluan
liberapay: deluan
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
issuehunt: # Replace with a single IssueHunt username

103
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@ -0,0 +1,103 @@
name: Bug Report
description: Before opening a new issue, please search to see if an issue already exists for the bug you encountered.
title: "[Bug]: "
labels: ["bug", "triage"]
#assignees:
# - deluan
body:
- type: markdown
attributes:
value: |
### Thanks for taking the time to fill out this bug report!
- type: checkboxes
id: requirements
attributes:
label: "I confirm that:"
options:
- label: I have searched the existing [open AND closed issues](https://github.com/navidrome/navidrome/issues?q=is%3Aissue) to see if an issue already exists for the bug I've encountered
required: true
- label: I'm using the latest version (your issue may have been fixed already)
required: false
- type: input
id: version
attributes:
label: Version
description: What version of Navidrome are you running? (please try upgrading first, as your issue may have been fixed already).
validations:
required: true
- type: textarea
attributes:
label: Current Behavior
description: A concise description of what you're experiencing.
validations:
required: true
- type: textarea
attributes:
label: Expected Behavior
description: A concise description of what you expected to happen.
validations:
required: true
- type: textarea
attributes:
label: Steps To Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. In this scenario...
2. With this config...
3. Click (or Execute) '...'
4. See error...
validations:
required: false
- type: textarea
id: env
attributes:
label: Environment
description: |
examples:
- **OS**: Ubuntu 20.04
- **Browser**: Chrome 110.0.5481.177 on Windows 11
- **Client**: DSub 5.5.1
value: |
- OS:
- Browser:
- Client:
render: markdown
- type: dropdown
id: distribution
attributes:
label: How Navidrome is installed?
multiple: false
options:
- Docker
- Binary (from downloads page)
- Package
- Built from sources
validations:
required: true
- type: textarea
id: config
attributes:
label: Configuration
description: Please copy and paste your `navidrome.toml` (and/or `docker-compose.yml`) configuration. This will be automatically formatted into code, so no need for backticks.
render: toml
- type: textarea
id: logs
attributes:
label: Relevant log output
description: Please copy and paste any relevant log output (change your `LogLevel` (`ND_LOGLEVEL`) to debug). This will be automatically formatted into code, so no need for backticks. ([Where I can find the logs?](https://www.navidrome.org/docs/faq/#where-are-the-logs))
render: shell
- type: textarea
attributes:
label: Anything else?
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
Tip: You can attach screenshots by clicking this area to highlight it and then dragging files in.
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/navidrome/navidrome/blob/master/CODE_OF_CONDUCT.md).
options:
- label: I agree to follow Navidrome's Code of Conduct
required: true

8
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,8 @@
blank_issues_enabled: false
contact_links:
- name: Ideas for new features
url: https://github.com/navidrome/navidrome/discussions/categories/ideas
about: This is the place to share and discuss new ideas and potentially new features.
- name: Support requests
url: https://github.com/navidrome/navidrome/discussions/categories/q-a
about: This is the place to ask questions.

12
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,12 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/ui"
schedule:
interval: weekly
open-pull-requests-limit: 10
- package-ecosystem: gomod
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 MiB

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 283 KiB

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 709 KiB

After

Width:  |  Height:  |  Size: 735 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 411 KiB

After

Width:  |  Height:  |  Size: 885 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

View File

@ -1,22 +0,0 @@
#!/bin/bash
GIT_TAG="${GITHUB_REF##refs/tags/}"
GIT_BRANCH="${GITHUB_REF##refs/heads/}"
GIT_SHA=$(git rev-parse --short HEAD)
PR_NUM=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH")
DOCKER_IMAGE_TAG="--tag ${DOCKER_IMAGE}:sha-${GIT_SHA}"
if [[ $PR_NUM != "null" ]]; then
DOCKER_IMAGE_TAG="${DOCKER_IMAGE_TAG} --tag ${DOCKER_IMAGE}:pr-${PR_NUM}"
fi
if [[ $GITHUB_REF != "$GIT_TAG" ]]; then
DOCKER_IMAGE_TAG="${DOCKER_IMAGE_TAG} --tag ${DOCKER_IMAGE}:${GIT_TAG#v} --tag ${DOCKER_IMAGE}:latest"
elif [[ $GITHUB_REF == "refs/heads/master" ]]; then
DOCKER_IMAGE_TAG="${DOCKER_IMAGE_TAG} --tag ${DOCKER_IMAGE}:develop"
elif [[ $GIT_BRANCH = feature/* ]]; then
DOCKER_IMAGE_TAG="${DOCKER_IMAGE_TAG} --tag ${DOCKER_IMAGE}:$(echo $GIT_BRANCH | tr / -)"
fi
echo ${DOCKER_IMAGE_TAG}

View File

@ -0,0 +1,54 @@
name: Add download link to PR
on:
workflow_run:
workflows: ['Pipeline: Test, Lint, Build']
types: [completed]
jobs:
pr_comment:
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v3
with:
# This snippet is public-domain, taken from
# https://github.com/oprypin/nightly.link/blob/master/.github/workflows/pr-comment.yml
script: |
const {owner, repo} = context.repo;
const run_id = ${{github.event.workflow_run.id}};
const pull_head_sha = '${{github.event.workflow_run.head_sha}}';
const pull_user_id = ${{github.event.sender.id}};
const issue_number = await (async () => {
const pulls = await github.pulls.list({owner, repo});
for await (const {data} of github.paginate.iterator(pulls)) {
for (const pull of data) {
if (pull.head.sha === pull_head_sha && pull.user.id === pull_user_id) {
return pull.number;
}
}
}
})();
if (issue_number) {
core.info(`Using pull request ${issue_number}`);
} else {
return core.error(`No matching pull request found`);
}
const {data: {artifacts}} = await github.actions.listWorkflowRunArtifacts({owner, repo, run_id});
if (!artifacts.length) {
return core.error(`No artifacts found`);
}
let body = `Download the artifacts for this pull request:\n`;
for (const art of artifacts) {
body += `\n* [${art.name}.zip](https://nightly.link/${owner}/${repo}/actions/artifacts/${art.id}.zip)`;
}
const {data: comments} = await github.issues.listComments({repo, owner, issue_number});
const existing_comment = comments.find((c) => c.user.login === 'github-actions[bot]');
if (existing_comment) {
core.info(`Updating comment ${existing_comment.id}`);
await github.issues.updateComment({repo, owner, comment_id: existing_comment.id, body});
} else {
core.info(`Creating a comment`);
await github.issues.createComment({repo, owner, issue_number, body});
}

View File

@ -6,7 +6,8 @@ ARG TARGETPLATFORM
RUN echo "Target Platform = ${TARGETPLATFORM}"
COPY dist .
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then cp navidrome_linux_amd64_linux_amd64/navidrome /navidrome; fi
RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then cp navidrome_linux_amd64_linux_amd64_v1/navidrome /navidrome; fi
RUN if [ "$TARGETPLATFORM" = "linux/386" ]; then cp navidrome_linux_386_linux_386/navidrome /navidrome; fi
RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then cp navidrome_linux_arm64_linux_arm64/navidrome /navidrome; fi
RUN if [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then cp navidrome_linux_arm_linux_arm_6/navidrome /navidrome; fi
RUN if [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then cp navidrome_linux_arm_linux_arm_7/navidrome /navidrome; fi
@ -15,11 +16,13 @@ RUN chmod +x /navidrome
#####################################################
### Build Final Image
FROM alpine as release
FROM alpine:3.18
LABEL maintainer="deluan@navidrome.org"
# Install ffmpeg and output build config
RUN apk add --no-cache ffmpeg
# Install ffmpeg and mpv
RUN apk add -U --no-cache ffmpeg mpv
# Show ffmpeg build info, for troubleshooting purposes
RUN ffmpeg -buildconf
COPY --from=copy-binary /navidrome /app/

View File

@ -1,4 +1,4 @@
name: Pipeline
name: "Pipeline: Test, Lint, Build"
on:
push:
branches:
@ -8,163 +8,200 @@ on:
pull_request:
branches:
- master
jobs:
golangci-lint:
name: Lint Server
go-lint:
name: Lint Go code
runs-on: ubuntu-latest
container: deluan/ci-goreleaser:1.22.3-1
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Config workspace folder as trusted
run: git config --global --add safe.directory $GITHUB_WORKSPACE; git describe --dirty --always --tags
- name: golangci-lint
uses: golangci/golangci-lint-action@v1
uses: golangci/golangci-lint-action@v6
with:
version: v1.27
version: latest
github-token: ${{ secrets.GITHUB_TOKEN }}
problem-matchers: true
args: --timeout 2m
- name: Install goimports
run: go install golang.org/x/tools/cmd/goimports@latest
- run: goimports -w `find . -name '*.go' | grep -v '_gen.go$'`
- run: go mod tidy
- name: Verify no changes from goimports and go mod tidy
run: |
git status --porcelain
if [ -n "$(git status --porcelain)" ]; then
echo 'To fix this check, run "make format" and commit the changes'
exit 1
fi
go:
name: Test Server on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
# TODO Fix tests in Windows
# os: [macOS-latest, ubuntu-latest, windows-latest]
os: [macOS-latest, ubuntu-latest]
name: Test Go code
runs-on: ubuntu-latest
container: deluan/ci-goreleaser:1.22.3-1
steps:
- name: Set up Go 1.14
uses: actions/setup-go@v1
with:
go-version: 1.14
id: go
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- uses: actions/cache@v1
id: cache-go
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Config workspace folder as trusted
run: git config --global --add safe.directory $GITHUB_WORKSPACE; git describe --dirty --always --tags
- name: Download dependencies
if: steps.cache-go.outputs.cache-hit != 'true'
continue-on-error: ${{contains(matrix.go_version, 'beta') || contains(matrix.go_version, 'rc')}}
run: go mod download
- name: Test
run: go test -cover ./... -v
continue-on-error: ${{contains(matrix.go_version, 'beta') || contains(matrix.go_version, 'rc')}}
run: go test -shuffle=on -race -cover ./... -v
js:
name: Build JS bundle
runs-on: ubuntu-latest
env:
NODE_OPTIONS: "--max_old_space_size=4096"
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 14
- uses: actions/cache@v1
id: cache-npm
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('ui/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
node-version: 20
cache: "npm"
cache-dependency-path: "**/package-lock.json"
- name: npm install dependencies
run: |
cd ui
npm ci
- name: npm check-formatting
- name: npm lint
run: |
cd ui
npm run check-formatting
npm run check-formatting && npm run lint
- name: npm test
run: |
cd ui
npm test
- name: npm build
run: |
cd ui
npm run build
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
with:
name: js-bundle
path: ui/build
retention-days: 7
binaries:
name: Binaries
needs: [js, go, golangci-lint]
name: Build binaries
needs: [js, go, go-lint]
runs-on: ubuntu-latest
container: deluan/ci-goreleaser:1.22.3-1
steps:
- name: Checkout Code
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/download-artifact@v2
- name: Config workspace folder as trusted
run: git config --global --add safe.directory $GITHUB_WORKSPACE; git describe --dirty --always --tags
- uses: actions/download-artifact@v4
with:
name: js-bundle
path: ui/build
- name: Show Tags
run: git tag
- name: Show Version
run: git describe --tags
- name: Run GoReleaser - SNAPSHOT
if: startsWith(github.ref, 'refs/tags/') != true
uses: docker://deluan/ci-goreleaser:1.14.4-3
run: goreleaser release --clean --skip=publish --snapshot
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: goreleaser release --rm-dist --skip-publish --snapshot
- name: Run GoReleaser - RELEASE
if: startsWith(github.ref, 'refs/tags/')
uses: docker://deluan/ci-goreleaser:1.14.4-3
run: goreleaser release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
args: goreleaser release --rm-dist
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v4
with:
name: binaries
path: |
dist
!dist/*.tar.gz
!dist/*.zip
retention-days: 7
docker:
name: Docker images
name: Build and publish Docker images
needs: [binaries]
runs-on: ubuntu-latest
env:
DOCKER_IMAGE: ${{secrets.DOCKER_IMAGE}}
steps:
- name: Set up QEMU
id: qemu
uses: docker/setup-qemu-action@v3
if: env.DOCKER_IMAGE != ''
- name: Set up Docker Buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v1
if: env.DOCKER_IMAGE != ''
with:
buildx-version: latest
qemu-version: latest
- uses: actions/checkout@v1
uses: docker/setup-buildx-action@v3
if: env.DOCKER_IMAGE != ''
- uses: actions/download-artifact@v2
- uses: actions/checkout@v4
if: env.DOCKER_IMAGE != ''
- uses: actions/download-artifact@v4
if: env.DOCKER_IMAGE != ''
with:
name: binaries
path: dist
- name: Build the Docker image and push
- name: Login to Docker Hub
if: env.DOCKER_IMAGE != ''
env:
DOCKER_IMAGE: ${{secrets.DOCKER_IMAGE}}
DOCKER_PLATFORM: linux/amd64,linux/arm/v7,linux/arm64
run: |
echo ${{secrets.DOCKER_PASSWORD}} | docker login -u ${{secrets.DOCKER_USERNAME}} --password-stdin
docker buildx build --platform ${DOCKER_PLATFORM} `.github/workflows/docker-tags.sh` -f .github/workflows/pipeline.dockerfile --push .
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Login to GitHub Container Registry
if: env.DOCKER_IMAGE != ''
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
if: env.DOCKER_IMAGE != ''
id: meta
uses: docker/metadata-action@v5
with:
labels: |
maintainer=deluan
images: |
name=${{secrets.DOCKER_IMAGE}}
name=ghcr.io/${{ github.repository }}
tags: |
type=ref,event=pr
type=semver,pattern={{version}}
type=raw,value=develop,enable={{is_default_branch}}
- name: Build and Push
if: env.DOCKER_IMAGE != ''
uses: docker/build-push-action@v5
with:
context: .
file: .github/workflows/pipeline.dockerfile
platforms: linux/amd64,linux/386,linux/arm/v6,linux/arm/v7,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}

View File

@ -1,18 +0,0 @@
name: Remove old artifacts
on:
schedule:
# Every day at 1am
- cron: '0 1 * * *'
jobs:
remove-old-artifacts:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Remove old artifacts
uses: c-hive/gha-remove-artifacts@v1
with:
age: '7 days'
skip-tags: false

56
.github/workflows/stale.yml vendored Normal file
View File

@ -0,0 +1,56 @@
name: 'Close stale issues and PRs'
on:
workflow_dispatch:
schedule:
- cron: '30 1 * * *'
permissions:
contents: read
jobs:
stale:
permissions:
issues: write
pull-requests: write
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v5
with:
process-only: 'issues, prs'
issue-inactive-days: 120
pr-inactive-days: 120
log-output: true
add-issue-labels: 'frozen-due-to-age'
add-pr-labels: 'frozen-due-to-age'
issue-comment: >
This issue has been automatically locked since there
has not been any recent activity after it was closed.
Please open a new issue for related bugs.
pr-comment: >
This pull request has been automatically locked since there
has not been any recent activity after it was closed.
Please open a new issue for related bugs.
- uses: actions/stale@v9
with:
operations-per-run: 999
days-before-issue-stale: 180
days-before-pr-stale: 180
days-before-issue-close: 30
days-before-pr-close: 30
stale-issue-message: >
This issue has been automatically marked as stale because it has not had
recent activity. The resources of the Navidrome team are limited, and so we are asking for your help.
If this is a **bug** and you can still reproduce this error on the <code>master</code> branch, please reply with all of the information you have about it in order to keep the issue open.
If this is a **feature request**, and you feel that it is still relevant and valuable, please tell us why.
This issue will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
stale-pr-message: This PR has been automatically marked as stale because it has not had
recent activity. The resources of the Navidrome team are limited, and so we are asking for your help.
Please check https://github.com/navidrome/navidrome/blob/master/CONTRIBUTING.md#pull-requests and verify that this code contribution fits with the description. If yes, tell it in a comment.
This PR will automatically be closed in the near future if no further activity occurs. Thank you for all your contributions.
stale-issue-label: 'stale'
exempt-issue-labels: 'keep,security'
stale-pr-label: 'stale'
exempt-pr-labels: 'keep,security'

View File

@ -0,0 +1,28 @@
name: POEditor import
on:
workflow_dispatch:
schedule:
- cron: '0 10 * * *'
jobs:
update-translations:
runs-on: ubuntu-latest
if: ${{ github.repository_owner == 'navidrome' }}
steps:
- uses: actions/checkout@v4
- name: Get updated translations
env:
POEDITOR_PROJECTID: ${{ secrets.POEDITOR_PROJECTID }}
POEDITOR_APIKEY: ${{ secrets.POEDITOR_APIKEY }}
run: |
./update-translations.sh
- name: Show changes, if any
run: |
git status --porcelain
git diff
- name: Create Pull Request
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.PAT }}
commit-message: Update translations
title: Update translations from POEditor
branch: update-translations

8
.gitignore vendored
View File

@ -12,15 +12,17 @@ TODO.md
var
navidrome.toml
master.zip
Jamstash-master
testDB
navidrome.db
cache/*
*.swp
embedded_gen.go
dist
music
docker-compose.yml
navidrome.db-shm
navidrome.db-wal
tags
.gitinfo
docker-compose.yml
!contrib/docker-compose.yml
test-123.db

View File

@ -1,29 +1,36 @@
linters:
enable:
- asasalint
- asciicheck
- bidichk
- bodyclose
- deadcode
- dogsled
- durationcheck
- errcheck
- errorlint
- exportloopref
- gocyclo
- goimports
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- misspell
- nakedret
- nilerr
- rowserrcheck
- staticcheck
- structcheck
- typecheck
- unconvert
- unused
- varcheck
- whitespace
issues:
exclude-rules:
- linters:
- gosec
text: "(G501|G401):"
linters-settings:
govet:
enable:
- nilness
gosec:
excludes:
- G501
- G401
- G505

View File

@ -1,26 +1,7 @@
# GoReleaser config
project_name: navidrome
before:
hooks:
- go-bindata -fs -prefix resources -tags embed -ignore="\\\*.go" -pkg resources -o resources/embedded_gen.go resources/...
- go-bindata -fs -prefix ui/build -tags embed -nocompress -pkg assets -o assets/embedded_gen.go ui/build/...
builds:
- id: navidrome_darwin
env:
- CGO_ENABLED=1
- CC=o64-clang
- CXX=o64-clang++
goos:
- darwin
goarch:
- amd64
flags:
- -tags=embed
ldflags:
- -s -w -X github.com/deluan/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/deluan/navidrome/consts.gitTag={{.Version}}
- id: navidrome_linux_amd64
env:
- CGO_ENABLED=1
@ -29,96 +10,112 @@ builds:
goarch:
- amd64
flags:
- -tags=embed
- -tags=netgo
ldflags:
- "-extldflags '-static'"
- -s -w -X github.com/deluan/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/deluan/navidrome/consts.gitTag={{.Version}}
- "-extldflags '-static -lz'"
- -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
- id: navidrome_linux_386
env:
- CGO_ENABLED=1
- PKG_CONFIG_PATH=/i386/lib/pkgconfig
goos:
- linux
goarch:
- 386
- "386"
flags:
- -tags=embed
- -tags=netgo
ldflags:
- "-extldflags '-static'"
- -s -w -X github.com/deluan/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/deluan/navidrome/consts.gitTag={{.Version}}
- -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
- id: navidrome_linux_arm
env:
- CGO_ENABLED=1
- CC=arm-linux-gnueabi-gcc-5
- CC=arm-linux-gnueabi-gcc
- CXX=arm-linux-gnueabi-g++
- PKG_CONFIG_PATH=/arm/lib/pkgconfig
goos:
- linux
goarch:
- arm
goarm:
- 5
- 6
- 7
- "5"
- "6"
- "7"
flags:
- -tags=embed
- -tags=netgo
ldflags:
- "-extldflags '-static'"
- -s -w -X github.com/deluan/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/deluan/navidrome/consts.gitTag={{.Version}}
- -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
- id: navidrome_linux_arm64
env:
- CGO_ENABLED=1
- CC=aarch64-linux-gnu-gcc-5
- CC=aarch64-linux-gnu-gcc
- CXX=aarch64-linux-gnu-g++
- PKG_CONFIG_PATH=/arm64/lib/pkgconfig
goos:
- linux
goarch:
- arm64
flags:
- -tags=embed
- -tags=netgo
ldflags:
- "-extldflags '-static'"
- -s -w -X github.com/deluan/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/deluan/navidrome/consts.gitTag={{.Version}}
- -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
- id: navidrome_windows_i686
- id: navidrome_windows_386
env:
- CGO_ENABLED=1
- CC=i686-w64-mingw32-gcc
- CXX=i686-w64-mingw32-g++
- PKG_CONFIG_PATH=/mingw32/lib/pkgconfig
goos:
- windows
goarch:
- 386
- "386"
flags:
- -tags=embed
- -tags=netgo
ldflags:
- "-extldflags '-static'"
- -s -w -X github.com/deluan/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/deluan/navidrome/consts.gitTag={{.Version}}
- -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
- id: navidrome_windows_x64
- id: navidrome_windows_amd64
env:
- CGO_ENABLED=1
- CC=x86_64-w64-mingw32-gcc
- CXX=x86_64-w64-mingw32-g++
- PKG_CONFIG_PATH=/mingw64/lib/pkgconfig
goos:
- windows
goarch:
- amd64
flags:
- -tags=embed
- -tags=netgo
ldflags:
- "-extldflags '-static'"
- -s -w -X github.com/deluan/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/deluan/navidrome/consts.gitTag={{.Version}}
- -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
- id: navidrome_darwin_amd64
env:
- CGO_ENABLED=1
- CC=o64-clang
- CXX=o64-clang++
- PKG_CONFIG_PATH=/darwin/lib/pkgconfig
goos:
- darwin
goarch:
- amd64
flags:
- -tags=netgo
ldflags:
- -s -w -X github.com/navidrome/navidrome/consts.gitSha={{.ShortCommit}} -X github.com/navidrome/navidrome/consts.gitTag={{.Version}}
archives:
- format_overrides:
- goos: windows
format: zip
replacements:
darwin: macOS
linux: Linux
windows: Windows
386: i386
amd64: x86_64
checksum:
name_template: "{{ .ProjectName }}_checksums.txt"

2
.nvmrc
View File

@ -1 +1 @@
v14
v20

92
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,92 @@
# Navidrome Contribution Guide
Navidrome is a streaming service which allows you to enjoy your music collection from anywhere. We'd welcome you to contribute to our open source project and make Navidrome even better. There are some basic guidelines which you need to follow if you like to contribute to Navidrome.
- [Asking Support Questions](#asking-support-questions)
- [Code of Conduct](#code-of-conduct)
- [Issues](#issues)
- [Pull Requests](#pull-requests)
## Asking Support Questions
We have an active [discussion forum](https://github.com/navidrome/navidrome/discussions) where users and developers can ask questions. Please don't use the GitHub issue tracker to ask questions.
## Code of Conduct
Please read the following [Code of Conduct](https://github.com/navidrome/navidrome/blob/master/CODE_OF_CONDUCT.md).
## Issues
Found any issue or bug in our codebase? Have a great idea you want to propose or discuss with
the developers? You can help by submitting an [issue](https://github.com/navidrome/navidrome/issues/new/choose)
to the GitHub repository.
**Before opening a new issue, please check if the issue has not been already made by searching
the [issues](https://github.com/navidrome/navidrome/issues)**
## Pull requests
Before submitting a pull request, ensure that you go through the following:
- Open a corresponding issue for the Pull Request, if not existing. The issue can be opened following [these guidelines](#issues)
- Ensure that there is no open or closed Pull Request corresponding to your submission to avoid duplication of effort.
- Setup the [development environment](https://www.navidrome.org/docs/developers/dev-environment/)
- Create a new branch on your forked repo and make the changes in it. Naming conventions for branch are: `<Issue Title>/<Issue Number>`. Example:
```
git checkout -b adding-docs/834 master
```
- The commits should follow a [specific convention](#commit-conventions)
- Ensure that a DCO sign-off for commits is provided via `--signoff` option of git commit
- Provide a link to the issue that will be closed via your Pull request.
### Commit Conventions
Each commit message must adhere to the following format:
```
<type>(scope): <description> - <issue number>
[optional body]
```
This improves the readability of the messages
#### Type
It can be one of the following:
1. **feat**: Addition of a new feature
2. **fix**: Bug fix
3. **docs**: Documentation Changes
4. **style**: Changes to styling
5. **refactor**: Refactoring of code
6. **perf**: Code that affects performance
7. **test**: Updating or improving the current tests
8. **build**: Changes to Build process
9. **revert**: Reverting to a previous commit
10. **chore** : updating grunt tasks etc
If there is a breaking change in your Pull Request, please add `BREAKING CHANGE` in the optional body section
#### Scope
The file or folder where the changes are made. If there are more than one, you can mention any
#### Description
A short description of the issue
#### Issue number
The issue fixed by this Pull Request.
The body is optional. It may contain short description of changes made.
Following all the guidelines an ideal commit will look like:
```
git commit --signoff -m "feat(themes): New-theme - #834"
```
After committing, push your commits to your forked branch and create a Pull Request from there.
The Pull Request Title can be the same as `<type>(scope): <description> - <issue number>`
A demo layout of how the Pull request body can look:
```
Closes <Issue number along with link>
Description (What does the pull request do)
Changes (What changes were made )
Screenshots or Videos
Related Issues and Pull Requests(if any)
```

237
Makefile
View File

@ -1,107 +1,188 @@
GO_VERSION=$(shell grep -e "^go " go.mod | cut -f 2 -d ' ')
GO_VERSION=$(shell grep "^go " go.mod | cut -f 2 -d ' ')
NODE_VERSION=$(shell cat .nvmrc)
ifneq ("$(wildcard .git/HEAD)","")
GIT_SHA=$(shell git rev-parse --short HEAD)
GIT_TAG=$(shell git describe --tags `git rev-list --tags --max-count=1`)
else
GIT_SHA=source_archive
GIT_TAG=$(patsubst navidrome-%,v%,$(notdir $(PWD)))
endif
## Default target just build the Go project.
default:
go build -ldflags="-X github.com/deluan/navidrome/consts.gitSha=$(GIT_SHA) -X github.com/deluan/navidrome/consts.gitTag=master"
.PHONY: default
CI_RELEASER_VERSION=1.22.3-1 ## https://github.com/navidrome/ci-goreleaser
dev: check_env
npx foreman -j Procfile.dev -p 4533 start
.PHONY: dev
server: check_go_env
@reflex -d none -c reflex.conf
.PHONY: server
wire: check_go_env
wire ./...
.PHONY: wire
watch: check_go_env
ginkgo watch -notify ./...
.PHONY: watch
test: check_go_env
go test ./... -v
.PHONY: test
testall: check_go_env test
@(cd ./ui && npm test -- --watchAll=false)
.PHONY: testall
update-snapshots: check_go_env
UPDATE_SNAPSHOTS=true ginkgo ./server/subsonic/...
.PHONY: update-snapshots
migration:
@if [ -z "${name}" ]; then echo "Usage: make create-migration name=name_of_migration_file"; exit 1; fi
goose -dir db/migration create ${name}
.PHONY: migration
setup:
@which go-bindata || (echo "Installing BinData" && GO111MODULE=off go get -u github.com/go-bindata/go-bindata/...)
go mod download
setup: check_env download-deps setup-git ##@1_Run_First Install dependencies and prepare development environment
@echo Downloading Node dependencies...
@(cd ./ui && npm ci)
.PHONY: setup
setup-dev: setup setup-git
@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 reflex || (echo "Installing Reflex" && GO111MODULE=off go get -u github.com/cespare/reflex)
@which golangci-lint || (echo "Installing GolangCI-Lint" && cd .. && GO111MODULE=on go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.27.0)
@which goimports || (echo "Installing goimports" && GO111MODULE=off go get -u golang.org/x/tools/cmd/goimports)
dev: check_env ##@Development Start Navidrome in development mode, with hot-reload for both frontend and backend
npx foreman -j Procfile.dev -p 4533 start
.PHONY: dev
server: check_go_env ##@Development Start the backend in development mode
@go run github.com/cespare/reflex@latest -d none -c reflex.conf
.PHONY: server
watch: ##@Development Start Go tests in watch mode (re-run when code changes)
go run github.com/onsi/ginkgo/v2/ginkgo@latest watch -notify ./...
.PHONY: watch
test: ##@Development Run Go tests
go test -race -shuffle=on ./...
.PHONY: test
testall: test ##@Development Run Go and JS tests
@(cd ./ui && npm test -- --watchAll=false)
.PHONY: testall
lint: ##@Development Lint Go code
go run github.com/golangci/golangci-lint/cmd/golangci-lint@latest run -v --timeout 5m
.PHONY: lint
lintall: lint ##@Development Lint Go and JS code
@(cd ./ui && npm run check-formatting) || (echo "\n\nPlease run 'npm run prettier' to fix formatting issues." && exit 1)
@(cd ./ui && npm run lint)
.PHONY: lintall
format: ##@Development Format code
@(cd ./ui && npm run prettier)
@go run golang.org/x/tools/cmd/goimports@latest -w `find . -name '*.go' | grep -v _gen.go$$`
@go mod tidy
.PHONY: format
wire: check_go_env ##@Development Update Dependency Injection
go run github.com/google/wire/cmd/wire@latest ./...
.PHONY: wire
snapshots: ##@Development Update (GoLang) Snapshot tests
UPDATE_SNAPSHOTS=true go run github.com/onsi/ginkgo/v2/ginkgo@latest ./server/subsonic/...
.PHONY: snapshots
migration-sql: ##@Development Create an empty SQL migration file
@if [ -z "${name}" ]; then echo "Usage: make migration-sql name=name_of_migration_file"; exit 1; fi
go run github.com/pressly/goose/v3/cmd/goose@latest -dir db/migrations create ${name} sql
.PHONY: migration
migration-go: ##@Development Create an empty Go migration file
@if [ -z "${name}" ]; then echo "Usage: make migration-go name=name_of_migration_file"; exit 1; fi
go run github.com/pressly/goose/v3/cmd/goose@latest -dir db/migrations create ${name}
.PHONY: migration
setup-dev: setup
.PHONY: setup-dev
setup-git:
setup-git: ##@Development Setup Git hooks (pre-commit and pre-push)
@echo Setting up git hooks
@mkdir -p .git/hooks
(cd .git/hooks && ln -sf ../../git/* .)
@(cd .git/hooks && ln -sf ../../git/* .)
.PHONY: setup-git
Jamstash-master:
wget -N https://github.com/tsquillario/Jamstash/archive/master.zip
unzip -o master.zip
rm master.zip
(cd Jamstash-master && npm ci && npx bower install && npx grunt build)
rm -rf Jamstash-master/node_modules Jamstash-master/bower_components
buildall: buildjs build ##@Build Build the project, both frontend and backend
.PHONY: buildall
build: warning-noui-build check_go_env ##@Build Build only backend
go build -ldflags="-X github.com/navidrome/navidrome/consts.gitSha=$(GIT_SHA) -X github.com/navidrome/navidrome/consts.gitTag=$(GIT_TAG)-SNAPSHOT" -tags=netgo
.PHONY: build
debug-build: warning-noui-build check_go_env ##@Build Build only backend (with remote debug on)
go build -gcflags="all=-N -l" -ldflags="-X github.com/navidrome/navidrome/consts.gitSha=$(GIT_SHA) -X github.com/navidrome/navidrome/consts.gitTag=$(GIT_TAG)-SNAPSHOT" -tags=netgo
.PHONY: debug-build
buildjs: check_node_env ##@Build Build only frontend
@(cd ./ui && npm run build)
.PHONY: buildjs
all: warning-noui-build ##@Cross_Compilation Build binaries for all supported platforms. It does not build the frontend
@echo "Building binaries for all platforms using builder ${CI_RELEASER_VERSION}"
docker run -t -v $(PWD):/workspace -w /workspace deluan/ci-goreleaser:$(CI_RELEASER_VERSION) \
goreleaser release --clean --skip=publish --snapshot
.PHONY: all
single: warning-noui-build ##@Cross_Compilation Build binaries for a single supported platforms. It does not build the frontend
@if [ -z "${GOOS}" -o -z "${GOARCH}" ]; then \
echo "Usage: GOOS=<os> GOARCH=<arch> make single"; \
echo "Options:"; \
grep -- "- id: navidrome_" .goreleaser.yml | sed 's/- id: navidrome_//g'; \
exit 1; \
fi
@echo "Building binaries for ${GOOS}/${GOARCH} using builder ${CI_RELEASER_VERSION}"
docker run -t -v $(PWD):/workspace -e GOOS -e GOARCH -w /workspace deluan/ci-goreleaser:$(CI_RELEASER_VERSION) \
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
get-music: ##@Development Download some free music from Navidrome's demo instance
mkdir -p music
( cd music; \
curl "https://demo.navidrome.org/rest/download?u=demo&p=demo&f=json&v=1.8.0&c=dev_download&id=ec2093ec4801402f1e17cc462195cdbb" > brock.zip; \
curl "https://demo.navidrome.org/rest/download?u=demo&p=demo&f=json&v=1.8.0&c=dev_download&id=b376eeb4652d2498aa2b25ba0696725e" > back_on_earth.zip; \
curl "https://demo.navidrome.org/rest/download?u=demo&p=demo&f=json&v=1.8.0&c=dev_download&id=e49c609b542fc51899ee8b53aa858cb4" > ugress.zip; \
curl "https://demo.navidrome.org/rest/download?u=demo&p=demo&f=json&v=1.8.0&c=dev_download&id=350bcab3a4c1d93869e39ce496464f03" > voodoocuts.zip; \
for file in *.zip; do unzip -n $${file}; done )
@echo "Done. Remember to set your MusicFolder to ./music"
.PHONY: get-music
##########################################
#### Miscellaneous
release:
@if [[ ! "${V}" =~ ^[0-9]+\.[0-9]+\.[0-9]+.*$$ ]]; then echo "Usage: make release V=X.X.X"; exit 1; fi
go mod tidy
@if [ -n "`git status -s`" ]; then echo "\n\nThere are pending changes. Please commit or stash first"; exit 1; fi
make pre-push
git tag v${V}
git push origin v${V} --no-verify
.PHONY: release
download-deps:
@echo Downloading Go dependencies...
@go mod download
@go mod tidy # To revert any changes made by the `go mod download` command
.PHONY: download-deps
check_env: check_go_env check_node_env
.PHONY: check_env
check_go_env:
@(hash go) || (echo "\nERROR: GO environment not setup properly!\n"; exit 1)
@go version | grep -q $(GO_VERSION) || (echo "\nERROR: Please upgrade your GO version\nThis project requires version $(GO_VERSION)"; exit 1)
@current_go_version=`go version | cut -d ' ' -f 3 | cut -c3-` && \
echo "$(GO_VERSION) $$current_go_version" | \
tr ' ' '\n' | sort -V | tail -1 | \
grep -q "^$${current_go_version}$$" || \
(echo "\nERROR: Please upgrade your GO version\nThis project requires at least the version $(GO_VERSION)"; exit 1)
.PHONY: check_go_env
check_node_env:
@(hash node) || (echo "\nERROR: Node environment not setup properly!\n"; exit 1)
@node --version | grep -q $(NODE_VERSION) || (echo "\nERROR: Please check your Node version. Should be $(NODE_VERSION)\n"; exit 1)
@current_node_version=`node --version` && \
echo "$(NODE_VERSION) $$current_node_version" | \
tr ' ' '\n' | sort -V | tail -1 | \
grep -q "^$${current_node_version}$$" || \
(echo "\nERROR: Please check your Node version. Should be at least $(NODE_VERSION)\n"; exit 1)
.PHONY: check_node_env
build: check_go_env
go build -ldflags="-X github.com/deluan/navidrome/consts.gitSha=$(GIT_SHA) -X github.com/deluan/navidrome/consts.gitTag=$(GIT_TAG)-SNAPSHOT"
.PHONY: build
pre-push: lintall testall
.PHONY: pre-push
buildall: check_env
@(cd ./ui && npm run build)
go-bindata -fs -prefix "resources" -tags embed -ignore="\\\*.go" -pkg resources -o resources/embedded_gen.go resources/...
go-bindata -fs -prefix "ui/build" -tags embed -nocompress -pkg assets -o assets/embedded_gen.go ui/build/...
go build -ldflags="-X github.com/deluan/navidrome/consts.gitSha=$(GIT_SHA) -X github.com/deluan/navidrome/consts.gitTag=$(GIT_TAG)-SNAPSHOT" -tags=embed
.PHONY: buildall
.DEFAULT_GOAL := help
release:
@if [[ ! "${V}" =~ ^[0-9]+\.[0-9]+\.[0-9]+.*$$ ]]; then echo "Usage: make release V=X.X.X"; exit 1; fi
go mod tidy
@if [ -n "`git status -s`" ]; then echo "\n\nThere are pending changes. Please commit or stash first"; exit 1; fi
make test
git tag v${V}
git push origin v${V}
.PHONY: release
HELP_FUN = \
%help; while(<>){push@{$$help{$$2//'options'}},[$$1,$$3] \
if/^([\w-_]+)\s*:.*\#\#(?:@(\w+))?\s(.*)$$/}; \
print"$$_:\n", map" $$_->[0]".(" "x(20-length($$_->[0])))."$$_->[1]\n",\
@{$$help{$$_}},"\n" for sort keys %help; \
snapshot:
docker run -it -v $(PWD):/workspace -w /workspace deluan/ci-goreleaser:1.14.4-3 goreleaser release --rm-dist --skip-publish --snapshot
.PHONY: snapshot
help: ##@Miscellaneous Show this help
@echo "Usage: make [target] ...\n"
@perl -e '$(HELP_FUN)' $(MAKEFILE_LIST)

View File

@ -1,2 +1,2 @@
JS: sh -c "cd ./ui && npm start"
GO: reflex -c reflex.conf
GO: go run github.com/cespare/reflex@latest -d none -c reflex.conf

View File

@ -1,31 +1,44 @@
# Navidrome Music Server
<a href="https://www.navidrome.org"><img src="resources/logo-192x192.png" alt="Navidrome logo" title="navidrome" align="right" height="60px" /></a>
[![Last Release](https://img.shields.io/github/v/release/deluan/navidrome?label=latest&style=flat-square)](https://github.com/deluan/navidrome/releases)
[![Build](https://img.shields.io/github/workflow/status/deluan/navidrome/Build?style=flat-square)](https://github.com/deluan/navidrome/actions)
[![Downloads](https://img.shields.io/github/downloads/deluan/navidrome/total)](https://github.com/deluan/navidrome/releases/latest)
[![Docker Pulls](https://img.shields.io/docker/pulls/deluan/navidrome?style=flat-square)](https://hub.docker.com/r/deluan/navidrome)
[![Dev Chat](https://img.shields.io/discord/671335427726114836?label=chat&style=flat-square)](https://discord.gg/xh7j7yF)
[![Subreddit](https://img.shields.io/reddit/subreddit-subscribers/navidrome?style=flat-square)](https://www.reddit.com/r/navidrome/)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0-ff69b4.svg)](code_of_conduct.md)
# Navidrome Music Server &nbsp;[![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=Tired%20of%20paying%20for%20music%20subscriptions%2C%20and%20not%20finding%20what%20you%20really%20like%3F%20Roll%20your%20own%20streaming%20service%21&url=https://navidrome.org&via=navidrome)
Navidrome is an open source web-based music collection server and streamer. It gives you freedom to listen to your
[![Last Release](https://img.shields.io/github/v/release/navidrome/navidrome?logo=github&label=latest&style=flat-square)](https://github.com/navidrome/navidrome/releases)
[![Build](https://img.shields.io/github/actions/workflow/status/navidrome/navidrome/pipeline.yml?branch=master&logo=github&style=flat-square)](https://nightly.link/navidrome/navidrome/workflows/pipeline/master)
[![Downloads](https://img.shields.io/github/downloads/navidrome/navidrome/total?logo=github&style=flat-square)](https://github.com/navidrome/navidrome/releases/latest)
[![Docker Pulls](https://img.shields.io/docker/pulls/deluan/navidrome?logo=docker&label=pulls&style=flat-square)](https://hub.docker.com/r/deluan/navidrome)
[![Dev Chat](https://img.shields.io/discord/671335427726114836?logo=discord&label=discord&style=flat-square)](https://discord.gg/xh7j7yF)
[![Subreddit](https://img.shields.io/badge/%2Fr%2Fnavidrome-%2B3000-red?logo=reddit)](https://www.reddit.com/r/navidrome/)
[![Contributor Covenant](https://img.shields.io/badge/Contributor%20Covenant-v2.0-ff69b4.svg?style=flat-square)](CODE_OF_CONDUCT.md)
Navidrome is an open source web-based music collection server and streamer. It gives you freedom to listen to your
music collection from any browser or mobile device. It's like your personal Spotify!
**Note**: The `master` branch may be in an unstable or even broken state during development.
Please use [releases](https://github.com/navidrome/navidrome/releases) instead of
the `master` branch in order to get a stable set of binaries.
## [Check out our Live Demo!](https://www.navidrome.org/demo/)
__Any feedback is welcome!__ If you need/want a new feature, find a bug or think of any way to improve Navidrome,
please file a [GitHub issue](https://github.com/deluan/navidrome/issues) or join the discussion in our
please file a [GitHub issue](https://github.com/navidrome/navidrome/issues) or join the discussion in our
[Subreddit](https://www.reddit.com/r/navidrome/). If you want to contribute to the project in any other way
([ui/backend dev](https://www.navidrome.org/docs/developers/),
[translations](https://www.navidrome.org/docs/developers/translations/),
[themes](https://www.navidrome.org/docs/developers/creating-themes)), please join the chat in our
[Discord server](https://discord.gg/xh7j7yF).
## Demo Site
To see Navidrome in action, check out our [live demo](https://www.navidrome.org/demo/)
## Installation
See instructions in the [project's website](https://www.navidrome.org/docs/installation/)
See instructions on the [project's website](https://www.navidrome.org/docs/installation/)
## Cloud Hosting
[PikaPods](https://www.pikapods.com) has partnered with us to offer you an
[officially supported, cloud-hosted solution](https://www.navidrome.org/docs/installation/managed/#pikapods).
A share of the revenue helps fund the development of Navidrome at no additional cost for you.
[![PikaPods](https://www.pikapods.com/static/run-button.svg)](https://www.pikapods.com/pods?run=navidrome)
## Features
@ -58,8 +71,8 @@ Here are some useful direct links:
## Screenshots
<p align="left">
<img height="550" src="https://raw.githubusercontent.com/deluan/navidrome/master/.github/screenshots/ss-mobile-login.png">
<img height="550" src="https://raw.githubusercontent.com/deluan/navidrome/master/.github/screenshots/ss-mobile-player.png">
<img height="550" src="https://raw.githubusercontent.com/deluan/navidrome/master/.github/screenshots/ss-mobile-album-view.png">
<img width="550" src="https://raw.githubusercontent.com/deluan/navidrome/master/.github/screenshots/ss-desktop-player.png">
<img height="550" src="https://raw.githubusercontent.com/navidrome/navidrome/master/.github/screenshots/ss-mobile-login.png">
<img height="550" src="https://raw.githubusercontent.com/navidrome/navidrome/master/.github/screenshots/ss-mobile-player.png">
<img height="550" src="https://raw.githubusercontent.com/navidrome/navidrome/master/.github/screenshots/ss-mobile-album-view.png">
<img width="550" src="https://raw.githubusercontent.com/navidrome/navidrome/master/.github/screenshots/ss-desktop-player.png">
</p>

View File

@ -1,19 +0,0 @@
// +build !embed
package assets
import (
"net/http"
"sync"
"github.com/deluan/navidrome/log"
)
var once sync.Once
func AssetFile() http.FileSystem {
once.Do(func() {
log.Warn("Using external assets from 'ui/build' folder")
})
return http.Dir("ui/build")
}

99
cmd/inspect.go Normal file
View File

@ -0,0 +1,99 @@
package cmd
import (
"encoding/json"
"fmt"
"strings"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/scanner"
"github.com/navidrome/navidrome/scanner/metadata"
"github.com/navidrome/navidrome/tests"
"github.com/pelletier/go-toml/v2"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
var (
extractor string
format string
)
func init() {
inspectCmd.Flags().StringVarP(&extractor, "extractor", "x", "", "extractor to use (ffmpeg or taglib, default: auto)")
inspectCmd.Flags().StringVarP(&format, "format", "f", "pretty", "output format (pretty, toml, yaml, json, jsonindent)")
rootCmd.AddCommand(inspectCmd)
}
var inspectCmd = &cobra.Command{
Use: "inspect [files to inspect]",
Short: "Inspect tags",
Long: "Show file tags as seen by Navidrome",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
runInspector(args)
},
}
var marshalers = map[string]func(interface{}) ([]byte, error){
"pretty": prettyMarshal,
"toml": toml.Marshal,
"yaml": yaml.Marshal,
"json": json.Marshal,
"jsonindent": func(v interface{}) ([]byte, error) {
return json.MarshalIndent(v, "", " ")
},
}
func prettyMarshal(v interface{}) ([]byte, error) {
out := v.([]inspectorOutput)
var res strings.Builder
for i := range out {
res.WriteString(fmt.Sprintf("====================\nFile: %s\n\n", out[i].File))
t, _ := toml.Marshal(out[i].RawTags)
res.WriteString(fmt.Sprintf("Raw tags:\n%s\n\n", t))
t, _ = toml.Marshal(out[i].MappedTags)
res.WriteString(fmt.Sprintf("Mapped tags:\n%s\n\n", t))
}
return []byte(res.String()), nil
}
type inspectorOutput struct {
File string
RawTags metadata.ParsedTags
MappedTags model.MediaFile
}
func runInspector(args []string) {
if extractor != "" {
conf.Server.Scanner.Extractor = extractor
}
log.Info("Using extractor", "extractor", conf.Server.Scanner.Extractor)
md, err := metadata.Extract(args...)
if err != nil {
log.Fatal("Error extracting tags", err)
}
mapper := scanner.NewMediaFileMapper(conf.Server.MusicFolder, &tests.MockedGenreRepo{})
marshal := marshalers[format]
if marshal == nil {
log.Fatal("Invalid format", "format", format)
}
var out []inspectorOutput
for k, v := range md {
if !model.IsAudioFile(k) {
continue
}
if len(v.Tags) == 0 {
continue
}
out = append(out, inspectorOutput{
File: k,
RawTags: v.Tags,
MappedTags: mapper.ToMediaFile(v),
})
}
data, _ := marshal(out)
fmt.Println(string(data))
}

71
cmd/pls.go Normal file
View File

@ -0,0 +1,71 @@
package cmd
import (
"context"
"errors"
"os"
"github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/core/auth"
"github.com/navidrome/navidrome/db"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/persistence"
"github.com/spf13/cobra"
)
var (
playlistID string
outputFile string
)
func init() {
plsCmd.Flags().StringVarP(&playlistID, "playlist", "p", "", "playlist name or ID")
plsCmd.Flags().StringVarP(&outputFile, "output", "o", "", "output file (default stdout)")
_ = plsCmd.MarkFlagRequired("playlist")
rootCmd.AddCommand(plsCmd)
}
var plsCmd = &cobra.Command{
Use: "pls",
Short: "Export playlists",
Long: "Export Navidrome playlists to M3U files",
Run: func(cmd *cobra.Command, args []string) {
runExporter()
},
}
func runExporter() {
sqlDB := db.Db()
ds := persistence.New(sqlDB)
ctx := auth.WithAdminUser(context.Background(), ds)
playlist, err := ds.Playlist(ctx).GetWithTracks(playlistID, true)
if err != nil && !errors.Is(err, model.ErrNotFound) {
log.Fatal("Error retrieving playlist", "name", playlistID, err)
}
if errors.Is(err, model.ErrNotFound) {
playlists, err := ds.Playlist(ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"playlist.name": playlistID}})
if err != nil {
log.Fatal("Error retrieving playlist", "name", playlistID, err)
}
if len(playlists) > 0 {
playlist, err = ds.Playlist(ctx).GetWithTracks(playlists[0].ID, true)
if err != nil {
log.Fatal("Error retrieving playlist", "name", playlistID, err)
}
}
}
if playlist == nil {
log.Fatal("Playlist not found", "name", playlistID)
}
pls := playlist.ToM3U8()
if outputFile == "-" || outputFile == "" {
println(pls)
return
}
err = os.WriteFile(outputFile, []byte(pls), 0600)
if err != nil {
log.Fatal("Error writing to the output file", "file", outputFile, err)
}
}

View File

@ -1,14 +1,27 @@
package cmd
import (
"context"
"fmt"
"os"
"os/signal"
"strings"
"syscall"
"time"
"github.com/deluan/navidrome/conf"
"github.com/deluan/navidrome/consts"
"github.com/deluan/navidrome/db"
"github.com/go-chi/chi/v5/middleware"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/db"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/resources"
"github.com/navidrome/navidrome/scheduler"
"github.com/navidrome/navidrome/server/backgrounds"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/sync/errgroup"
)
var (
@ -24,12 +37,16 @@ Complete documentation is available at https://www.navidrome.org/docs`,
preRun()
},
Run: func(cmd *cobra.Command, args []string) {
startServer()
runNavidrome()
},
Version: consts.Version(),
PostRun: func(cmd *cobra.Command, args []string) {
postRun()
},
Version: consts.Version,
}
)
// Execute runs the root cobra command, which will start the Navidrome server by calling the runNavidrome function.
func Execute() {
rootCmd.SetVersionTemplate(`{{println .Version}}`)
if err := rootCmd.Execute(); err != nil {
@ -40,25 +57,129 @@ func Execute() {
func preRun() {
if !noBanner {
println(consts.Banner())
println(resources.Banner())
}
conf.Load()
}
func startServer() {
db.EnsureLatestVersion()
subsonic, err := CreateSubsonicAPIRouter()
if err != nil {
panic(fmt.Sprintf("Could not create the Subsonic API router. Aborting! err=%v", err))
}
a := CreateServer(conf.Server.MusicFolder)
a.MountRouter(consts.URLPathSubsonicAPI, subsonic)
a.MountRouter(consts.URLPathUI, CreateAppRouter())
a.Run(fmt.Sprintf("%s:%d", conf.Server.Address, conf.Server.Port))
func postRun() {
log.Info("Navidrome stopped, bye.")
}
// TODO: Implemement some struct tags to map flags to viper
// runNavidrome is the main entry point for the Navidrome server. It starts all the services and blocks.
// If any of the services returns an error, it will log it and exit. If the process receives a signal to exit,
// it will cancel the context and exit gracefully.
func runNavidrome() {
defer db.Init()()
ctx, cancel := mainContext()
defer cancel()
g, ctx := errgroup.WithContext(ctx)
g.Go(startServer(ctx))
g.Go(startSignaller(ctx))
g.Go(startScheduler(ctx))
g.Go(startPlaybackServer(ctx))
g.Go(schedulePeriodicScan(ctx))
if err := g.Wait(); err != nil {
log.Error("Fatal error in Navidrome. Aborting", err)
}
}
// mainContext returns a context that is cancelled when the process receives a signal to exit.
func mainContext() (context.Context, context.CancelFunc) {
return signal.NotifyContext(context.Background(),
os.Interrupt,
syscall.SIGHUP,
syscall.SIGTERM,
syscall.SIGABRT,
)
}
// startServer starts the Navidrome web server, adding all the necessary routers.
func startServer(ctx context.Context) func() error {
return func() error {
a := CreateServer(conf.Server.MusicFolder)
a.MountRouter("Native API", consts.URLPathNativeAPI, CreateNativeAPIRouter())
a.MountRouter("Subsonic API", consts.URLPathSubsonicAPI, CreateSubsonicAPIRouter())
a.MountRouter("Public Endpoints", consts.URLPathPublic, CreatePublicRouter())
if conf.Server.LastFM.Enabled {
a.MountRouter("LastFM Auth", consts.URLPathNativeAPI+"/lastfm", CreateLastFMRouter())
}
if conf.Server.ListenBrainz.Enabled {
a.MountRouter("ListenBrainz Auth", consts.URLPathNativeAPI+"/listenbrainz", CreateListenBrainzRouter())
}
if conf.Server.Prometheus.Enabled {
// blocking call because takes <1ms but useful if fails
core.WriteInitialMetrics()
a.MountRouter("Prometheus metrics", conf.Server.Prometheus.MetricsPath, promhttp.Handler())
}
if conf.Server.DevEnableProfiler {
a.MountRouter("Profiling", "/debug", middleware.Profiler())
}
if strings.HasPrefix(conf.Server.UILoginBackgroundURL, "/") {
a.MountRouter("Background images", consts.DefaultUILoginBackgroundURL, backgrounds.NewHandler())
}
return a.Run(ctx, conf.Server.Address, conf.Server.Port, conf.Server.TLSCert, conf.Server.TLSKey)
}
}
// schedulePeriodicScan schedules a periodic scan of the music library, if configured.
func schedulePeriodicScan(ctx context.Context) func() error {
return func() error {
schedule := conf.Server.ScanSchedule
if schedule == "" {
log.Warn("Periodic scan is DISABLED")
return nil
}
scanner := GetScanner()
schedulerInstance := scheduler.GetInstance()
log.Info("Scheduling periodic scan", "schedule", schedule)
err := schedulerInstance.Add(schedule, func() {
_ = scanner.RescanAll(ctx, false)
})
if err != nil {
log.Error("Error scheduling periodic scan", err)
}
time.Sleep(2 * time.Second) // Wait 2 seconds before the initial scan
log.Debug("Executing initial scan")
if err := scanner.RescanAll(ctx, false); err != nil {
log.Error("Error executing initial scan", err)
}
log.Debug("Finished initial scan")
return nil
}
}
// startScheduler starts the Navidrome scheduler, which is used to run periodic tasks.
func startScheduler(ctx context.Context) func() error {
return func() error {
log.Info(ctx, "Starting scheduler")
schedulerInstance := scheduler.GetInstance()
schedulerInstance.Run(ctx)
return nil
}
}
// startPlaybackServer starts the Navidrome playback server, if configured.
// It is responsible for the Jukebox functionality
func startPlaybackServer(ctx context.Context) func() error {
return func() error {
if !conf.Server.Jukebox.Enabled {
log.Debug("Jukebox is DISABLED")
return nil
}
log.Info(ctx, "Starting Jukebox service")
playbackInstance := GetPlaybackServer()
return playbackInstance.Run(ctx)
}
}
// TODO: Implement some struct tags to map flags to viper
func init() {
cobra.OnInitialize(func() {
conf.InitConfig(cfgFile)
@ -67,30 +188,48 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "configfile", "c", "", `config file (default "./navidrome.toml")`)
rootCmd.PersistentFlags().BoolVarP(&noBanner, "nobanner", "n", false, `don't show banner`)
rootCmd.PersistentFlags().String("musicfolder", viper.GetString("musicfolder"), "folder where your music is stored")
rootCmd.PersistentFlags().String("datafolder", viper.GetString("datafolder"), "folder to store application data (DB, cache...), needs write access")
rootCmd.PersistentFlags().String("datafolder", viper.GetString("datafolder"), "folder to store application data (DB), needs write access")
rootCmd.PersistentFlags().String("cachefolder", viper.GetString("cachefolder"), "folder to store cache data (transcoding, images...), needs write access")
rootCmd.PersistentFlags().StringP("loglevel", "l", viper.GetString("loglevel"), "log level, possible values: error, info, debug, trace")
_ = viper.BindPFlag("musicfolder", rootCmd.PersistentFlags().Lookup("musicfolder"))
_ = viper.BindPFlag("datafolder", rootCmd.PersistentFlags().Lookup("datafolder"))
_ = viper.BindPFlag("cachefolder", rootCmd.PersistentFlags().Lookup("cachefolder"))
_ = viper.BindPFlag("loglevel", rootCmd.PersistentFlags().Lookup("loglevel"))
rootCmd.Flags().StringP("address", "a", viper.GetString("address"), "IP address to bind")
rootCmd.Flags().IntP("port", "p", viper.GetInt("port"), "HTTP port Navidrome will use")
rootCmd.Flags().StringP("address", "a", viper.GetString("address"), "IP address to bind to")
rootCmd.Flags().IntP("port", "p", viper.GetInt("port"), "HTTP port Navidrome will listen to")
rootCmd.Flags().String("baseurl", viper.GetString("baseurl"), "base URL to configure Navidrome behind a proxy (ex: /music or http://my.server.com)")
rootCmd.Flags().String("tlscert", viper.GetString("tlscert"), "optional path to a TLS cert file (enables HTTPS listening)")
rootCmd.Flags().String("unixsocketperm", viper.GetString("unixsocketperm"), "optional file permission for the unix socket")
rootCmd.Flags().String("tlskey", viper.GetString("tlskey"), "optional path to a TLS key file (enables HTTPS listening)")
rootCmd.Flags().Duration("sessiontimeout", viper.GetDuration("sessiontimeout"), "how long Navidrome will wait before closing web ui idle sessions")
rootCmd.Flags().Duration("scaninterval", viper.GetDuration("scaninterval"), "how frequently to scan for changes in your music library")
rootCmd.Flags().String("baseurl", viper.GetString("baseurl"), "base URL (only the path part) to configure Navidrome behind a proxy (ex: /music)")
rootCmd.Flags().String("uiloginbackgroundurl", viper.GetString("uiloginbackgroundurl"), "URL to a backaground image used in the Login page")
rootCmd.Flags().Bool("enabletranscodingconfig", viper.GetBool("enabletranscodingconfig"), "enables transcoding configuration in the UI")
rootCmd.Flags().String("transcodingcachesize", viper.GetString("transcodingcachesize"), "size of transcoding cache")
rootCmd.Flags().String("imagecachesize", viper.GetString("imagecachesize"), "size of image (art work) cache. set to 0 to disable cache")
rootCmd.Flags().String("albumplaycountmode", viper.GetString("albumplaycountmode"), "how to compute playcount for albums. absolute (default) or normalized")
rootCmd.Flags().Bool("autoimportplaylists", viper.GetBool("autoimportplaylists"), "enable/disable .m3u playlist auto-import`")
rootCmd.Flags().Bool("prometheus.enabled", viper.GetBool("prometheus.enabled"), "enable/disable prometheus metrics endpoint`")
rootCmd.Flags().String("prometheus.metricspath", viper.GetString("prometheus.metricspath"), "http endpoint for prometheus metrics")
_ = viper.BindPFlag("address", rootCmd.Flags().Lookup("address"))
_ = viper.BindPFlag("port", rootCmd.Flags().Lookup("port"))
_ = viper.BindPFlag("tlscert", rootCmd.Flags().Lookup("tlscert"))
_ = viper.BindPFlag("unixsocketperm", rootCmd.Flags().Lookup("unixsocketperm"))
_ = viper.BindPFlag("tlskey", rootCmd.Flags().Lookup("tlskey"))
_ = viper.BindPFlag("baseurl", rootCmd.Flags().Lookup("baseurl"))
_ = viper.BindPFlag("sessiontimeout", rootCmd.Flags().Lookup("sessiontimeout"))
_ = viper.BindPFlag("scaninterval", rootCmd.Flags().Lookup("scaninterval"))
_ = viper.BindPFlag("baseurl", rootCmd.Flags().Lookup("baseurl"))
_ = viper.BindPFlag("uiloginbackgroundurl", rootCmd.Flags().Lookup("uiloginbackgroundurl"))
_ = viper.BindPFlag("prometheus.enabled", rootCmd.Flags().Lookup("prometheus.enabled"))
_ = viper.BindPFlag("prometheus.metricspath", rootCmd.Flags().Lookup("prometheus.metricspath"))
_ = viper.BindPFlag("enabletranscodingconfig", rootCmd.Flags().Lookup("enabletranscodingconfig"))
_ = viper.BindPFlag("transcodingcachesize", rootCmd.Flags().Lookup("transcodingcachesize"))
_ = viper.BindPFlag("imagecachesize", rootCmd.Flags().Lookup("imagecachesize"))

View File

@ -1,8 +1,9 @@
package cmd
import (
"github.com/deluan/navidrome/conf"
"github.com/deluan/navidrome/log"
"context"
"github.com/navidrome/navidrome/log"
"github.com/spf13/cobra"
)
@ -23,11 +24,8 @@ var scanCmd = &cobra.Command{
}
func runScanner() {
scanner := CreateScanner(conf.Server.MusicFolder)
err := scanner.RescanAll(fullRescan)
if err != nil {
log.Error("Error scanning media folder", "folder", conf.Server.MusicFolder, err)
}
scanner := GetScanner()
_ = scanner.RescanAll(context.Background(), fullRescan)
if fullRescan {
log.Info("Finished full rescan")
} else {

14
cmd/signaller_nounix.go Normal file
View File

@ -0,0 +1,14 @@
//go:build windows || plan9
package cmd
import (
"context"
)
// Windows and Plan9 don't support SIGUSR1, so we don't need to start a signaler
func startSignaller(ctx context.Context) func() error {
return func() error {
return nil
}
}

40
cmd/signaller_unix.go Normal file
View File

@ -0,0 +1,40 @@
//go:build !windows && !plan9
package cmd
import (
"context"
"os"
"os/signal"
"syscall"
"time"
"github.com/navidrome/navidrome/log"
)
const triggerScanSignal = syscall.SIGUSR1
func startSignaller(ctx context.Context) func() error {
log.Info(ctx, "Starting signaler")
scanner := GetScanner()
return func() error {
var sigChan = make(chan os.Signal, 1)
signal.Notify(sigChan, triggerScanSignal)
for {
select {
case sig := <-sigChan:
log.Info(ctx, "Received signal, triggering a new scan", "signal", sig)
start := time.Now()
err := scanner.RescanAll(ctx, false)
if err != nil {
log.Error(ctx, "Error scanning", err)
}
log.Info(ctx, "Triggered scan complete", "elapsed", time.Since(start).Round(100*time.Millisecond))
case <-ctx.Done():
return nil
}
}
}
}

View File

@ -1,59 +1,125 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate wire
//+build !wireinject
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:build !wireinject
// +build !wireinject
package cmd
import (
"github.com/deluan/navidrome/core"
"github.com/deluan/navidrome/core/transcoder"
"github.com/deluan/navidrome/persistence"
"github.com/deluan/navidrome/scanner"
"github.com/deluan/navidrome/server"
"github.com/deluan/navidrome/server/app"
"github.com/deluan/navidrome/server/subsonic"
"github.com/deluan/navidrome/server/subsonic/engine"
"github.com/google/wire"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/core/agents/lastfm"
"github.com/navidrome/navidrome/core/agents/listenbrainz"
"github.com/navidrome/navidrome/core/artwork"
"github.com/navidrome/navidrome/core/ffmpeg"
"github.com/navidrome/navidrome/core/playback"
"github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/db"
"github.com/navidrome/navidrome/persistence"
"github.com/navidrome/navidrome/scanner"
"github.com/navidrome/navidrome/server"
"github.com/navidrome/navidrome/server/events"
"github.com/navidrome/navidrome/server/nativeapi"
"github.com/navidrome/navidrome/server/public"
"github.com/navidrome/navidrome/server/subsonic"
)
// Injectors from wire_injectors.go:
func CreateServer(musicFolder string) *server.Server {
dataStore := persistence.New()
scannerScanner := scanner.New(dataStore)
serverServer := server.New(scannerScanner, dataStore)
dbDB := db.Db()
dataStore := persistence.New(dbDB)
broker := events.GetBroker()
serverServer := server.New(dataStore, broker)
return serverServer
}
func CreateScanner(musicFolder string) *scanner.Scanner {
dataStore := persistence.New()
scannerScanner := scanner.New(dataStore)
return scannerScanner
}
func CreateAppRouter() *app.Router {
dataStore := persistence.New()
router := app.New(dataStore)
func CreateNativeAPIRouter() *nativeapi.Router {
dbDB := db.Db()
dataStore := persistence.New(dbDB)
share := core.NewShare(dataStore)
playlists := core.NewPlaylists(dataStore)
router := nativeapi.New(dataStore, share, playlists)
return router
}
func CreateSubsonicAPIRouter() (*subsonic.Router, error) {
dataStore := persistence.New()
artworkCache := core.NewImageCache()
artwork := core.NewArtwork(dataStore, artworkCache)
nowPlayingRepository := engine.NewNowPlayingRepository()
listGenerator := engine.NewListGenerator(dataStore, nowPlayingRepository)
playlists := engine.NewPlaylists(dataStore)
transcoderTranscoder := transcoder.New()
transcodingCache := core.NewTranscodingCache()
mediaStreamer := core.NewMediaStreamer(dataStore, transcoderTranscoder, transcodingCache)
archiver := core.NewArchiver(dataStore)
players := engine.NewPlayers(dataStore)
router := subsonic.New(artwork, listGenerator, playlists, mediaStreamer, archiver, players, dataStore)
return router, nil
func CreateSubsonicAPIRouter() *subsonic.Router {
dbDB := db.Db()
dataStore := persistence.New(dbDB)
fileCache := artwork.GetImageCache()
fFmpeg := ffmpeg.New()
agentsAgents := agents.New(dataStore)
externalMetadata := core.NewExternalMetadata(dataStore, agentsAgents)
artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, externalMetadata)
transcodingCache := core.GetTranscodingCache()
mediaStreamer := core.NewMediaStreamer(dataStore, fFmpeg, transcodingCache)
share := core.NewShare(dataStore)
archiver := core.NewArchiver(mediaStreamer, dataStore, share)
players := core.NewPlayers(dataStore)
playlists := core.NewPlaylists(dataStore)
cacheWarmer := artwork.NewCacheWarmer(artworkArtwork, fileCache)
broker := events.GetBroker()
scannerScanner := scanner.GetInstance(dataStore, playlists, cacheWarmer, broker)
playTracker := scrobbler.GetPlayTracker(dataStore, broker)
playbackServer := playback.GetInstance(dataStore)
router := subsonic.New(dataStore, artworkArtwork, mediaStreamer, archiver, players, externalMetadata, scannerScanner, broker, playlists, playTracker, share, playbackServer)
return router
}
func CreatePublicRouter() *public.Router {
dbDB := db.Db()
dataStore := persistence.New(dbDB)
fileCache := artwork.GetImageCache()
fFmpeg := ffmpeg.New()
agentsAgents := agents.New(dataStore)
externalMetadata := core.NewExternalMetadata(dataStore, agentsAgents)
artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, externalMetadata)
transcodingCache := core.GetTranscodingCache()
mediaStreamer := core.NewMediaStreamer(dataStore, fFmpeg, transcodingCache)
share := core.NewShare(dataStore)
archiver := core.NewArchiver(mediaStreamer, dataStore, share)
router := public.New(dataStore, artworkArtwork, mediaStreamer, share, archiver)
return router
}
func CreateLastFMRouter() *lastfm.Router {
dbDB := db.Db()
dataStore := persistence.New(dbDB)
router := lastfm.NewRouter(dataStore)
return router
}
func CreateListenBrainzRouter() *listenbrainz.Router {
dbDB := db.Db()
dataStore := persistence.New(dbDB)
router := listenbrainz.NewRouter(dataStore)
return router
}
func GetScanner() scanner.Scanner {
dbDB := db.Db()
dataStore := persistence.New(dbDB)
playlists := core.NewPlaylists(dataStore)
fileCache := artwork.GetImageCache()
fFmpeg := ffmpeg.New()
agentsAgents := agents.New(dataStore)
externalMetadata := core.NewExternalMetadata(dataStore, agentsAgents)
artworkArtwork := artwork.NewArtwork(dataStore, fileCache, fFmpeg, externalMetadata)
cacheWarmer := artwork.NewCacheWarmer(artworkArtwork, fileCache)
broker := events.GetBroker()
scannerScanner := scanner.GetInstance(dataStore, playlists, cacheWarmer, broker)
return scannerScanner
}
func GetPlaybackServer() playback.PlaybackServer {
dbDB := db.Db()
dataStore := persistence.New(dbDB)
playbackServer := playback.GetInstance(dataStore)
return playbackServer
}
// wire_injectors.go:
var allProviders = wire.NewSet(engine.Set, core.Set, scanner.New, subsonic.New, app.New, persistence.New)
var allProviders = wire.NewSet(core.Set, artwork.Set, server.New, subsonic.New, nativeapi.New, public.New, persistence.New, lastfm.NewRouter, listenbrainz.NewRouter, events.GetBroker, scanner.GetInstance, db.Db)

View File

@ -1,44 +1,83 @@
//+build wireinject
//go:build wireinject
package cmd
import (
"github.com/deluan/navidrome/core"
"github.com/deluan/navidrome/persistence"
"github.com/deluan/navidrome/scanner"
"github.com/deluan/navidrome/server"
"github.com/deluan/navidrome/server/app"
"github.com/deluan/navidrome/server/subsonic"
"github.com/deluan/navidrome/server/subsonic/engine"
"github.com/google/wire"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/agents/lastfm"
"github.com/navidrome/navidrome/core/agents/listenbrainz"
"github.com/navidrome/navidrome/core/artwork"
"github.com/navidrome/navidrome/core/playback"
"github.com/navidrome/navidrome/db"
"github.com/navidrome/navidrome/persistence"
"github.com/navidrome/navidrome/scanner"
"github.com/navidrome/navidrome/server"
"github.com/navidrome/navidrome/server/events"
"github.com/navidrome/navidrome/server/nativeapi"
"github.com/navidrome/navidrome/server/public"
"github.com/navidrome/navidrome/server/subsonic"
)
var allProviders = wire.NewSet(
engine.Set,
core.Set,
scanner.New,
artwork.Set,
server.New,
subsonic.New,
app.New,
nativeapi.New,
public.New,
persistence.New,
lastfm.NewRouter,
listenbrainz.NewRouter,
events.GetBroker,
scanner.GetInstance,
db.Db,
)
func CreateServer(musicFolder string) *server.Server {
panic(wire.Build(
server.New,
allProviders,
))
}
func CreateScanner(musicFolder string) *scanner.Scanner {
func CreateNativeAPIRouter() *nativeapi.Router {
panic(wire.Build(
allProviders,
))
}
func CreateAppRouter() *app.Router {
panic(wire.Build(allProviders))
func CreateSubsonicAPIRouter() *subsonic.Router {
panic(wire.Build(
allProviders,
))
}
func CreateSubsonicAPIRouter() (*subsonic.Router, error) {
panic(wire.Build(allProviders))
func CreatePublicRouter() *public.Router {
panic(wire.Build(
allProviders,
))
}
func CreateLastFMRouter() *lastfm.Router {
panic(wire.Build(
allProviders,
))
}
func CreateListenBrainzRouter() *listenbrainz.Router {
panic(wire.Build(
allProviders,
))
}
func GetScanner() scanner.Scanner {
panic(wire.Build(
allProviders,
))
}
func GetPlaybackServer() playback.PlaybackServer {
panic(wire.Build(
allProviders,
))
}

View File

@ -0,0 +1,10 @@
package configtest
import "github.com/navidrome/navidrome/conf"
func SetupConfig() func() {
oldValues := *conf.Server
return func() {
conf.Server = &oldValues
}
}

View File

@ -2,105 +2,380 @@ package conf
import (
"fmt"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"time"
"github.com/deluan/navidrome/consts"
"github.com/deluan/navidrome/log"
"github.com/kr/pretty"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/log"
"github.com/robfig/cron/v3"
"github.com/spf13/viper"
)
type configOptions struct {
ConfigFile string
Address string
Port int
MusicFolder string
DataFolder string
DbPath string
LogLevel string
ScanInterval time.Duration
SessionTimeout time.Duration
BaseURL string
UILoginBackgroundURL string
EnableTranscodingConfig bool
TranscodingCacheSize string
ImageCacheSize string
AutoImportPlaylists bool
ConfigFile string
Address string
Port int
UnixSocketPerm string
MusicFolder string
DataFolder string
CacheFolder string
DbPath string
LogLevel string
ScanInterval time.Duration
ScanSchedule string
SessionTimeout time.Duration
BaseURL string
BasePath string
BaseHost string
BaseScheme string
TLSCert string
TLSKey string
UILoginBackgroundURL string
UIWelcomeMessage string
MaxSidebarPlaylists int
EnableTranscodingConfig bool
EnableDownloads bool
EnableExternalServices bool
EnableMediaFileCoverArt bool
TranscodingCacheSize string
ImageCacheSize string
AlbumPlayCountMode string
EnableArtworkPrecache bool
AutoImportPlaylists bool
PlaylistsPath string
AutoTranscodeDownload bool
DefaultDownsamplingFormat string
SearchFullString bool
RecentlyAddedByModTime bool
PreferSortTags bool
IgnoredArticles string
IndexGroups string
SubsonicArtistParticipations bool
FFmpegPath string
MPVPath string
MPVCmdTemplate string
CoverArtPriority string
CoverJpegQuality int
ArtistArtPriority string
EnableGravatar bool
EnableFavourites bool
EnableStarRating bool
EnableUserEditing bool
EnableSharing bool
DefaultDownloadableShare bool
DefaultTheme string
DefaultLanguage string
DefaultUIVolume int
EnableReplayGain bool
EnableCoverAnimation bool
GATrackingID string
EnableLogRedacting bool
AuthRequestLimit int
AuthWindowLength time.Duration
PasswordEncryptionKey string
ReverseProxyUserHeader string
ReverseProxyWhitelist string
HTTPSecurityHeaders secureOptions
Prometheus prometheusOptions
Scanner scannerOptions
Jukebox jukeboxOptions
IgnoredArticles string
IndexGroups string
ProbeCommand string
CoverArtPriority string
CoverJpegQuality int
UIWelcomeMessage string
GATrackingID string
AuthRequestLimit int
AuthWindowLength time.Duration
Agents string
LastFM lastfmOptions
Spotify spotifyOptions
ListenBrainz listenBrainzOptions
// DevFlags. These are used to enable/disable debugging and incomplete features
DevLogSourceLine bool
DevAutoCreateAdminPassword string
DevLogSourceLine bool
DevLogLevels map[string]string
DevEnableProfiler bool
DevAutoCreateAdminPassword string
DevAutoLoginUsername string
DevActivityPanel bool
DevSidebarPlaylists bool
DevEnableBufferedScrobble bool
DevShowArtistPage bool
DevOffsetOptimize int
DevArtworkMaxRequests int
DevArtworkThrottleBacklogLimit int
DevArtworkThrottleBacklogTimeout time.Duration
DevArtistInfoTimeToLive time.Duration
DevAlbumInfoTimeToLive time.Duration
}
var Server = &configOptions{}
type scannerOptions struct {
Extractor string
GenreSeparators string
GroupAlbumReleases bool
}
type lastfmOptions struct {
Enabled bool
ApiKey string
Secret string
Language string
}
type spotifyOptions struct {
ID string
Secret string
}
type listenBrainzOptions struct {
Enabled bool
BaseURL string
}
type secureOptions struct {
CustomFrameOptionsValue string
}
type prometheusOptions struct {
Enabled bool
MetricsPath string
}
type AudioDeviceDefinition []string
type jukeboxOptions struct {
Enabled bool
Devices []AudioDeviceDefinition
Default string
AdminOnly bool
}
var (
Server = &configOptions{}
hooks []func()
)
func LoadFromFile(confFile string) {
viper.SetConfigFile(confFile)
err := viper.ReadInConfig()
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "FATAL: Error reading config file:", err)
os.Exit(1)
}
Load()
}
func Load() {
err := viper.Unmarshal(&Server)
if err != nil {
fmt.Println("Error parsing config:", err)
_, _ = fmt.Fprintln(os.Stderr, "FATAL: Error parsing config:", err)
os.Exit(1)
}
err = os.MkdirAll(Server.DataFolder, os.ModePerm)
if err != nil {
fmt.Println("Error creating data path:", "path", Server.DataFolder, err)
_, _ = fmt.Fprintln(os.Stderr, "FATAL: Error creating data path:", "path", Server.DataFolder, err)
os.Exit(1)
}
if Server.CacheFolder == "" {
Server.CacheFolder = filepath.Join(Server.DataFolder, "cache")
}
err = os.MkdirAll(Server.CacheFolder, os.ModePerm)
if err != nil {
_, _ = fmt.Fprintln(os.Stderr, "FATAL: Error creating cache path:", "path", Server.CacheFolder, err)
os.Exit(1)
}
Server.ConfigFile = viper.GetViper().ConfigFileUsed()
if Server.DbPath == "" {
Server.DbPath = filepath.Join(Server.DataFolder, consts.DefaultDbPath)
}
log.SetLevelString(Server.LogLevel)
log.SetLogLevels(Server.DevLogLevels)
log.SetLogSourceLine(Server.DevLogSourceLine)
log.Debug("Loaded configuration", "file", Server.ConfigFile, "config", fmt.Sprintf("%#v", Server))
log.SetRedacting(Server.EnableLogRedacting)
if err := validateScanSchedule(); err != nil {
os.Exit(1)
}
if Server.BaseURL != "" {
u, err := url.Parse(Server.BaseURL)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "FATAL: Invalid BaseURL %s: %s\n", Server.BaseURL, err.Error())
os.Exit(1)
}
Server.BasePath = u.Path
u.Path = ""
u.RawQuery = ""
Server.BaseHost = u.Host
Server.BaseScheme = u.Scheme
}
// Print current configuration if log level is Debug
if log.IsGreaterOrEqualTo(log.LevelDebug) {
prettyConf := pretty.Sprintf("Loaded configuration from '%s': %# v", Server.ConfigFile, Server)
if Server.EnableLogRedacting {
prettyConf = log.Redact(prettyConf)
}
_, _ = fmt.Fprintln(os.Stderr, prettyConf)
}
if !Server.EnableExternalServices {
disableExternalServices()
}
// Call init hooks
for _, hook := range hooks {
hook()
}
}
func disableExternalServices() {
log.Info("All external integrations are DISABLED!")
Server.LastFM.Enabled = false
Server.Spotify.ID = ""
Server.ListenBrainz.Enabled = false
Server.Agents = ""
if Server.UILoginBackgroundURL == consts.DefaultUILoginBackgroundURL {
Server.UILoginBackgroundURL = consts.DefaultUILoginBackgroundURLOffline
}
}
func validateScanSchedule() error {
if Server.ScanInterval != -1 {
log.Warn("ScanInterval is DEPRECATED. Please use ScanSchedule. See docs at https://navidrome.org/docs/usage/configuration-options/")
if Server.ScanSchedule != "@every 1m" {
log.Error("You cannot specify both ScanInterval and ScanSchedule, ignoring ScanInterval")
} else {
if Server.ScanInterval == 0 {
Server.ScanSchedule = ""
} else {
Server.ScanSchedule = fmt.Sprintf("@every %s", Server.ScanInterval)
}
log.Warn("Setting ScanSchedule", "schedule", Server.ScanSchedule)
}
}
if Server.ScanSchedule == "0" || Server.ScanSchedule == "" {
Server.ScanSchedule = ""
return nil
}
if _, err := time.ParseDuration(Server.ScanSchedule); err == nil {
Server.ScanSchedule = "@every " + Server.ScanSchedule
}
c := cron.New()
_, err := c.AddFunc(Server.ScanSchedule, func() {})
if err != nil {
log.Error("Invalid ScanSchedule. Please read format spec at https://pkg.go.dev/github.com/robfig/cron#hdr-CRON_Expression_Format", "schedule", Server.ScanSchedule, err)
}
return err
}
// AddHook is used to register initialization code that should run as soon as the config is loaded
func AddHook(hook func()) {
hooks = append(hooks, hook)
}
func init() {
viper.SetDefault("musicfolder", filepath.Join(".", "music"))
viper.SetDefault("cachefolder", "")
viper.SetDefault("datafolder", ".")
viper.SetDefault("loglevel", "info")
viper.SetDefault("address", "0.0.0.0")
viper.SetDefault("port", 4533)
viper.SetDefault("unixsocketperm", "0660")
viper.SetDefault("sessiontimeout", consts.DefaultSessionTimeout)
viper.SetDefault("scaninterval", time.Minute)
viper.SetDefault("scaninterval", -1)
viper.SetDefault("scanschedule", "@every 1m")
viper.SetDefault("baseurl", "")
viper.SetDefault("uiloginbackgroundurl", "https://source.unsplash.com/random/1600x900?music")
viper.SetDefault("tlscert", "")
viper.SetDefault("tlskey", "")
viper.SetDefault("uiloginbackgroundurl", consts.DefaultUILoginBackgroundURL)
viper.SetDefault("uiwelcomemessage", "")
viper.SetDefault("maxsidebarplaylists", consts.DefaultMaxSidebarPlaylists)
viper.SetDefault("enabletranscodingconfig", false)
viper.SetDefault("transcodingcachesize", "100MB")
viper.SetDefault("imagecachesize", "100MB")
viper.SetDefault("albumplaycountmode", consts.AlbumPlayCountModeAbsolute)
viper.SetDefault("enableartworkprecache", true)
viper.SetDefault("autoimportplaylists", true)
// Config options only valid for file/env configuration
viper.SetDefault("playlistspath", consts.DefaultPlaylistsPath)
viper.SetDefault("enabledownloads", true)
viper.SetDefault("enableexternalservices", true)
viper.SetDefault("enablemediafilecoverart", true)
viper.SetDefault("autotranscodedownload", false)
viper.SetDefault("defaultdownsamplingformat", consts.DefaultDownsamplingFormat)
viper.SetDefault("searchfullstring", false)
viper.SetDefault("recentlyaddedbymodtime", false)
viper.SetDefault("prefersorttags", false)
viper.SetDefault("ignoredarticles", "The El La Los Las Le Les Os As O A")
viper.SetDefault("indexgroups", "A B C D E F G H I J K L M N O P Q R S T U V W X-Z(XYZ) [Unknown]([)")
viper.SetDefault("probecommand", "ffmpeg %s -f ffmetadata")
viper.SetDefault("coverartpriority", "embedded, cover.*, folder.*, front.*")
viper.SetDefault("subsonicartistparticipations", false)
viper.SetDefault("ffmpegpath", "")
viper.SetDefault("mpvcmdtemplate", "mpv --audio-device=%d --no-audio-display --pause %f --input-ipc-server=%s")
viper.SetDefault("coverartpriority", "cover.*, folder.*, front.*, embedded, external")
viper.SetDefault("coverjpegquality", 75)
viper.SetDefault("uiwelcomemessage", "")
viper.SetDefault("artistartpriority", "artist.*, album/artist.*, external")
viper.SetDefault("enablegravatar", false)
viper.SetDefault("enablefavourites", true)
viper.SetDefault("enablestarrating", true)
viper.SetDefault("enableuserediting", true)
viper.SetDefault("defaulttheme", "Dark")
viper.SetDefault("defaultlanguage", "")
viper.SetDefault("defaultuivolume", consts.DefaultUIVolume)
viper.SetDefault("enablereplaygain", true)
viper.SetDefault("enablecoveranimation", true)
viper.SetDefault("gatrackingid", "")
viper.SetDefault("enablelogredacting", true)
viper.SetDefault("authrequestlimit", 5)
viper.SetDefault("authwindowlength", 20*time.Second)
viper.SetDefault("passwordencryptionkey", "")
viper.SetDefault("reverseproxyuserheader", "Remote-User")
viper.SetDefault("reverseproxywhitelist", "")
viper.SetDefault("prometheus.enabled", false)
viper.SetDefault("prometheus.metricspath", "/metrics")
viper.SetDefault("jukebox.enabled", false)
viper.SetDefault("jukebox.devices", []AudioDeviceDefinition{})
viper.SetDefault("jukebox.default", "")
viper.SetDefault("jukebox.adminonly", true)
viper.SetDefault("scanner.extractor", consts.DefaultScannerExtractor)
viper.SetDefault("scanner.genreseparators", ";/,")
viper.SetDefault("scanner.groupalbumreleases", false)
viper.SetDefault("agents", "lastfm,spotify")
viper.SetDefault("lastfm.enabled", true)
viper.SetDefault("lastfm.language", "en")
viper.SetDefault("lastfm.apikey", "")
viper.SetDefault("lastfm.secret", "")
viper.SetDefault("spotify.id", "")
viper.SetDefault("spotify.secret", "")
viper.SetDefault("listenbrainz.enabled", true)
viper.SetDefault("listenbrainz.baseurl", "https://api.listenbrainz.org/1/")
viper.SetDefault("httpsecurityheaders.customframeoptionsvalue", "DENY")
// DevFlags. These are used to enable/disable debugging and incomplete features
viper.SetDefault("devlogsourceline", false)
viper.SetDefault("devenableprofiler", false)
viper.SetDefault("devautocreateadminpassword", "")
viper.SetDefault("devoldscanner", false)
viper.SetDefault("devautologinusername", "")
viper.SetDefault("devactivitypanel", true)
viper.SetDefault("enablesharing", false)
viper.SetDefault("defaultdownloadableshare", false)
viper.SetDefault("devenablebufferedscrobble", true)
viper.SetDefault("devsidebarplaylists", true)
viper.SetDefault("devshowartistpage", true)
viper.SetDefault("devoffsetoptimize", 50000)
viper.SetDefault("devartworkmaxrequests", max(2, runtime.NumCPU()/3))
viper.SetDefault("devartworkthrottlebackloglimit", consts.RequestThrottleBacklogLimit)
viper.SetDefault("devartworkthrottlebacklogtimeout", consts.RequestThrottleBacklogTimeout)
viper.SetDefault("devartistinfotimetolive", consts.ArtistInfoTimeToLive)
viper.SetDefault("devalbuminfotimetolive", consts.AlbumInfoTimeToLive)
}
func InitConfig(cfgFile string) {
@ -116,11 +391,13 @@ func InitConfig(cfgFile string) {
_ = viper.BindEnv("port")
viper.SetEnvPrefix("ND")
replacer := strings.NewReplacer(".", "_")
viper.SetEnvKeyReplacer(replacer)
viper.AutomaticEnv()
err := viper.ReadInConfig()
if cfgFile != "" && err != nil {
fmt.Println("Navidrome could not open config file: ", err)
if viper.ConfigFileUsed() != "" && err != nil {
_, _ = fmt.Fprintln(os.Stderr, "FATAL: Navidrome could not open config file: ", err)
os.Exit(1)
}
}

47
conf/mime/mime_types.go Normal file
View File

@ -0,0 +1,47 @@
package mime
import (
"mime"
"strings"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/resources"
"gopkg.in/yaml.v3"
)
type mimeConf struct {
Types map[string]string `yaml:"types"`
Lossless []string `yaml:"lossless"`
}
var LosslessFormats []string
func initMimeTypes() {
// In some circumstances, Windows sets JS mime-type to `text/plain`!
_ = mime.AddExtensionType(".js", "text/javascript")
_ = mime.AddExtensionType(".css", "text/css")
f, err := resources.FS().Open("mime_types.yaml")
if err != nil {
log.Fatal("Fatal error opening mime_types.yaml", err)
}
defer f.Close()
var mimeConf mimeConf
err = yaml.NewDecoder(f).Decode(&mimeConf)
if err != nil {
log.Fatal("Fatal error parsing mime_types.yaml", err)
}
for ext, typ := range mimeConf.Types {
_ = mime.AddExtensionType(ext, typ)
}
for _, ext := range mimeConf.Lossless {
LosslessFormats = append(LosslessFormats, strings.TrimPrefix(ext, "."))
}
}
func init() {
conf.AddHook(initMimeTypes)
}

View File

@ -1,20 +0,0 @@
package consts
import (
"fmt"
"strings"
"unicode"
"github.com/deluan/navidrome/resources"
)
func getBanner() string {
data, _ := resources.Asset("banner.txt")
return strings.TrimRightFunc(string(data), unicode.IsSpace)
}
func Banner() string {
version := "Version: " + Version()
padding := strings.Repeat(" ", 52-len(version))
return fmt.Sprintf("%s\n%s%s\n", getBanner(), padding, version)
}

View File

@ -3,6 +3,7 @@ package consts
import (
"crypto/md5"
"fmt"
"path/filepath"
"strings"
"time"
)
@ -10,60 +11,119 @@ import (
const (
AppName = "navidrome"
DefaultDbPath = "navidrome.db?cache=shared&_busy_timeout=15000&_journal_mode=WAL&_foreign_keys=on"
DefaultDbPath = "navidrome.db?cache=shared&_cache_size=1000000000&_busy_timeout=5000&_journal_mode=WAL&_synchronous=NORMAL&_foreign_keys=on&_txlock=immediate"
InitialSetupFlagKey = "InitialSetup"
UIAuthorizationHeader = "X-ND-Authorization"
JWTSecretKey = "JWTSecret"
JWTIssuer = "ND"
DefaultSessionTimeout = 24 * time.Hour
UIAuthorizationHeader = "X-ND-Authorization"
UIClientUniqueIDHeader = "X-ND-Client-Unique-Id"
JWTSecretKey = "JWTSecret"
JWTIssuer = "ND"
DefaultSessionTimeout = 24 * time.Hour
CookieExpiry = 365 * 24 * 3600 // One year
// DefaultEncryptionKey This is the encryption key used if none is specified in the `PasswordEncryptionKey` option
// Never ever change this! Or it will break all Navidrome installations that don't set the config option
DefaultEncryptionKey = "just for obfuscation"
PasswordsEncryptedKey = "PasswordsEncryptedKey"
PasswordAutogenPrefix = "__NAVIDROME_AUTOGEN__" //nolint:gosec
DevInitialUserName = "admin"
DevInitialName = "Dev Admin"
URLPathUI = "/app"
URLPathSubsonicAPI = "/rest"
URLPathUI = "/app"
URLPathNativeAPI = "/api"
URLPathSubsonicAPI = "/rest"
URLPathPublic = "/share"
URLPathPublicImages = URLPathPublic + "/img"
// DefaultUILoginBackgroundURL uses Navidrome curated background images collection,
// available at https://unsplash.com/collections/20072696/navidrome
DefaultUILoginBackgroundURL = "/backgrounds"
// DefaultUILoginBackgroundOffline Background image used in case external integrations are disabled
DefaultUILoginBackgroundOffline = "iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAIAAAAiOjnJAAAABGdBTUEAALGPC/xhBQAAAiJJREFUeF7t0IEAAAAAw6D5Ux/khVBhwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDBgwIABAwYMGDDwMDDVlwABBWcSrQAAAABJRU5ErkJggg=="
DefaultUILoginBackgroundURLOffline = "data:image/png;base64," + DefaultUILoginBackgroundOffline
DefaultMaxSidebarPlaylists = 100
RequestThrottleBacklogLimit = 100
RequestThrottleBacklogTimeout = time.Minute
ServerReadHeaderTimeout = 3 * time.Second
ArtistInfoTimeToLive = 24 * time.Hour
AlbumInfoTimeToLive = 7 * 24 * time.Hour
I18nFolder = "i18n"
SkipScanFile = ".ndignore"
PlaceholderAlbumArt = "navidrome-600x600.png"
PlaceholderArtistArt = "artist-placeholder.webp"
PlaceholderAlbumArt = "placeholder.png"
PlaceholderAvatar = "logo-192x192.png"
UICoverArtSize = 300
DefaultUIVolume = 100
DefaultHttpClientTimeOut = 10 * time.Second
DefaultScannerExtractor = "taglib"
Zwsp = string('\u200b')
)
// Cache options
const (
TranscodingCacheDir = "cache/transcoding"
TranscodingCacheDir = "transcoding"
DefaultTranscodingCacheMaxItems = 0 // Unlimited
ImageCacheDir = "cache/images"
ImageCacheDir = "images"
DefaultImageCacheMaxItems = 0 // Unlimited
DefaultCacheSize = 100 * 1024 * 1024 // 100MB
DefaultCacheCleanUpInterval = 10 * time.Minute
)
var (
DefaultTranscodings = []map[string]interface{}{
{
"name": "mp3 audio",
"targetFormat": "mp3",
"defaultBitRate": 192,
"command": "ffmpeg -i %s -map 0:0 -b:a %bk -v 0 -f mp3 -",
},
{
"name": "opus audio",
"targetFormat": "oga",
"defaultBitRate": 128,
"command": "ffmpeg -i %s -map 0:0 -b:a %bk -v 0 -c:a libopus -f opus -",
},
}
const (
AlbumPlayCountModeAbsolute = "absolute"
AlbumPlayCountModeNormalized = "normalized"
)
var (
VariousArtists = "Various Artists"
VariousArtistsID = fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(VariousArtists))))
UnknownArtist = "[Unknown Artist]"
DefaultDownsamplingFormat = "opus"
DefaultTranscodings = []struct {
Name string
TargetFormat string
DefaultBitRate int
Command string
}{
{
Name: "mp3 audio",
TargetFormat: "mp3",
DefaultBitRate: 192,
Command: "ffmpeg -i %s -ss %t -map 0:a:0 -b:a %bk -v 0 -f mp3 -",
},
{
Name: "opus audio",
TargetFormat: "opus",
DefaultBitRate: 128,
Command: "ffmpeg -i %s -ss %t -map 0:a:0 -b:a %bk -v 0 -c:a libopus -f opus -",
},
{
Name: "aac audio",
TargetFormat: "aac",
DefaultBitRate: 256,
Command: "ffmpeg -i %s -ss %t -map 0:a:0 -b:a %bk -v 0 -c:a aac -f adts -",
},
}
DefaultPlaylistsPath = strings.Join([]string{".", "**/**"}, string(filepath.ListSeparator))
)
var (
VariousArtists = "Various Artists"
VariousArtistsID = fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(VariousArtists))))
UnknownAlbum = "[Unknown Album]"
UnknownArtist = "[Unknown Artist]"
UnknownArtistID = fmt.Sprintf("%x", md5.Sum([]byte(strings.ToLower(UnknownArtist))))
VariousArtistsMbzId = "89ad4ac3-39f7-470e-963a-56509c546377"
ServerStart = time.Now()
)

View File

@ -1,35 +0,0 @@
package consts
import "mime"
func init() {
mt := map[string]string{
".mp3": "audio/mpeg",
".ogg": "audio/ogg",
".oga": "audio/ogg",
".opus": "audio/ogg",
".aac": "audio/mp4",
".m4a": "audio/mp4",
".m4b": "audio/mp4",
".flac": "audio/flac",
".wav": "audio/x-wav",
".wma": "audio/x-ms-wma",
".ape": "audio/x-monkeys-audio",
".mpc": "audio/x-musepack",
".shn": "audio/x-shn",
".aif": "audio/x-aiff",
".aiff": "audio/x-aiff",
".m3u": "audio/x-mpegurl",
".pls": "audio/x-scpls",
".dsf": "audio/dsd",
".gif": "image/gif",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".png": "image/png",
".bmp": "image/bmp",
}
for ext, typ := range mt {
_ = mime.AddExtensionType(ext, typ)
}
}

View File

@ -11,15 +11,16 @@ var (
gitSha string
)
// Formats:
// Version holds the version string, with tag and git sha info.
// Examples:
// dev
// v0.2.0 (5b84188)
// v0.3.2-SNAPSHOT (715f552)
// master (9ed35cb)
func Version() string {
var Version = func() string {
if gitSha == "" {
return "dev"
}
gitTag = strings.TrimPrefix(gitTag, "v")
return fmt.Sprintf("%s (%s)", gitTag, gitSha)
}
}()

View File

@ -0,0 +1,7 @@
https://your.website {
reverse_proxy * navidrome:4533 {
header_up Host {http.reverse_proxy.upstream.hostport}
header_up X-Forwarded-For {http.request.remote}
header_up X-Real-IP {http.reverse_proxy.upstream.port}
}
}

View File

@ -0,0 +1,31 @@
version: '3.6'
volumes:
caddy_data:
navidrome_data:
services:
caddy:
container_name: "caddy"
image: caddy:2.6-alpine
restart: unless-stopped
read_only: true
volumes:
- "caddy_data:/data:rw"
- "./Caddyfile:/etc/caddy/Caddyfile:ro"
ports:
- "80:80"
- "443:443"
navidrome:
container_name: "navidrome"
image: deluan/navidrome:latest
restart: unless-stopped
read_only: true
# user: 1000:1000
ports:
- "4533:4533"
volumes:
- "navidrome_data:/data"
#- "/mnt/music:/music:ro"

View File

@ -0,0 +1,51 @@
version: "3.6"
volumes:
traefik_data:
navidrome_data:
services:
traefik:
container_name: "traefik"
image: traefik:2.9
restart: unless-stopped
read_only: true
command:
- "--log.level=ERROR"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.tc.acme.tlschallenge=true"
#- "--certificatesresolvers.tc.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
- "--certificatesresolvers.tc.acme.email=foo@foo.com"
- "--certificatesresolvers.tc.acme.storage=/letsencrypt/acme.json"
ports:
- "443:443"
volumes:
- "traefik_data:/letsencrypt"
#- "/var/run/docker.sock:/var/run/docker.sock:ro"
navidrome:
container_name: "navidrome"
image: deluan/navidrome:latest
restart: unless-stopped
read_only: true
# user: 1000:1000
ports:
- "4533:4533"
environment:
ND_SCANINTERVAL: 6h
ND_LOGLEVEL: info
ND_SESSIONTIMEOUT: 168h
ND_BASEURL: ""
volumes:
- "navidrome_data:/data"
#- "/mnt/music:/music:ro"
labels:
- "traefik.enable=true"
- "traefik.http.routers.navidrome.rule=Host(`foo.com`)"
- "traefik.http.routers.navidrome.entrypoints=websecure"
- "traefik.http.routers.navidrome.tls=true"
- "traefik.http.routers.navidrome.tls.certresolver=tc"
- "traefik.http.services.navidrome.loadbalancer.server.port=4533"

View File

@ -0,0 +1,18 @@
version: '3.6'
volumes:
navidrome_data:
services:
navidrome:
container_name: "navidrome"
image: deluan/navidrome:latest
restart: unless-stopped
read_only: true
# user: 1000:1000
ports:
- "4533:4533"
volumes:
- "navidrome_data:/data"
#- "/mnt/music:/music:ro"

52
contrib/freebsd_rc Normal file
View File

@ -0,0 +1,52 @@
#!/bin/sh
#
# $FreeBSD: $
#
# PROVIDE: navidrome
# REQUIRE: NETWORKING
# KEYWORD:
#
# Add the following lines to /etc/rc.conf to enable navidrome:
# navidrome_enable="YES"
#
# navidrome_enable (bool): Set to YES to enable navidrome
# Default: NO
# navidrome_config (str): navidrome configuration file
# Default: /usr/local/etc/navidrome/config.toml
# navidrome_datafolder (str): navidrome Folder to store application data
# Default: www
# navidrome_user (str): navidrome daemon user
# Default: www
# navidrome_group (str): navidrome daemon group
# Default: www
. /etc/rc.subr
name="navidrome"
rcvar="navidrome_enable"
load_rc_config $name
: ${navidrome_user:="www"}
: ${navidrome_group:="www"}
: ${navidrome_enable:="NO"}
: ${navidrome_config:="/usr/local/etc/navidrome/config.toml"}
: ${navidrome_flags=""}
: ${navidrome_facility:="daemon"}
: ${navidrome_priority:="debug"}
: ${navidrome_datafolder:="/var/db/${name}"}
required_dirs=${navidrome_datafolder}
required_files=${navidrome_config}
procname="/usr/local/bin/${name}"
pidfile="/var/run/${name}.pid"
start_precmd="${name}_precmd"
command=/usr/sbin/daemon
command_args="-S -l ${navidrome_facility} -s ${navidrome_priority} -T ${name} -t ${name} -p ${pidfile} \
${procname} --configfile ${navidrome_config} --datafolder ${navidrome_datafolder} ${navidrome_flags}"
navidrome_precmd()
{
install -o ${navidrome_user} /dev/null ${pidfile}
}
run_rc_command "$1"

11
contrib/k8s/README.md Normal file
View File

@ -0,0 +1,11 @@
# Kubernetes
A couple things to keep in mind with this manifest:
1. This creates a namespace called `navidrome`. Adjust this as needed.
1. This manifest was created on [K3s](https://github.com/k3s-io/k3s), which uses its own storage provisioner called [local-path-provisioner](https://github.com/rancher/local-path-provisioner). Be sure to change the `storageClassName` of the `PersistentVolumeClaim` as needed.
1. The `PersistentVolumeClaim` sets up a 2Gi volume for Navidrome's database. Adjust this as needed.
1. Be sure to change the `image` tag from `ghcr.io/navidrome/navidrome:0.49.3` to whatever the newest version is.
1. This assumes your music is mounted on the host using `hostPath` at `/path/to/your/music/on/the/host`. Adjust this as needed.
1. The `Ingress` is already configured for `cert-manager` to obtain a Let's Encrypt TLS certificate and uses Traefik for routing. Adjust this as needed.
1. The `Ingress` presents the service at `navidrome.${SECRET_INTERNAL_DOMAIN_NAME}`, which needs to already be setup in DNS.

111
contrib/k8s/manifest.yml Normal file
View File

@ -0,0 +1,111 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: navidrome
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: navidrome-data-pvc
namespace: navidrome
annotations:
volumeType: local
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
storageClassName: local-path
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: navidrome-deployment
namespace: navidrome
spec:
replicas: 1
revisionHistoryLimit: 2
selector:
matchLabels:
app: navidrome
template:
metadata:
labels:
app: navidrome
spec:
containers:
- name: navidrome
image: ghcr.io/navidrome/navidrome:0.49.3
ports:
- containerPort: 4533
env:
- name: ND_SCANSCHEDULE
value: "12h"
- name: ND_SESSIONTIMEOUT
value: "24h"
- name: ND_LOGLEVEL
value: "info"
- name: ND_ENABLETRANSCODINGCONFIG
value: "false"
- name: ND_TRANSCODINGCACHESIZE
value: "512MB"
- name: ND_ENABLESTARRATING
value: "false"
- name: ND_ENABLEFAVOURITES
value: "false"
volumeMounts:
- name: data
mountPath: /data
- name: music
mountPath: /music
readOnly: true
volumes:
- name: data
persistentVolumeClaim:
claimName: navidrome-data-pvc
- name: music
hostPath:
path: /path/to/your/music/on/the/host
type: Directory
---
apiVersion: v1
kind: Service
metadata:
name: navidrome-service
namespace: navidrome
spec:
type: ClusterIP
ports:
- name: http
targetPort: 4533
port: 4533
protocol: TCP
selector:
app: navidrome
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: navidrome-ingress
namespace: navidrome
annotations:
cert-manager.io/cluster-issuer: letsencrypt-production
traefik.ingress.kubernetes.io/router.tls: "true"
spec:
rules:
- host: navidrome.${SECRET_INTERNAL_DOMAIN_NAME}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: navidrome-service
port:
number: 4533
tls:
- hosts:
- navidrome.${SECRET_INTERNAL_DOMAIN_NAME}
secretName: navidrome-tls

View File

@ -3,7 +3,6 @@
[Unit]
Description=Navidrome Music Server and Streamer compatible with Subsonic/Airsonic
After=remote-fs.target network.target
AssertPathExists=/var/lib/navidrome
[Install]
WantedBy=multi-user.target
@ -13,6 +12,7 @@ User=navidrome
Group=navidrome
Type=simple
ExecStart=/usr/bin/navidrome
StateDirectory=navidrome
WorkingDirectory=/var/lib/navidrome
TimeoutStopSec=20
KillMode=process
@ -21,18 +21,26 @@ Restart=on-failure
EnvironmentFile=-/etc/sysconfig/navidrome
# See https://www.freedesktop.org/software/systemd/man/systemd.exec.html
CapabilityBoundingSet=
DevicePolicy=closed
NoNewPrivileges=yes
LockPersonality=yes
PrivateTmp=yes
PrivateUsers=yes
ProtectControlGroups=yes
ProtectKernelModules=yes
ProtectKernelTunables=yes
ProtectClock=yes
ProtectHostname=yes
ProtectKernelLogs=yes
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictNamespaces=yes
RestrictRealtime=yes
SystemCallFilter=~@clock @debug @module @mount @obsolete @privileged @reboot @setuid @swap
ReadWritePaths=/var/lib/navidrome
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
SystemCallFilter=setrlimit
SystemCallArchitectures=native
UMask=0066
# You can uncomment the following line if you're not using the jukebox This
# will prevent navidrome from accessing any real (physical) devices

12
core/agents/README.md Normal file
View File

@ -0,0 +1,12 @@
This folder abstracts metadata lookup into "agents". Each agent can be implemented to get as
much info as the external source provides, by using a granular set of interfaces
(see [interfaces](interfaces.go)).
A new agent must comply with these simple implementation rules:
1) Implement the `AgentName()` method. It just returns the name of the agent for logging purposes.
2) Implement one or more of the `*Retriever()` interfaces. That's where the agent's logic resides.
3) Register itself (in its `init()` function).
For an agent to be used it needs to be listed in the `Agents` config option (default is `"lastfm,spotify"`). The order dictates the priority of the agents
For a simple Agent example, look at the [local_agent](local_agent.go) agent source code.

228
core/agents/agents.go Normal file
View File

@ -0,0 +1,228 @@
package agents
import (
"context"
"strings"
"time"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils"
)
type Agents struct {
ds model.DataStore
agents []Interface
}
func New(ds model.DataStore) *Agents {
var order []string
if conf.Server.Agents != "" {
order = strings.Split(conf.Server.Agents, ",")
}
order = append(order, LocalAgentName)
var res []Interface
for _, name := range order {
init, ok := Map[name]
if !ok {
log.Error("Agent not available. Check configuration", "name", name)
continue
}
res = append(res, init(ds))
}
return &Agents{ds: ds, agents: res}
}
func (a *Agents) AgentName() string {
return "agents"
}
func (a *Agents) GetArtistMBID(ctx context.Context, id string, name string) (string, error) {
switch id {
case consts.UnknownArtistID:
return "", ErrNotFound
case consts.VariousArtistsID:
return "", nil
}
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistMBIDRetriever)
if !ok {
continue
}
mbid, err := agent.GetArtistMBID(ctx, id, name)
if mbid != "" && err == nil {
log.Debug(ctx, "Got MBID", "agent", ag.AgentName(), "artist", name, "mbid", mbid, "elapsed", time.Since(start))
return mbid, nil
}
}
return "", ErrNotFound
}
func (a *Agents) GetArtistURL(ctx context.Context, id, name, mbid string) (string, error) {
switch id {
case consts.UnknownArtistID:
return "", ErrNotFound
case consts.VariousArtistsID:
return "", nil
}
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistURLRetriever)
if !ok {
continue
}
url, err := agent.GetArtistURL(ctx, id, name, mbid)
if url != "" && err == nil {
log.Debug(ctx, "Got External Url", "agent", ag.AgentName(), "artist", name, "url", url, "elapsed", time.Since(start))
return url, nil
}
}
return "", ErrNotFound
}
func (a *Agents) GetArtistBiography(ctx context.Context, id, name, mbid string) (string, error) {
switch id {
case consts.UnknownArtistID:
return "", ErrNotFound
case consts.VariousArtistsID:
return "", nil
}
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistBiographyRetriever)
if !ok {
continue
}
bio, err := agent.GetArtistBiography(ctx, id, name, mbid)
if err == nil {
log.Debug(ctx, "Got Biography", "agent", ag.AgentName(), "artist", name, "len", len(bio), "elapsed", time.Since(start))
return bio, nil
}
}
return "", ErrNotFound
}
func (a *Agents) GetSimilarArtists(ctx context.Context, id, name, mbid string, limit int) ([]Artist, error) {
switch id {
case consts.UnknownArtistID:
return nil, ErrNotFound
case consts.VariousArtistsID:
return nil, nil
}
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistSimilarRetriever)
if !ok {
continue
}
similar, err := agent.GetSimilarArtists(ctx, id, name, mbid, limit)
if len(similar) > 0 && err == nil {
if log.IsGreaterOrEqualTo(log.LevelTrace) {
log.Debug(ctx, "Got Similar Artists", "agent", ag.AgentName(), "artist", name, "similar", similar, "elapsed", time.Since(start))
} else {
log.Debug(ctx, "Got Similar Artists", "agent", ag.AgentName(), "artist", name, "similarReceived", len(similar), "elapsed", time.Since(start))
}
return similar, err
}
}
return nil, ErrNotFound
}
func (a *Agents) GetArtistImages(ctx context.Context, id, name, mbid string) ([]ExternalImage, error) {
switch id {
case consts.UnknownArtistID:
return nil, ErrNotFound
case consts.VariousArtistsID:
return nil, nil
}
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistImageRetriever)
if !ok {
continue
}
images, err := agent.GetArtistImages(ctx, id, name, mbid)
if len(images) > 0 && err == nil {
log.Debug(ctx, "Got Images", "agent", ag.AgentName(), "artist", name, "images", images, "elapsed", time.Since(start))
return images, nil
}
}
return nil, ErrNotFound
}
func (a *Agents) GetArtistTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]Song, error) {
switch id {
case consts.UnknownArtistID:
return nil, ErrNotFound
case consts.VariousArtistsID:
return nil, nil
}
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(ArtistTopSongsRetriever)
if !ok {
continue
}
songs, err := agent.GetArtistTopSongs(ctx, id, artistName, mbid, count)
if len(songs) > 0 && err == nil {
log.Debug(ctx, "Got Top Songs", "agent", ag.AgentName(), "artist", artistName, "songs", songs, "elapsed", time.Since(start))
return songs, nil
}
}
return nil, ErrNotFound
}
func (a *Agents) GetAlbumInfo(ctx context.Context, name, artist, mbid string) (*AlbumInfo, error) {
if name == consts.UnknownAlbum {
return nil, ErrNotFound
}
start := time.Now()
for _, ag := range a.agents {
if utils.IsCtxDone(ctx) {
break
}
agent, ok := ag.(AlbumInfoRetriever)
if !ok {
continue
}
album, err := agent.GetAlbumInfo(ctx, name, artist, mbid)
if err == nil {
log.Debug(ctx, "Got Album Info", "agent", ag.AgentName(), "album", name, "artist", artist,
"mbid", mbid, "elapsed", time.Since(start))
return album, nil
}
}
return nil, ErrNotFound
}
var _ Interface = (*Agents)(nil)
var _ ArtistMBIDRetriever = (*Agents)(nil)
var _ ArtistURLRetriever = (*Agents)(nil)
var _ ArtistBiographyRetriever = (*Agents)(nil)
var _ ArtistSimilarRetriever = (*Agents)(nil)
var _ ArtistImageRetriever = (*Agents)(nil)
var _ ArtistTopSongsRetriever = (*Agents)(nil)
var _ AlbumInfoRetriever = (*Agents)(nil)

View File

@ -0,0 +1,17 @@
package agents
import (
"testing"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestAgents(t *testing.T) {
tests.Init(t, false)
log.SetLevel(log.LevelFatal)
RegisterFailHandler(Fail)
RunSpecs(t, "Agents Test Suite")
}

346
core/agents/agents_test.go Normal file
View File

@ -0,0 +1,346 @@
package agents
import (
"context"
"errors"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/tests"
"github.com/navidrome/navidrome/conf"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Agents", func() {
var ctx context.Context
var cancel context.CancelFunc
var ds model.DataStore
var mfRepo *tests.MockMediaFileRepo
BeforeEach(func() {
ctx, cancel = context.WithCancel(context.Background())
mfRepo = tests.CreateMockMediaFileRepo()
ds = &tests.MockDataStore{MockedMediaFile: mfRepo}
})
Describe("Local", func() {
var ag *Agents
BeforeEach(func() {
conf.Server.Agents = ""
ag = New(ds)
})
It("calls the placeholder GetArtistImages", func() {
mfRepo.SetData(model.MediaFiles{{ID: "1", Title: "One", MbzReleaseTrackID: "111"}, {ID: "2", Title: "Two", MbzReleaseTrackID: "222"}})
songs, err := ag.GetArtistTopSongs(ctx, "123", "John Doe", "mb123", 2)
Expect(err).ToNot(HaveOccurred())
Expect(songs).To(ConsistOf([]Song{{Name: "One", MBID: "111"}, {Name: "Two", MBID: "222"}}))
})
})
Describe("Agents", func() {
var ag *Agents
var mock *mockAgent
BeforeEach(func() {
mock = &mockAgent{}
Register("fake", func(ds model.DataStore) Interface {
return mock
})
Register("empty", func(ds model.DataStore) Interface {
return struct {
Interface
}{}
})
conf.Server.Agents = "empty,fake"
ag = New(ds)
Expect(ag.AgentName()).To(Equal("agents"))
})
Describe("GetArtistMBID", func() {
It("returns on first match", func() {
Expect(ag.GetArtistMBID(ctx, "123", "test")).To(Equal("mbid"))
Expect(mock.Args).To(ConsistOf("123", "test"))
})
It("returns empty if artist is Various Artists", func() {
mbid, err := ag.GetArtistMBID(ctx, consts.VariousArtistsID, consts.VariousArtists)
Expect(err).ToNot(HaveOccurred())
Expect(mbid).To(BeEmpty())
Expect(mock.Args).To(BeEmpty())
})
It("returns not found if artist is Unknown Artist", func() {
mbid, err := ag.GetArtistMBID(ctx, consts.VariousArtistsID, consts.VariousArtists)
Expect(err).ToNot(HaveOccurred())
Expect(mbid).To(BeEmpty())
Expect(mock.Args).To(BeEmpty())
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
_, err := ag.GetArtistMBID(ctx, "123", "test")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(ConsistOf("123", "test"))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetArtistMBID(ctx, "123", "test")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
})
Describe("GetArtistURL", func() {
It("returns on first match", func() {
Expect(ag.GetArtistURL(ctx, "123", "test", "mb123")).To(Equal("url"))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123"))
})
It("returns empty if artist is Various Artists", func() {
url, err := ag.GetArtistURL(ctx, consts.VariousArtistsID, consts.VariousArtists, "")
Expect(err).ToNot(HaveOccurred())
Expect(url).To(BeEmpty())
Expect(mock.Args).To(BeEmpty())
})
It("returns not found if artist is Unknown Artist", func() {
url, err := ag.GetArtistURL(ctx, consts.VariousArtistsID, consts.VariousArtists, "")
Expect(err).ToNot(HaveOccurred())
Expect(url).To(BeEmpty())
Expect(mock.Args).To(BeEmpty())
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
_, err := ag.GetArtistURL(ctx, "123", "test", "mb123")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123"))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetArtistURL(ctx, "123", "test", "mb123")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
})
Describe("GetArtistBiography", func() {
It("returns on first match", func() {
Expect(ag.GetArtistBiography(ctx, "123", "test", "mb123")).To(Equal("bio"))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123"))
})
It("returns empty if artist is Various Artists", func() {
bio, err := ag.GetArtistBiography(ctx, consts.VariousArtistsID, consts.VariousArtists, "")
Expect(err).ToNot(HaveOccurred())
Expect(bio).To(BeEmpty())
Expect(mock.Args).To(BeEmpty())
})
It("returns not found if artist is Unknown Artist", func() {
bio, err := ag.GetArtistBiography(ctx, consts.VariousArtistsID, consts.VariousArtists, "")
Expect(err).ToNot(HaveOccurred())
Expect(bio).To(BeEmpty())
Expect(mock.Args).To(BeEmpty())
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
_, err := ag.GetArtistBiography(ctx, "123", "test", "mb123")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123"))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetArtistBiography(ctx, "123", "test", "mb123")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
})
Describe("GetArtistImages", func() {
It("returns on first match", func() {
Expect(ag.GetArtistImages(ctx, "123", "test", "mb123")).To(Equal([]ExternalImage{{
URL: "imageUrl",
Size: 100,
}}))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123"))
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
_, err := ag.GetArtistImages(ctx, "123", "test", "mb123")
Expect(err).To(MatchError("not found"))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123"))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetArtistImages(ctx, "123", "test", "mb123")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
})
Describe("GetSimilarArtists", func() {
It("returns on first match", func() {
Expect(ag.GetSimilarArtists(ctx, "123", "test", "mb123", 1)).To(Equal([]Artist{{
Name: "Joe Dohn",
MBID: "mbid321",
}}))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123", 1))
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
_, err := ag.GetSimilarArtists(ctx, "123", "test", "mb123", 1)
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123", 1))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetSimilarArtists(ctx, "123", "test", "mb123", 1)
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
})
Describe("GetArtistTopSongs", func() {
It("returns on first match", func() {
Expect(ag.GetArtistTopSongs(ctx, "123", "test", "mb123", 2)).To(Equal([]Song{{
Name: "A Song",
MBID: "mbid444",
}}))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123", 2))
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
_, err := ag.GetArtistTopSongs(ctx, "123", "test", "mb123", 2)
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(ConsistOf("123", "test", "mb123", 2))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetArtistTopSongs(ctx, "123", "test", "mb123", 2)
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
})
Describe("GetAlbumInfo", func() {
It("returns meaningful data", func() {
Expect(ag.GetAlbumInfo(ctx, "album", "artist", "mbid")).To(Equal(&AlbumInfo{
Name: "A Song",
MBID: "mbid444",
Description: "A Description",
URL: "External URL",
Images: []ExternalImage{
{
Size: 174,
URL: "https://lastfm.freetls.fastly.net/i/u/174s/00000000000000000000000000000000.png",
}, {
Size: 64,
URL: "https://lastfm.freetls.fastly.net/i/u/64s/00000000000000000000000000000000.png",
}, {
Size: 34,
URL: "https://lastfm.freetls.fastly.net/i/u/34s/00000000000000000000000000000000.png",
},
},
}))
Expect(mock.Args).To(ConsistOf("album", "artist", "mbid"))
})
It("skips the agent if it returns an error", func() {
mock.Err = errors.New("error")
_, err := ag.GetAlbumInfo(ctx, "album", "artist", "mbid")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(ConsistOf("album", "artist", "mbid"))
})
It("interrupts if the context is canceled", func() {
cancel()
_, err := ag.GetAlbumInfo(ctx, "album", "artist", "mbid")
Expect(err).To(MatchError(ErrNotFound))
Expect(mock.Args).To(BeEmpty())
})
})
})
})
type mockAgent struct {
Args []interface{}
Err error
}
func (a *mockAgent) AgentName() string {
return "fake"
}
func (a *mockAgent) GetArtistMBID(_ context.Context, id string, name string) (string, error) {
a.Args = []interface{}{id, name}
if a.Err != nil {
return "", a.Err
}
return "mbid", nil
}
func (a *mockAgent) GetArtistURL(_ context.Context, id, name, mbid string) (string, error) {
a.Args = []interface{}{id, name, mbid}
if a.Err != nil {
return "", a.Err
}
return "url", nil
}
func (a *mockAgent) GetArtistBiography(_ context.Context, id, name, mbid string) (string, error) {
a.Args = []interface{}{id, name, mbid}
if a.Err != nil {
return "", a.Err
}
return "bio", nil
}
func (a *mockAgent) GetArtistImages(_ context.Context, id, name, mbid string) ([]ExternalImage, error) {
a.Args = []interface{}{id, name, mbid}
if a.Err != nil {
return nil, a.Err
}
return []ExternalImage{{
URL: "imageUrl",
Size: 100,
}}, nil
}
func (a *mockAgent) GetSimilarArtists(_ context.Context, id, name, mbid string, limit int) ([]Artist, error) {
a.Args = []interface{}{id, name, mbid, limit}
if a.Err != nil {
return nil, a.Err
}
return []Artist{{
Name: "Joe Dohn",
MBID: "mbid321",
}}, nil
}
func (a *mockAgent) GetArtistTopSongs(_ context.Context, id, artistName, mbid string, count int) ([]Song, error) {
a.Args = []interface{}{id, artistName, mbid, count}
if a.Err != nil {
return nil, a.Err
}
return []Song{{
Name: "A Song",
MBID: "mbid444",
}}, nil
}
func (a *mockAgent) GetAlbumInfo(ctx context.Context, name, artist, mbid string) (*AlbumInfo, error) {
a.Args = []interface{}{name, artist, mbid}
if a.Err != nil {
return nil, a.Err
}
return &AlbumInfo{
Name: "A Song",
MBID: "mbid444",
Description: "A Description",
URL: "External URL",
Images: []ExternalImage{
{
Size: 174,
URL: "https://lastfm.freetls.fastly.net/i/u/174s/00000000000000000000000000000000.png",
}, {
Size: 64,
URL: "https://lastfm.freetls.fastly.net/i/u/64s/00000000000000000000000000000000.png",
}, {
Size: 34,
URL: "https://lastfm.freetls.fastly.net/i/u/34s/00000000000000000000000000000000.png",
},
},
}, nil
}

79
core/agents/interfaces.go Normal file
View File

@ -0,0 +1,79 @@
package agents
import (
"context"
"errors"
"github.com/navidrome/navidrome/model"
)
type Constructor func(ds model.DataStore) Interface
type Interface interface {
AgentName() string
}
type AlbumInfo struct {
Name string
MBID string
Description string
URL string
Images []ExternalImage
}
type Artist struct {
Name string
MBID string
}
type ExternalImage struct {
URL string
Size int
}
type Song struct {
Name string
MBID string
}
var (
ErrNotFound = errors.New("not found")
)
// TODO Break up this interface in more specific methods, like artists
type AlbumInfoRetriever interface {
GetAlbumInfo(ctx context.Context, name, artist, mbid string) (*AlbumInfo, error)
}
type ArtistMBIDRetriever interface {
GetArtistMBID(ctx context.Context, id string, name string) (string, error)
}
type ArtistURLRetriever interface {
GetArtistURL(ctx context.Context, id, name, mbid string) (string, error)
}
type ArtistBiographyRetriever interface {
GetArtistBiography(ctx context.Context, id, name, mbid string) (string, error)
}
type ArtistSimilarRetriever interface {
GetSimilarArtists(ctx context.Context, id, name, mbid string, limit int) ([]Artist, error)
}
type ArtistImageRetriever interface {
GetArtistImages(ctx context.Context, id, name, mbid string) ([]ExternalImage, error)
}
type ArtistTopSongsRetriever interface {
GetArtistTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]Song, error)
}
var Map map[string]Constructor
func Register(name string, init Constructor) {
if Map == nil {
Map = make(map[string]Constructor)
}
Map[name] = init
}

324
core/agents/lastfm/agent.go Normal file
View File

@ -0,0 +1,324 @@
package lastfm
import (
"context"
"errors"
"net/http"
"regexp"
"strconv"
"strings"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils/cache"
)
const (
lastFMAgentName = "lastfm"
sessionKeyProperty = "LastFMSessionKey"
)
var ignoredBiographies = []string{
// Unknown Artist
`<a href="https://www.last.fm/music/`,
}
type lastfmAgent struct {
ds model.DataStore
sessionKeys *agents.SessionKeys
apiKey string
secret string
lang string
client *client
}
func lastFMConstructor(ds model.DataStore) *lastfmAgent {
l := &lastfmAgent{
ds: ds,
lang: conf.Server.LastFM.Language,
apiKey: conf.Server.LastFM.ApiKey,
secret: conf.Server.LastFM.Secret,
sessionKeys: &agents.SessionKeys{DataStore: ds, KeyName: sessionKeyProperty},
}
hc := &http.Client{
Timeout: consts.DefaultHttpClientTimeOut,
}
chc := cache.NewHTTPClient(hc, consts.DefaultHttpClientTimeOut)
l.client = newClient(l.apiKey, l.secret, l.lang, chc)
return l
}
func (l *lastfmAgent) AgentName() string {
return lastFMAgentName
}
var imageRegex = regexp.MustCompile(`u\/(\d+)`)
func (l *lastfmAgent) GetAlbumInfo(ctx context.Context, name, artist, mbid string) (*agents.AlbumInfo, error) {
a, err := l.callAlbumGetInfo(ctx, name, artist, mbid)
if err != nil {
return nil, err
}
response := agents.AlbumInfo{
Name: a.Name,
MBID: a.MBID,
Description: a.Description.Summary,
URL: a.URL,
Images: make([]agents.ExternalImage, 0),
}
// Last.fm can return duplicate sizes.
seenSizes := map[int]bool{}
// This assumes that Last.fm returns images with size small, medium, and large.
// This is true as of December 29, 2022
for _, img := range a.Image {
size := imageRegex.FindStringSubmatch(img.URL)
// Last.fm can return images without URL
if len(size) == 0 || len(size[0]) < 4 {
log.Trace(ctx, "LastFM/albuminfo image URL does not match expected regex or is empty", "url", img.URL, "size", img.Size)
continue
}
numericSize, err := strconv.Atoi(size[0][2:])
if err != nil {
log.Error(ctx, "LastFM/albuminfo image URL does not match expected regex", "url", img.URL, "size", img.Size, err)
return nil, err
} else {
if _, exists := seenSizes[numericSize]; !exists {
response.Images = append(response.Images, agents.ExternalImage{
Size: numericSize,
URL: img.URL,
})
seenSizes[numericSize] = true
}
}
}
return &response, nil
}
func (l *lastfmAgent) GetArtistMBID(ctx context.Context, id string, name string) (string, error) {
a, err := l.callArtistGetInfo(ctx, name, "")
if err != nil {
return "", err
}
if a.MBID == "" {
return "", agents.ErrNotFound
}
return a.MBID, nil
}
func (l *lastfmAgent) GetArtistURL(ctx context.Context, id, name, mbid string) (string, error) {
a, err := l.callArtistGetInfo(ctx, name, mbid)
if err != nil {
return "", err
}
if a.URL == "" {
return "", agents.ErrNotFound
}
return a.URL, nil
}
func (l *lastfmAgent) GetArtistBiography(ctx context.Context, id, name, mbid string) (string, error) {
a, err := l.callArtistGetInfo(ctx, name, mbid)
if err != nil {
return "", err
}
a.Bio.Summary = strings.TrimSpace(a.Bio.Summary)
if a.Bio.Summary == "" {
return "", agents.ErrNotFound
}
for _, ign := range ignoredBiographies {
if strings.HasPrefix(a.Bio.Summary, ign) {
return "", nil
}
}
return a.Bio.Summary, nil
}
func (l *lastfmAgent) GetSimilarArtists(ctx context.Context, id, name, mbid string, limit int) ([]agents.Artist, error) {
resp, err := l.callArtistGetSimilar(ctx, name, mbid, limit)
if err != nil {
return nil, err
}
if len(resp) == 0 {
return nil, agents.ErrNotFound
}
var res []agents.Artist
for _, a := range resp {
res = append(res, agents.Artist{
Name: a.Name,
MBID: a.MBID,
})
}
return res, nil
}
func (l *lastfmAgent) GetArtistTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]agents.Song, error) {
resp, err := l.callArtistGetTopTracks(ctx, artistName, mbid, count)
if err != nil {
return nil, err
}
if len(resp) == 0 {
return nil, agents.ErrNotFound
}
var res []agents.Song
for _, t := range resp {
res = append(res, agents.Song{
Name: t.Name,
MBID: t.MBID,
})
}
return res, nil
}
func (l *lastfmAgent) callAlbumGetInfo(ctx context.Context, name, artist, mbid string) (*Album, error) {
a, err := l.client.albumGetInfo(ctx, name, artist, mbid)
var lfErr *lastFMError
isLastFMError := errors.As(err, &lfErr)
if mbid != "" && (isLastFMError && lfErr.Code == 6) {
log.Warn(ctx, "LastFM/album.getInfo could not find album by mbid, trying again", "album", name, "mbid", mbid)
return l.callAlbumGetInfo(ctx, name, artist, "")
}
if err != nil {
if isLastFMError && lfErr.Code == 6 {
log.Debug(ctx, "Album not found", "album", name, "mbid", mbid, err)
} else {
log.Error(ctx, "Error calling LastFM/album.getInfo", "album", name, "mbid", mbid, err)
}
return nil, err
}
return a, nil
}
func (l *lastfmAgent) callArtistGetInfo(ctx context.Context, name string, mbid string) (*Artist, error) {
a, err := l.client.artistGetInfo(ctx, name, mbid)
var lfErr *lastFMError
isLastFMError := errors.As(err, &lfErr)
if mbid != "" && ((err == nil && a.Name == "[unknown]") || (isLastFMError && lfErr.Code == 6)) {
log.Warn(ctx, "LastFM/artist.getInfo could not find artist by mbid, trying again", "artist", name, "mbid", mbid)
return l.callArtistGetInfo(ctx, name, "")
}
if err != nil {
log.Error(ctx, "Error calling LastFM/artist.getInfo", "artist", name, "mbid", mbid, err)
return nil, err
}
return a, nil
}
func (l *lastfmAgent) callArtistGetSimilar(ctx context.Context, name string, mbid string, limit int) ([]Artist, error) {
s, err := l.client.artistGetSimilar(ctx, name, mbid, limit)
var lfErr *lastFMError
isLastFMError := errors.As(err, &lfErr)
if mbid != "" && ((err == nil && s.Attr.Artist == "[unknown]") || (isLastFMError && lfErr.Code == 6)) {
log.Warn(ctx, "LastFM/artist.getSimilar could not find artist by mbid, trying again", "artist", name, "mbid", mbid)
return l.callArtistGetSimilar(ctx, name, "", limit)
}
if err != nil {
log.Error(ctx, "Error calling LastFM/artist.getSimilar", "artist", name, "mbid", mbid, err)
return nil, err
}
return s.Artists, nil
}
func (l *lastfmAgent) callArtistGetTopTracks(ctx context.Context, artistName, mbid string, count int) ([]Track, error) {
t, err := l.client.artistGetTopTracks(ctx, artistName, mbid, count)
var lfErr *lastFMError
isLastFMError := errors.As(err, &lfErr)
if mbid != "" && ((err == nil && t.Attr.Artist == "[unknown]") || (isLastFMError && lfErr.Code == 6)) {
log.Warn(ctx, "LastFM/artist.getTopTracks could not find artist by mbid, trying again", "artist", artistName, "mbid", mbid)
return l.callArtistGetTopTracks(ctx, artistName, "", count)
}
if err != nil {
log.Error(ctx, "Error calling LastFM/artist.getTopTracks", "artist", artistName, "mbid", mbid, err)
return nil, err
}
return t.Track, nil
}
func (l *lastfmAgent) NowPlaying(ctx context.Context, userId string, track *model.MediaFile) error {
sk, err := l.sessionKeys.Get(ctx, userId)
if err != nil || sk == "" {
return scrobbler.ErrNotAuthorized
}
err = l.client.updateNowPlaying(ctx, sk, ScrobbleInfo{
artist: track.Artist,
track: track.Title,
album: track.Album,
trackNumber: track.TrackNumber,
mbid: track.MbzRecordingID,
duration: int(track.Duration),
albumArtist: track.AlbumArtist,
})
if err != nil {
log.Warn(ctx, "Last.fm client.updateNowPlaying returned error", "track", track.Title, err)
return scrobbler.ErrUnrecoverable
}
return nil
}
func (l *lastfmAgent) Scrobble(ctx context.Context, userId string, s scrobbler.Scrobble) error {
sk, err := l.sessionKeys.Get(ctx, userId)
if err != nil || sk == "" {
return scrobbler.ErrNotAuthorized
}
if s.Duration <= 30 {
log.Debug(ctx, "Skipping Last.fm scrobble for short song", "track", s.Title, "duration", s.Duration)
return nil
}
err = l.client.scrobble(ctx, sk, ScrobbleInfo{
artist: s.Artist,
track: s.Title,
album: s.Album,
trackNumber: s.TrackNumber,
mbid: s.MbzRecordingID,
duration: int(s.Duration),
albumArtist: s.AlbumArtist,
timestamp: s.TimeStamp,
})
if err == nil {
return nil
}
var lfErr *lastFMError
isLastFMError := errors.As(err, &lfErr)
if !isLastFMError {
log.Warn(ctx, "Last.fm client.scrobble returned error", "track", s.Title, err)
return scrobbler.ErrRetryLater
}
if lfErr.Code == 11 || lfErr.Code == 16 {
return scrobbler.ErrRetryLater
}
return scrobbler.ErrUnrecoverable
}
func (l *lastfmAgent) IsAuthorized(ctx context.Context, userId string) bool {
sk, err := l.sessionKeys.Get(ctx, userId)
return err == nil && sk != ""
}
func init() {
conf.AddHook(func() {
if conf.Server.LastFM.Enabled {
if conf.Server.LastFM.ApiKey != "" && conf.Server.LastFM.Secret != "" {
agents.Register(lastFMAgentName, func(ds model.DataStore) agents.Interface {
return lastFMConstructor(ds)
})
scrobbler.Register(lastFMAgentName, func(ds model.DataStore) scrobbler.Scrobbler {
return lastFMConstructor(ds)
})
}
}
})
}

View File

@ -0,0 +1,438 @@
package lastfm
import (
"bytes"
"context"
"errors"
"io"
"net/http"
"os"
"strconv"
"time"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
const (
lastfmError3 = `{"error":3,"message":"Invalid Method - No method with that name in this package","links":[]}`
lastfmError6 = `{"error":6,"message":"The artist you supplied could not be found","links":[]}`
)
var _ = Describe("lastfmAgent", func() {
var ds model.DataStore
var ctx context.Context
BeforeEach(func() {
ds = &tests.MockDataStore{}
ctx = context.Background()
})
Describe("lastFMConstructor", func() {
It("uses configured api key and language", func() {
conf.Server.LastFM.ApiKey = "123"
conf.Server.LastFM.Secret = "secret"
conf.Server.LastFM.Language = "pt"
agent := lastFMConstructor(ds)
Expect(agent.apiKey).To(Equal("123"))
Expect(agent.secret).To(Equal("secret"))
Expect(agent.lang).To(Equal("pt"))
})
})
Describe("GetArtistBiography", func() {
var agent *lastfmAgent
var httpClient *tests.FakeHttpClient
BeforeEach(func() {
httpClient = &tests.FakeHttpClient{}
client := newClient("API_KEY", "SECRET", "pt", httpClient)
agent = lastFMConstructor(ds)
agent.client = client
})
It("returns the biography", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
Expect(agent.GetArtistBiography(ctx, "123", "U2", "mbid-1234")).To(Equal("U2 é uma das mais importantes bandas de rock de todos os tempos. Formada em 1976 em Dublin, composta por Bono (vocalista e guitarrista), The Edge (guitarrista, pianista e backing vocal), Adam Clayton (baixista), Larry Mullen, Jr. (baterista e percussionista).\n\nDesde a década de 80, U2 é uma das bandas mais populares no mundo. Seus shows são únicos e um verdadeiro festival de efeitos especiais, além de serem um dos que mais arrecadam anualmente. <a href=\"https://www.last.fm/music/U2\">Read more on Last.fm</a>"))
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.fm call fails", func() {
httpClient.Err = errors.New("error")
_, err := agent.GetArtistBiography(ctx, "123", "U2", "mbid-1234")
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.fm call returns an error", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
_, err := agent.GetArtistBiography(ctx, "123", "U2", "mbid-1234")
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.fm call returns an error 6 and mbid is empty", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, err := agent.GetArtistBiography(ctx, "123", "U2", "")
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
})
Context("MBID non existent in Last.fm", func() {
It("calls again when the response is artist == [unknown]", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.unknown.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
_, _ = agent.GetArtistBiography(ctx, "123", "U2", "mbid-1234")
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
It("calls again when last.fm returns an error 6", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, _ = agent.GetArtistBiography(ctx, "123", "U2", "mbid-1234")
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
})
})
Describe("GetSimilarArtists", func() {
var agent *lastfmAgent
var httpClient *tests.FakeHttpClient
BeforeEach(func() {
httpClient = &tests.FakeHttpClient{}
client := newClient("API_KEY", "SECRET", "pt", httpClient)
agent = lastFMConstructor(ds)
agent.client = client
})
It("returns similar artists", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.getsimilar.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
Expect(agent.GetSimilarArtists(ctx, "123", "U2", "mbid-1234", 2)).To(Equal([]agents.Artist{
{Name: "Passengers", MBID: "e110c11f-1c94-4471-a350-c38f46b29389"},
{Name: "INXS", MBID: "481bf5f9-2e7c-4c44-b08a-05b32bc7c00d"},
}))
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.fm call fails", func() {
httpClient.Err = errors.New("error")
_, err := agent.GetSimilarArtists(ctx, "123", "U2", "mbid-1234", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.fm call returns an error", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
_, err := agent.GetSimilarArtists(ctx, "123", "U2", "mbid-1234", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.fm call returns an error 6 and mbid is empty", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, err := agent.GetSimilarArtists(ctx, "123", "U2", "", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
})
Context("MBID non existent in Last.fm", func() {
It("calls again when the response is artist == [unknown]", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.getsimilar.unknown.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
_, _ = agent.GetSimilarArtists(ctx, "123", "U2", "mbid-1234", 2)
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
It("calls again when last.fm returns an error 6", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, _ = agent.GetSimilarArtists(ctx, "123", "U2", "mbid-1234", 2)
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
})
})
Describe("GetArtistTopSongs", func() {
var agent *lastfmAgent
var httpClient *tests.FakeHttpClient
BeforeEach(func() {
httpClient = &tests.FakeHttpClient{}
client := newClient("API_KEY", "SECRET", "pt", httpClient)
agent = lastFMConstructor(ds)
agent.client = client
})
It("returns top songs", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.gettoptracks.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
Expect(agent.GetArtistTopSongs(ctx, "123", "U2", "mbid-1234", 2)).To(Equal([]agents.Song{
{Name: "Beautiful Day", MBID: "f7f264d0-a89b-4682-9cd7-a4e7c37637af"},
{Name: "With or Without You", MBID: "6b9a509f-6907-4a6e-9345-2f12da09ba4b"},
}))
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.fm call fails", func() {
httpClient.Err = errors.New("error")
_, err := agent.GetArtistTopSongs(ctx, "123", "U2", "mbid-1234", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.fm call returns an error", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
_, err := agent.GetArtistTopSongs(ctx, "123", "U2", "mbid-1234", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.fm call returns an error 6 and mbid is empty", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, err := agent.GetArtistTopSongs(ctx, "123", "U2", "", 2)
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
})
Context("MBID non existent in Last.fm", func() {
It("calls again when the response is artist == [unknown]", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.gettoptracks.unknown.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
_, _ = agent.GetArtistTopSongs(ctx, "123", "U2", "mbid-1234", 2)
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
It("calls again when last.fm returns an error 6", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, _ = agent.GetArtistTopSongs(ctx, "123", "U2", "mbid-1234", 2)
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
})
})
Describe("Scrobbling", func() {
var agent *lastfmAgent
var httpClient *tests.FakeHttpClient
var track *model.MediaFile
BeforeEach(func() {
_ = ds.UserProps(ctx).Put("user-1", sessionKeyProperty, "SK-1")
httpClient = &tests.FakeHttpClient{}
client := newClient("API_KEY", "SECRET", "en", httpClient)
agent = lastFMConstructor(ds)
agent.client = client
track = &model.MediaFile{
ID: "123",
Title: "Track Title",
Album: "Track Album",
Artist: "Track Artist",
AlbumArtist: "Track AlbumArtist",
TrackNumber: 1,
Duration: 180,
MbzRecordingID: "mbz-123",
}
})
Describe("NowPlaying", func() {
It("calls Last.fm with correct params", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString("{}")), StatusCode: 200}
err := agent.NowPlaying(ctx, "user-1", track)
Expect(err).ToNot(HaveOccurred())
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodPost))
sentParams := httpClient.SavedRequest.URL.Query()
Expect(sentParams.Get("method")).To(Equal("track.updateNowPlaying"))
Expect(sentParams.Get("sk")).To(Equal("SK-1"))
Expect(sentParams.Get("track")).To(Equal(track.Title))
Expect(sentParams.Get("album")).To(Equal(track.Album))
Expect(sentParams.Get("artist")).To(Equal(track.Artist))
Expect(sentParams.Get("albumArtist")).To(Equal(track.AlbumArtist))
Expect(sentParams.Get("trackNumber")).To(Equal(strconv.Itoa(track.TrackNumber)))
Expect(sentParams.Get("duration")).To(Equal(strconv.FormatFloat(float64(track.Duration), 'G', -1, 32)))
Expect(sentParams.Get("mbid")).To(Equal(track.MbzRecordingID))
})
It("returns ErrNotAuthorized if user is not linked", func() {
err := agent.NowPlaying(ctx, "user-2", track)
Expect(err).To(MatchError(scrobbler.ErrNotAuthorized))
})
})
Describe("scrobble", func() {
It("calls Last.fm with correct params", func() {
ts := time.Now()
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString("{}")), StatusCode: 200}
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: ts})
Expect(err).ToNot(HaveOccurred())
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodPost))
sentParams := httpClient.SavedRequest.URL.Query()
Expect(sentParams.Get("method")).To(Equal("track.scrobble"))
Expect(sentParams.Get("sk")).To(Equal("SK-1"))
Expect(sentParams.Get("track")).To(Equal(track.Title))
Expect(sentParams.Get("album")).To(Equal(track.Album))
Expect(sentParams.Get("artist")).To(Equal(track.Artist))
Expect(sentParams.Get("albumArtist")).To(Equal(track.AlbumArtist))
Expect(sentParams.Get("trackNumber")).To(Equal(strconv.Itoa(track.TrackNumber)))
Expect(sentParams.Get("duration")).To(Equal(strconv.FormatFloat(float64(track.Duration), 'G', -1, 32)))
Expect(sentParams.Get("mbid")).To(Equal(track.MbzRecordingID))
Expect(sentParams.Get("timestamp")).To(Equal(strconv.FormatInt(ts.Unix(), 10)))
})
It("skips songs with less than 31 seconds", func() {
track.Duration = 29
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString("{}")), StatusCode: 200}
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
Expect(err).ToNot(HaveOccurred())
Expect(httpClient.SavedRequest).To(BeNil())
})
It("returns ErrNotAuthorized if user is not linked", func() {
err := agent.Scrobble(ctx, "user-2", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
Expect(err).To(MatchError(scrobbler.ErrNotAuthorized))
})
It("returns ErrRetryLater on error 11", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"error":11,"message":"Service Offline - This service is temporarily offline. Try again later."}`)),
StatusCode: 400,
}
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
Expect(err).To(MatchError(scrobbler.ErrRetryLater))
})
It("returns ErrRetryLater on error 16", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"error":16,"message":"There was a temporary error processing your request. Please try again"}`)),
StatusCode: 400,
}
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
Expect(err).To(MatchError(scrobbler.ErrRetryLater))
})
It("returns ErrRetryLater on http errors", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`internal server error`)),
StatusCode: 500,
}
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
Expect(err).To(MatchError(scrobbler.ErrRetryLater))
})
It("returns ErrUnrecoverable on other errors", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"error":8,"message":"Operation failed - Something else went wrong"}`)),
StatusCode: 400,
}
err := agent.Scrobble(ctx, "user-1", scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()})
Expect(err).To(MatchError(scrobbler.ErrUnrecoverable))
})
})
})
Describe("GetAlbumInfo", func() {
var agent *lastfmAgent
var httpClient *tests.FakeHttpClient
BeforeEach(func() {
httpClient = &tests.FakeHttpClient{}
client := newClient("API_KEY", "SECRET", "pt", httpClient)
agent = lastFMConstructor(ds)
agent.client = client
})
It("returns the biography", func() {
f, _ := os.Open("tests/fixtures/lastfm.album.getinfo.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
Expect(agent.GetAlbumInfo(ctx, "Believe", "Cher", "03c91c40-49a6-44a7-90e7-a700edf97a62")).To(Equal(&agents.AlbumInfo{
Name: "Believe",
MBID: "03c91c40-49a6-44a7-90e7-a700edf97a62",
Description: "Believe is the twenty-third studio album by American singer-actress Cher, released on November 10, 1998 by Warner Bros. Records. The RIAA certified it Quadruple Platinum on December 23, 1999, recognizing four million shipments in the United States; Worldwide, the album has sold more than 20 million copies, making it the biggest-selling album of her career. In 1999 the album received three Grammy Awards nominations including \"Record of the Year\", \"Best Pop Album\" and winning \"Best Dance Recording\" for the single \"Believe\". It was released by Warner Bros. Records at the end of 1998. The album was executive produced by Rob <a href=\"https://www.last.fm/music/Cher/Believe\">Read more on Last.fm</a>.",
URL: "https://www.last.fm/music/Cher/Believe",
Images: []agents.ExternalImage{
{
URL: "https://lastfm.freetls.fastly.net/i/u/34s/3b54885952161aaea4ce2965b2db1638.png",
Size: 34,
},
{
URL: "https://lastfm.freetls.fastly.net/i/u/64s/3b54885952161aaea4ce2965b2db1638.png",
Size: 64,
},
{
URL: "https://lastfm.freetls.fastly.net/i/u/174s/3b54885952161aaea4ce2965b2db1638.png",
Size: 174,
},
{
URL: "https://lastfm.freetls.fastly.net/i/u/300x300/3b54885952161aaea4ce2965b2db1638.png",
Size: 300,
},
},
}))
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("03c91c40-49a6-44a7-90e7-a700edf97a62"))
})
It("returns empty images if no images are available", func() {
f, _ := os.Open("tests/fixtures/lastfm.album.getinfo.empty_urls.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
Expect(agent.GetAlbumInfo(ctx, "The Definitive Less Damage And More Joy", "The Jesus and Mary Chain", "")).To(Equal(&agents.AlbumInfo{
Name: "The Definitive Less Damage And More Joy",
URL: "https://www.last.fm/music/The+Jesus+and+Mary+Chain/The+Definitive+Less+Damage+And+More+Joy",
Images: []agents.ExternalImage{},
}))
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("album")).To(Equal("The Definitive Less Damage And More Joy"))
})
It("returns an error if Last.fm call fails", func() {
httpClient.Err = errors.New("error")
_, err := agent.GetAlbumInfo(ctx, "123", "U2", "mbid-1234")
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.fm call returns an error", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError3)), StatusCode: 200}
_, err := agent.GetAlbumInfo(ctx, "123", "U2", "mbid-1234")
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(Equal("mbid-1234"))
})
It("returns an error if Last.fm call returns an error 6 and mbid is empty", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, err := agent.GetAlbumInfo(ctx, "123", "U2", "")
Expect(err).To(HaveOccurred())
Expect(httpClient.RequestCount).To(Equal(1))
})
Context("MBID non existent in Last.fm", func() {
It("calls again when last.fm returns an error 6", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(lastfmError6)), StatusCode: 200}
_, _ = agent.GetAlbumInfo(ctx, "123", "U2", "mbid-1234")
Expect(httpClient.RequestCount).To(Equal(2))
Expect(httpClient.SavedRequest.URL.Query().Get("mbid")).To(BeEmpty())
})
})
})
})

View File

@ -0,0 +1,130 @@
package lastfm
import (
"bytes"
"context"
_ "embed"
"errors"
"net/http"
"time"
"github.com/deluan/rest"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/server"
"github.com/navidrome/navidrome/utils/req"
)
//go:embed token_received.html
var tokenReceivedPage []byte
type Router struct {
http.Handler
ds model.DataStore
sessionKeys *agents.SessionKeys
client *client
apiKey string
secret string
}
func NewRouter(ds model.DataStore) *Router {
r := &Router{
ds: ds,
apiKey: conf.Server.LastFM.ApiKey,
secret: conf.Server.LastFM.Secret,
sessionKeys: &agents.SessionKeys{DataStore: ds, KeyName: sessionKeyProperty},
}
r.Handler = r.routes()
hc := &http.Client{
Timeout: consts.DefaultHttpClientTimeOut,
}
r.client = newClient(r.apiKey, r.secret, "en", hc)
return r
}
func (s *Router) routes() http.Handler {
r := chi.NewRouter()
r.Group(func(r chi.Router) {
r.Use(server.Authenticator(s.ds))
r.Use(server.JWTRefresher)
r.Get("/link", s.getLinkStatus)
r.Delete("/link", s.unlink)
})
r.Get("/link/callback", s.callback)
return r
}
func (s *Router) getLinkStatus(w http.ResponseWriter, r *http.Request) {
resp := map[string]interface{}{}
u, _ := request.UserFrom(r.Context())
key, err := s.sessionKeys.Get(r.Context(), u.ID)
if err != nil && !errors.Is(err, model.ErrNotFound) {
resp["error"] = err
resp["status"] = false
_ = rest.RespondWithJSON(w, http.StatusInternalServerError, resp)
return
}
resp["status"] = key != ""
_ = rest.RespondWithJSON(w, http.StatusOK, resp)
}
func (s *Router) unlink(w http.ResponseWriter, r *http.Request) {
u, _ := request.UserFrom(r.Context())
err := s.sessionKeys.Delete(r.Context(), u.ID)
if err != nil {
_ = rest.RespondWithError(w, http.StatusInternalServerError, err.Error())
} else {
_ = rest.RespondWithJSON(w, http.StatusOK, map[string]string{})
}
}
func (s *Router) callback(w http.ResponseWriter, r *http.Request) {
p := req.Params(r)
token, err := p.String("token")
if err != nil {
_ = rest.RespondWithError(w, http.StatusBadRequest, "token not received")
return
}
uid, err := p.String("uid")
if err != nil {
_ = rest.RespondWithError(w, http.StatusBadRequest, "uid not received")
return
}
// Need to add user to context, as this is a non-authenticated endpoint, so it does not
// automatically contain any user info
ctx := request.WithUser(r.Context(), model.User{ID: uid})
err = s.fetchSessionKey(ctx, uid, token)
if err != nil {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("An error occurred while authorizing with Last.fm. \n\nRequest ID: " + middleware.GetReqID(ctx)))
return
}
http.ServeContent(w, r, "response", time.Now(), bytes.NewReader(tokenReceivedPage))
}
func (s *Router) fetchSessionKey(ctx context.Context, uid, token string) error {
sessionKey, err := s.client.getSession(ctx, token)
if err != nil {
log.Error(ctx, "Could not fetch LastFM session key", "userId", uid, "token", token,
"requestId", middleware.GetReqID(ctx), err)
return err
}
err = s.sessionKeys.Put(ctx, uid, sessionKey)
if err != nil {
log.Error("Could not save LastFM session key", "userId", uid, "requestId", middleware.GetReqID(ctx), err)
}
return err
}

View File

@ -0,0 +1,236 @@
package lastfm
import (
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"net/url"
"slices"
"sort"
"strconv"
"strings"
"time"
"github.com/navidrome/navidrome/log"
)
const (
apiBaseUrl = "https://ws.audioscrobbler.com/2.0/"
)
type lastFMError struct {
Code int
Message string
}
func (e *lastFMError) Error() string {
return fmt.Sprintf("last.fm error(%d): %s", e.Code, e.Message)
}
type httpDoer interface {
Do(req *http.Request) (*http.Response, error)
}
func newClient(apiKey string, secret string, lang string, hc httpDoer) *client {
return &client{apiKey, secret, lang, hc}
}
type client struct {
apiKey string
secret string
lang string
hc httpDoer
}
func (c *client) albumGetInfo(ctx context.Context, name string, artist string, mbid string) (*Album, error) {
params := url.Values{}
params.Add("method", "album.getInfo")
params.Add("album", name)
params.Add("artist", artist)
params.Add("mbid", mbid)
params.Add("lang", c.lang)
response, err := c.makeRequest(ctx, http.MethodGet, params, false)
if err != nil {
return nil, err
}
return &response.Album, nil
}
func (c *client) artistGetInfo(ctx context.Context, name string, mbid string) (*Artist, error) {
params := url.Values{}
params.Add("method", "artist.getInfo")
params.Add("artist", name)
params.Add("mbid", mbid)
params.Add("lang", c.lang)
response, err := c.makeRequest(ctx, http.MethodGet, params, false)
if err != nil {
return nil, err
}
return &response.Artist, nil
}
func (c *client) artistGetSimilar(ctx context.Context, name string, mbid string, limit int) (*SimilarArtists, error) {
params := url.Values{}
params.Add("method", "artist.getSimilar")
params.Add("artist", name)
params.Add("mbid", mbid)
params.Add("limit", strconv.Itoa(limit))
response, err := c.makeRequest(ctx, http.MethodGet, params, false)
if err != nil {
return nil, err
}
return &response.SimilarArtists, nil
}
func (c *client) artistGetTopTracks(ctx context.Context, name string, mbid string, limit int) (*TopTracks, error) {
params := url.Values{}
params.Add("method", "artist.getTopTracks")
params.Add("artist", name)
params.Add("mbid", mbid)
params.Add("limit", strconv.Itoa(limit))
response, err := c.makeRequest(ctx, http.MethodGet, params, false)
if err != nil {
return nil, err
}
return &response.TopTracks, nil
}
func (c *client) GetToken(ctx context.Context) (string, error) {
params := url.Values{}
params.Add("method", "auth.getToken")
c.sign(params)
response, err := c.makeRequest(ctx, http.MethodGet, params, true)
if err != nil {
return "", err
}
return response.Token, nil
}
func (c *client) getSession(ctx context.Context, token string) (string, error) {
params := url.Values{}
params.Add("method", "auth.getSession")
params.Add("token", token)
response, err := c.makeRequest(ctx, http.MethodGet, params, true)
if err != nil {
return "", err
}
return response.Session.Key, nil
}
type ScrobbleInfo struct {
artist string
track string
album string
trackNumber int
mbid string
duration int
albumArtist string
timestamp time.Time
}
func (c *client) updateNowPlaying(ctx context.Context, sessionKey string, info ScrobbleInfo) error {
params := url.Values{}
params.Add("method", "track.updateNowPlaying")
params.Add("artist", info.artist)
params.Add("track", info.track)
params.Add("album", info.album)
params.Add("trackNumber", strconv.Itoa(info.trackNumber))
params.Add("mbid", info.mbid)
params.Add("duration", strconv.Itoa(info.duration))
params.Add("albumArtist", info.albumArtist)
params.Add("sk", sessionKey)
resp, err := c.makeRequest(ctx, http.MethodPost, params, true)
if err != nil {
return err
}
if resp.NowPlaying.IgnoredMessage.Code != "0" {
log.Warn(ctx, "LastFM: NowPlaying was ignored", "code", resp.NowPlaying.IgnoredMessage.Code,
"text", resp.NowPlaying.IgnoredMessage.Text)
}
return nil
}
func (c *client) scrobble(ctx context.Context, sessionKey string, info ScrobbleInfo) error {
params := url.Values{}
params.Add("method", "track.scrobble")
params.Add("timestamp", strconv.FormatInt(info.timestamp.Unix(), 10))
params.Add("artist", info.artist)
params.Add("track", info.track)
params.Add("album", info.album)
params.Add("trackNumber", strconv.Itoa(info.trackNumber))
params.Add("mbid", info.mbid)
params.Add("duration", strconv.Itoa(info.duration))
params.Add("albumArtist", info.albumArtist)
params.Add("sk", sessionKey)
resp, err := c.makeRequest(ctx, http.MethodPost, params, true)
if err != nil {
return err
}
if resp.Scrobbles.Scrobble.IgnoredMessage.Code != "0" {
log.Warn(ctx, "LastFM: scrobble was ignored", "code", resp.Scrobbles.Scrobble.IgnoredMessage.Code,
"text", resp.Scrobbles.Scrobble.IgnoredMessage.Text, "info", info)
}
if resp.Scrobbles.Attr.Accepted != 1 {
log.Warn(ctx, "LastFM: scrobble was not accepted", "code", resp.Scrobbles.Scrobble.IgnoredMessage.Code,
"text", resp.Scrobbles.Scrobble.IgnoredMessage.Text, "info", info)
}
return nil
}
func (c *client) makeRequest(ctx context.Context, method string, params url.Values, signed bool) (*Response, error) {
params.Add("format", "json")
params.Add("api_key", c.apiKey)
if signed {
c.sign(params)
}
req, _ := http.NewRequestWithContext(ctx, method, apiBaseUrl, nil)
req.URL.RawQuery = params.Encode()
log.Trace(ctx, fmt.Sprintf("Sending Last.fm %s request", req.Method), "url", req.URL)
resp, err := c.hc.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
var response Response
jsonErr := decoder.Decode(&response)
if resp.StatusCode != 200 && jsonErr != nil {
return nil, fmt.Errorf("last.fm http status: (%d)", resp.StatusCode)
}
if jsonErr != nil {
return nil, jsonErr
}
if response.Error != 0 {
return &response, &lastFMError{Code: response.Error, Message: response.Message}
}
return &response, nil
}
func (c *client) sign(params url.Values) {
// the parameters must be in order before hashing
keys := make([]string, 0, len(params))
for k := range params {
if slices.Contains([]string{"format", "callback"}, k) {
continue
}
keys = append(keys, k)
}
sort.Strings(keys)
msg := strings.Builder{}
for _, k := range keys {
msg.WriteString(k)
msg.WriteString(params[k][0])
}
msg.WriteString(c.secret)
hash := md5.Sum([]byte(msg.String()))
params.Add("api_sig", hex.EncodeToString(hash[:]))
}

View File

@ -0,0 +1,173 @@
package lastfm
import (
"bytes"
"context"
"crypto/md5"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"os"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("client", func() {
var httpClient *tests.FakeHttpClient
var client *client
BeforeEach(func() {
httpClient = &tests.FakeHttpClient{}
client = newClient("API_KEY", "SECRET", "pt", httpClient)
})
Describe("albumGetInfo", func() {
It("returns an album on successful response", func() {
f, _ := os.Open("tests/fixtures/lastfm.album.getinfo.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
album, err := client.albumGetInfo(context.Background(), "Believe", "U2", "mbid-1234")
Expect(err).To(BeNil())
Expect(album.Name).To(Equal("Believe"))
Expect(httpClient.SavedRequest.URL.String()).To(Equal(apiBaseUrl + "?album=Believe&api_key=API_KEY&artist=U2&format=json&lang=pt&mbid=mbid-1234&method=album.getInfo"))
})
})
Describe("artistGetInfo", func() {
It("returns an artist for a successful response", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.getinfo.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
artist, err := client.artistGetInfo(context.Background(), "U2", "123")
Expect(err).To(BeNil())
Expect(artist.Name).To(Equal("U2"))
Expect(httpClient.SavedRequest.URL.String()).To(Equal(apiBaseUrl + "?api_key=API_KEY&artist=U2&format=json&lang=pt&mbid=123&method=artist.getInfo"))
})
It("fails if Last.fm returns an http status != 200", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`Internal Server Error`)),
StatusCode: 500,
}
_, err := client.artistGetInfo(context.Background(), "U2", "123")
Expect(err).To(MatchError("last.fm http status: (500)"))
})
It("fails if Last.fm returns an http status != 200", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"error":3,"message":"Invalid Method - No method with that name in this package"}`)),
StatusCode: 400,
}
_, err := client.artistGetInfo(context.Background(), "U2", "123")
Expect(err).To(MatchError(&lastFMError{Code: 3, Message: "Invalid Method - No method with that name in this package"}))
})
It("fails if Last.fm returns an error", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"error":6,"message":"The artist you supplied could not be found"}`)),
StatusCode: 200,
}
_, err := client.artistGetInfo(context.Background(), "U2", "123")
Expect(err).To(MatchError(&lastFMError{Code: 6, Message: "The artist you supplied could not be found"}))
})
It("fails if HttpClient.Do() returns error", func() {
httpClient.Err = errors.New("generic error")
_, err := client.artistGetInfo(context.Background(), "U2", "123")
Expect(err).To(MatchError("generic error"))
})
It("fails if returned body is not a valid JSON", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`<xml>NOT_VALID_JSON</xml>`)),
StatusCode: 200,
}
_, err := client.artistGetInfo(context.Background(), "U2", "123")
Expect(err).To(MatchError("invalid character '<' looking for beginning of value"))
})
})
Describe("artistGetSimilar", func() {
It("returns an artist for a successful response", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.getsimilar.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
similar, err := client.artistGetSimilar(context.Background(), "U2", "123", 2)
Expect(err).To(BeNil())
Expect(len(similar.Artists)).To(Equal(2))
Expect(httpClient.SavedRequest.URL.String()).To(Equal(apiBaseUrl + "?api_key=API_KEY&artist=U2&format=json&limit=2&mbid=123&method=artist.getSimilar"))
})
})
Describe("artistGetTopTracks", func() {
It("returns top tracks for a successful response", func() {
f, _ := os.Open("tests/fixtures/lastfm.artist.gettoptracks.json")
httpClient.Res = http.Response{Body: f, StatusCode: 200}
top, err := client.artistGetTopTracks(context.Background(), "U2", "123", 2)
Expect(err).To(BeNil())
Expect(len(top.Track)).To(Equal(2))
Expect(httpClient.SavedRequest.URL.String()).To(Equal(apiBaseUrl + "?api_key=API_KEY&artist=U2&format=json&limit=2&mbid=123&method=artist.getTopTracks"))
})
})
Describe("GetToken", func() {
It("returns a token when the request is successful", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"token":"TOKEN"}`)),
StatusCode: 200,
}
Expect(client.GetToken(context.Background())).To(Equal("TOKEN"))
queryParams := httpClient.SavedRequest.URL.Query()
Expect(queryParams.Get("method")).To(Equal("auth.getToken"))
Expect(queryParams.Get("format")).To(Equal("json"))
Expect(queryParams.Get("api_key")).To(Equal("API_KEY"))
Expect(queryParams.Get("api_sig")).ToNot(BeEmpty())
})
})
Describe("getSession", func() {
It("returns a session key when the request is successful", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"session":{"name":"Navidrome","key":"SESSION_KEY","subscriber":0}}`)),
StatusCode: 200,
}
Expect(client.getSession(context.Background(), "TOKEN")).To(Equal("SESSION_KEY"))
queryParams := httpClient.SavedRequest.URL.Query()
Expect(queryParams.Get("method")).To(Equal("auth.getSession"))
Expect(queryParams.Get("format")).To(Equal("json"))
Expect(queryParams.Get("token")).To(Equal("TOKEN"))
Expect(queryParams.Get("api_key")).To(Equal("API_KEY"))
Expect(queryParams.Get("api_sig")).ToNot(BeEmpty())
})
})
Describe("sign", func() {
It("adds an api_sig param with the signature", func() {
params := url.Values{}
params.Add("d", "444")
params.Add("callback", "https://myserver.com")
params.Add("a", "111")
params.Add("format", "json")
params.Add("c", "333")
params.Add("b", "222")
client.sign(params)
Expect(params).To(HaveKey("api_sig"))
sig := params.Get("api_sig")
expected := fmt.Sprintf("%x", md5.Sum([]byte("a111b222c333d444SECRET")))
Expect(sig).To(Equal(expected))
})
})
})

View File

@ -0,0 +1,17 @@
package lastfm
import (
"testing"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestLastFM(t *testing.T) {
tests.Init(t, false)
log.SetLevel(log.LevelFatal)
RegisterFailHandler(Fail)
RunSpecs(t, "LastFM Test Suite")
}

View File

@ -0,0 +1,119 @@
package lastfm
type Response struct {
Artist Artist `json:"artist"`
SimilarArtists SimilarArtists `json:"similarartists"`
TopTracks TopTracks `json:"toptracks"`
Album Album `json:"album"`
Error int `json:"error"`
Message string `json:"message"`
Token string `json:"token"`
Session Session `json:"session"`
NowPlaying NowPlaying `json:"nowplaying"`
Scrobbles Scrobbles `json:"scrobbles"`
}
type Album struct {
Name string `json:"name"`
MBID string `json:"mbid"`
URL string `json:"url"`
Image []ExternalImage `json:"image"`
Description Description `json:"wiki"`
}
type Artist struct {
Name string `json:"name"`
MBID string `json:"mbid"`
URL string `json:"url"`
Image []ExternalImage `json:"image"`
Bio Description `json:"bio"`
}
type SimilarArtists struct {
Artists []Artist `json:"artist"`
Attr Attr `json:"@attr"`
}
type Attr struct {
Artist string `json:"artist"`
}
type ExternalImage struct {
URL string `json:"#text"`
Size string `json:"size"`
}
type Description struct {
Published string `json:"published"`
Summary string `json:"summary"`
Content string `json:"content"`
}
type Track struct {
Name string `json:"name"`
MBID string `json:"mbid"`
}
type TopTracks struct {
Track []Track `json:"track"`
Attr Attr `json:"@attr"`
}
type Session struct {
Name string `json:"name"`
Key string `json:"key"`
Subscriber int `json:"subscriber"`
}
type NowPlaying struct {
Artist struct {
Corrected string `json:"corrected"`
Text string `json:"#text"`
} `json:"artist"`
IgnoredMessage struct {
Code string `json:"code"`
Text string `json:"#text"`
} `json:"ignoredMessage"`
Album struct {
Corrected string `json:"corrected"`
Text string `json:"#text"`
} `json:"album"`
AlbumArtist struct {
Corrected string `json:"corrected"`
Text string `json:"#text"`
} `json:"albumArtist"`
Track struct {
Corrected string `json:"corrected"`
Text string `json:"#text"`
} `json:"track"`
}
type Scrobbles struct {
Attr struct {
Accepted int `json:"accepted"`
Ignored int `json:"ignored"`
} `json:"@attr"`
Scrobble struct {
Artist struct {
Corrected string `json:"corrected"`
Text string `json:"#text"`
} `json:"artist"`
IgnoredMessage struct {
Code string `json:"code"`
Text string `json:"#text"`
} `json:"ignoredMessage"`
AlbumArtist struct {
Corrected string `json:"corrected"`
Text string `json:"#text"`
} `json:"albumArtist"`
Timestamp string `json:"timestamp"`
Album struct {
Corrected string `json:"corrected"`
Text string `json:"#text"`
} `json:"album"`
Track struct {
Corrected string `json:"corrected"`
Text string `json:"#text"`
} `json:"track"`
} `json:"scrobble"`
}

View File

@ -0,0 +1,65 @@
package lastfm
import (
"encoding/json"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("LastFM responses", func() {
Describe("Artist", func() {
It("parses the response correctly", func() {
var resp Response
body, _ := os.ReadFile("tests/fixtures/lastfm.artist.getinfo.json")
err := json.Unmarshal(body, &resp)
Expect(err).To(BeNil())
Expect(resp.Artist.Name).To(Equal("U2"))
Expect(resp.Artist.MBID).To(Equal("a3cb23fc-acd3-4ce0-8f36-1e5aa6a18432"))
Expect(resp.Artist.URL).To(Equal("https://www.last.fm/music/U2"))
Expect(resp.Artist.Bio.Summary).To(ContainSubstring("U2 é uma das mais importantes bandas de rock de todos os tempos"))
})
})
Describe("SimilarArtists", func() {
It("parses the response correctly", func() {
var resp Response
body, _ := os.ReadFile("tests/fixtures/lastfm.artist.getsimilar.json")
err := json.Unmarshal(body, &resp)
Expect(err).To(BeNil())
Expect(resp.SimilarArtists.Artists).To(HaveLen(2))
Expect(resp.SimilarArtists.Artists[0].Name).To(Equal("Passengers"))
Expect(resp.SimilarArtists.Artists[1].Name).To(Equal("INXS"))
})
})
Describe("TopTracks", func() {
It("parses the response correctly", func() {
var resp Response
body, _ := os.ReadFile("tests/fixtures/lastfm.artist.gettoptracks.json")
err := json.Unmarshal(body, &resp)
Expect(err).To(BeNil())
Expect(resp.TopTracks.Track).To(HaveLen(2))
Expect(resp.TopTracks.Track[0].Name).To(Equal("Beautiful Day"))
Expect(resp.TopTracks.Track[0].MBID).To(Equal("f7f264d0-a89b-4682-9cd7-a4e7c37637af"))
Expect(resp.TopTracks.Track[1].Name).To(Equal("With or Without You"))
Expect(resp.TopTracks.Track[1].MBID).To(Equal("6b9a509f-6907-4a6e-9345-2f12da09ba4b"))
})
})
Describe("Error", func() {
It("parses the error response correctly", func() {
var error Response
body := []byte(`{"error":3,"message":"Invalid Method - No method with that name in this package"}`)
err := json.Unmarshal(body, &error)
Expect(err).To(BeNil())
Expect(error.Error).To(Equal(3))
Expect(error.Message).To(Equal("Invalid Method - No method with that name in this package"))
})
})
})

View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Account Linking Success</title>
</head>
<body>
<h2 id="msg"></h2>
<script>
setTimeout("document.getElementById('msg').innerHTML = 'Success! Your account is linked to Last.fm. You can close this tab now.';",2000)
document.addEventListener("DOMContentLoaded", () => {
window.close();
});
</script>
</body>
</html>

View File

@ -0,0 +1,120 @@
package listenbrainz
import (
"context"
"errors"
"net/http"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils/cache"
)
const (
listenBrainzAgentName = "listenbrainz"
sessionKeyProperty = "ListenBrainzSessionKey"
)
type listenBrainzAgent struct {
ds model.DataStore
sessionKeys *agents.SessionKeys
baseURL string
client *client
}
func listenBrainzConstructor(ds model.DataStore) *listenBrainzAgent {
l := &listenBrainzAgent{
ds: ds,
sessionKeys: &agents.SessionKeys{DataStore: ds, KeyName: sessionKeyProperty},
baseURL: conf.Server.ListenBrainz.BaseURL,
}
hc := &http.Client{
Timeout: consts.DefaultHttpClientTimeOut,
}
chc := cache.NewHTTPClient(hc, consts.DefaultHttpClientTimeOut)
l.client = newClient(l.baseURL, chc)
return l
}
func (l *listenBrainzAgent) AgentName() string {
return listenBrainzAgentName
}
func (l *listenBrainzAgent) formatListen(track *model.MediaFile) listenInfo {
li := listenInfo{
TrackMetadata: trackMetadata{
ArtistName: track.Artist,
TrackName: track.Title,
ReleaseName: track.Album,
AdditionalInfo: additionalInfo{
SubmissionClient: consts.AppName,
SubmissionClientVersion: consts.Version,
TrackNumber: track.TrackNumber,
ArtistMbzIDs: []string{track.MbzArtistID},
RecordingMbzID: track.MbzRecordingID,
ReleaseMbID: track.MbzAlbumID,
DurationMs: int(track.Duration * 1000),
},
},
}
return li
}
func (l *listenBrainzAgent) NowPlaying(ctx context.Context, userId string, track *model.MediaFile) error {
sk, err := l.sessionKeys.Get(ctx, userId)
if err != nil || sk == "" {
return scrobbler.ErrNotAuthorized
}
li := l.formatListen(track)
err = l.client.updateNowPlaying(ctx, sk, li)
if err != nil {
log.Warn(ctx, "ListenBrainz updateNowPlaying returned error", "track", track.Title, err)
return scrobbler.ErrUnrecoverable
}
return nil
}
func (l *listenBrainzAgent) Scrobble(ctx context.Context, userId string, s scrobbler.Scrobble) error {
sk, err := l.sessionKeys.Get(ctx, userId)
if err != nil || sk == "" {
return scrobbler.ErrNotAuthorized
}
li := l.formatListen(&s.MediaFile)
li.ListenedAt = int(s.TimeStamp.Unix())
err = l.client.scrobble(ctx, sk, li)
if err == nil {
return nil
}
var lbErr *listenBrainzError
isListenBrainzError := errors.As(err, &lbErr)
if !isListenBrainzError {
log.Warn(ctx, "ListenBrainz Scrobble returned HTTP error", "track", s.Title, err)
return scrobbler.ErrRetryLater
}
if lbErr.Code == 500 || lbErr.Code == 503 {
return scrobbler.ErrRetryLater
}
return scrobbler.ErrUnrecoverable
}
func (l *listenBrainzAgent) IsAuthorized(ctx context.Context, userId string) bool {
sk, err := l.sessionKeys.Get(ctx, userId)
return err == nil && sk != ""
}
func init() {
conf.AddHook(func() {
if conf.Server.ListenBrainz.Enabled {
scrobbler.Register(listenBrainzAgentName, func(ds model.DataStore) scrobbler.Scrobbler {
return listenBrainzConstructor(ds)
})
}
})
}

View File

@ -0,0 +1,163 @@
package listenbrainz
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"time"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core/scrobbler"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gstruct"
)
var _ = Describe("listenBrainzAgent", func() {
var ds model.DataStore
var ctx context.Context
var agent *listenBrainzAgent
var httpClient *tests.FakeHttpClient
var track *model.MediaFile
BeforeEach(func() {
ds = &tests.MockDataStore{}
ctx = context.Background()
_ = ds.UserProps(ctx).Put("user-1", sessionKeyProperty, "SK-1")
httpClient = &tests.FakeHttpClient{}
agent = listenBrainzConstructor(ds)
agent.client = newClient("http://localhost:8080", httpClient)
track = &model.MediaFile{
ID: "123",
Title: "Track Title",
Album: "Track Album",
Artist: "Track Artist",
TrackNumber: 1,
MbzRecordingID: "mbz-123",
MbzAlbumID: "mbz-456",
MbzArtistID: "mbz-789",
Duration: 142.2,
}
})
Describe("formatListen", func() {
It("constructs the listenInfo properly", func() {
var idArtistId = func(element interface{}) string {
return element.(string)
}
lr := agent.formatListen(track)
Expect(lr).To(MatchAllFields(Fields{
"ListenedAt": Equal(0),
"TrackMetadata": MatchAllFields(Fields{
"ArtistName": Equal(track.Artist),
"TrackName": Equal(track.Title),
"ReleaseName": Equal(track.Album),
"AdditionalInfo": MatchAllFields(Fields{
"SubmissionClient": Equal(consts.AppName),
"SubmissionClientVersion": Equal(consts.Version),
"TrackNumber": Equal(track.TrackNumber),
"RecordingMbzID": Equal(track.MbzRecordingID),
"ReleaseMbID": Equal(track.MbzAlbumID),
"ArtistMbzIDs": MatchAllElements(idArtistId, Elements{
"mbz-789": Equal(track.MbzArtistID),
}),
"DurationMs": Equal(142200),
}),
}),
}))
})
})
Describe("NowPlaying", func() {
It("updates NowPlaying successfully", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(`{"status": "ok"}`)), StatusCode: 200}
err := agent.NowPlaying(ctx, "user-1", track)
Expect(err).ToNot(HaveOccurred())
})
It("returns ErrNotAuthorized if user is not linked", func() {
err := agent.NowPlaying(ctx, "user-2", track)
Expect(err).To(MatchError(scrobbler.ErrNotAuthorized))
})
})
Describe("Scrobble", func() {
var sc scrobbler.Scrobble
BeforeEach(func() {
sc = scrobbler.Scrobble{MediaFile: *track, TimeStamp: time.Now()}
})
It("sends a Scrobble successfully", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(`{"status": "ok"}`)), StatusCode: 200}
err := agent.Scrobble(ctx, "user-1", sc)
Expect(err).ToNot(HaveOccurred())
})
It("sets the Timestamp properly", func() {
httpClient.Res = http.Response{Body: io.NopCloser(bytes.NewBufferString(`{"status": "ok"}`)), StatusCode: 200}
err := agent.Scrobble(ctx, "user-1", sc)
Expect(err).ToNot(HaveOccurred())
decoder := json.NewDecoder(httpClient.SavedRequest.Body)
var lr listenBrainzRequestBody
err = decoder.Decode(&lr)
Expect(err).ToNot(HaveOccurred())
Expect(lr.Payload[0].ListenedAt).To(Equal(int(sc.TimeStamp.Unix())))
})
It("returns ErrNotAuthorized if user is not linked", func() {
err := agent.Scrobble(ctx, "user-2", sc)
Expect(err).To(MatchError(scrobbler.ErrNotAuthorized))
})
It("returns ErrRetryLater on error 503", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"code": 503, "error": "Cannot submit listens to queue, please try again later."}`)),
StatusCode: 503,
}
err := agent.Scrobble(ctx, "user-1", sc)
Expect(err).To(MatchError(scrobbler.ErrRetryLater))
})
It("returns ErrRetryLater on error 500", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"code": 500, "error": "Something went wrong. Please try again."}`)),
StatusCode: 500,
}
err := agent.Scrobble(ctx, "user-1", sc)
Expect(err).To(MatchError(scrobbler.ErrRetryLater))
})
It("returns ErrRetryLater on http errors", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`Bad Gateway`)),
StatusCode: 500,
}
err := agent.Scrobble(ctx, "user-1", sc)
Expect(err).To(MatchError(scrobbler.ErrRetryLater))
})
It("returns ErrUnrecoverable on other errors", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"code": 400, "error": "BadRequest: Invalid JSON document submitted."}`)),
StatusCode: 400,
}
err := agent.Scrobble(ctx, "user-1", sc)
Expect(err).To(MatchError(scrobbler.ErrUnrecoverable))
})
})
})

View File

@ -0,0 +1,121 @@
package listenbrainz
import (
"context"
"encoding/json"
"errors"
"net/http"
"github.com/deluan/rest"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/server"
)
type sessionKeysRepo interface {
Put(ctx context.Context, userId, sessionKey string) error
Get(ctx context.Context, userId string) (string, error)
Delete(ctx context.Context, userId string) error
}
type Router struct {
http.Handler
ds model.DataStore
sessionKeys sessionKeysRepo
client *client
}
func NewRouter(ds model.DataStore) *Router {
r := &Router{
ds: ds,
sessionKeys: &agents.SessionKeys{DataStore: ds, KeyName: sessionKeyProperty},
}
r.Handler = r.routes()
hc := &http.Client{
Timeout: consts.DefaultHttpClientTimeOut,
}
r.client = newClient(conf.Server.ListenBrainz.BaseURL, hc)
return r
}
func (s *Router) routes() http.Handler {
r := chi.NewRouter()
r.Group(func(r chi.Router) {
r.Use(server.Authenticator(s.ds))
r.Use(server.JWTRefresher)
r.Get("/link", s.getLinkStatus)
r.Put("/link", s.link)
r.Delete("/link", s.unlink)
})
return r
}
func (s *Router) getLinkStatus(w http.ResponseWriter, r *http.Request) {
resp := map[string]interface{}{}
u, _ := request.UserFrom(r.Context())
key, err := s.sessionKeys.Get(r.Context(), u.ID)
if err != nil && !errors.Is(err, model.ErrNotFound) {
resp["error"] = err
resp["status"] = false
_ = rest.RespondWithJSON(w, http.StatusInternalServerError, resp)
return
}
resp["status"] = key != ""
_ = rest.RespondWithJSON(w, http.StatusOK, resp)
}
func (s *Router) link(w http.ResponseWriter, r *http.Request) {
type tokenPayload struct {
Token string `json:"token"`
}
var payload tokenPayload
err := json.NewDecoder(r.Body).Decode(&payload)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
if payload.Token == "" {
_ = rest.RespondWithError(w, http.StatusBadRequest, "Token is required")
return
}
u, _ := request.UserFrom(r.Context())
resp, err := s.client.validateToken(r.Context(), payload.Token)
if err != nil {
log.Error(r.Context(), "Could not validate ListenBrainz token", "userId", u.ID, "requestId", middleware.GetReqID(r.Context()), err)
_ = rest.RespondWithError(w, http.StatusInternalServerError, err.Error())
return
}
if !resp.Valid {
_ = rest.RespondWithError(w, http.StatusBadRequest, "Invalid token")
return
}
err = s.sessionKeys.Put(r.Context(), u.ID, payload.Token)
if err != nil {
log.Error("Could not save ListenBrainz token", "userId", u.ID, "requestId", middleware.GetReqID(r.Context()), err)
_ = rest.RespondWithError(w, http.StatusInternalServerError, err.Error())
return
}
_ = rest.RespondWithJSON(w, http.StatusOK, map[string]interface{}{"status": resp.Valid, "user": resp.UserName})
}
func (s *Router) unlink(w http.ResponseWriter, r *http.Request) {
u, _ := request.UserFrom(r.Context())
err := s.sessionKeys.Delete(r.Context(), u.ID)
if err != nil {
_ = rest.RespondWithError(w, http.StatusInternalServerError, err.Error())
} else {
_ = rest.RespondWithJSON(w, http.StatusOK, map[string]string{})
}
}

View File

@ -0,0 +1,130 @@
package listenbrainz
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("ListenBrainz Auth Router", func() {
var sk *fakeSessionKeys
var httpClient *tests.FakeHttpClient
var r Router
var req *http.Request
var resp *httptest.ResponseRecorder
BeforeEach(func() {
sk = &fakeSessionKeys{KeyName: sessionKeyProperty}
httpClient = &tests.FakeHttpClient{}
cl := newClient("http://localhost/", httpClient)
r = Router{
sessionKeys: sk,
client: cl,
}
resp = httptest.NewRecorder()
})
Describe("getLinkStatus", func() {
It("returns false when there is no stored session key", func() {
req = httptest.NewRequest("GET", "/listenbrainz/link", nil)
r.getLinkStatus(resp, req)
Expect(resp.Code).To(Equal(http.StatusOK))
var parsed map[string]interface{}
Expect(json.Unmarshal(resp.Body.Bytes(), &parsed)).To(BeNil())
Expect(parsed["status"]).To(Equal(false))
})
It("returns true when there is a stored session key", func() {
sk.KeyValue = "sk-1"
req = httptest.NewRequest("GET", "/listenbrainz/link", nil)
r.getLinkStatus(resp, req)
Expect(resp.Code).To(Equal(http.StatusOK))
var parsed map[string]interface{}
Expect(json.Unmarshal(resp.Body.Bytes(), &parsed)).To(BeNil())
Expect(parsed["status"]).To(Equal(true))
})
})
Describe("link", func() {
It("returns bad request when no token is sent", func() {
req = httptest.NewRequest("PUT", "/listenbrainz/link", strings.NewReader(`{}`))
r.link(resp, req)
Expect(resp.Code).To(Equal(http.StatusBadRequest))
})
It("returns bad request when the token is invalid", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"code": 200, "message": "Token invalid.", "valid": false}`)),
StatusCode: 200,
}
req = httptest.NewRequest("PUT", "/listenbrainz/link", strings.NewReader(`{"token": "invalid-tok-1"}`))
r.link(resp, req)
Expect(resp.Code).To(Equal(http.StatusBadRequest))
})
It("returns true and the username when the token is valid", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"code": 200, "message": "Token valid.", "user_name": "ListenBrainzUser", "valid": true}`)),
StatusCode: 200,
}
req = httptest.NewRequest("PUT", "/listenbrainz/link", strings.NewReader(`{"token": "tok-1"}`))
r.link(resp, req)
Expect(resp.Code).To(Equal(http.StatusOK))
var parsed map[string]interface{}
Expect(json.Unmarshal(resp.Body.Bytes(), &parsed)).To(BeNil())
Expect(parsed["status"]).To(Equal(true))
Expect(parsed["user"]).To(Equal("ListenBrainzUser"))
})
It("saves the session key when the token is valid", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"code": 200, "message": "Token valid.", "user_name": "ListenBrainzUser", "valid": true}`)),
StatusCode: 200,
}
req = httptest.NewRequest("PUT", "/listenbrainz/link", strings.NewReader(`{"token": "tok-1"}`))
r.link(resp, req)
Expect(resp.Code).To(Equal(http.StatusOK))
Expect(sk.KeyValue).To(Equal("tok-1"))
})
})
Describe("unlink", func() {
It("removes the session key when unlinking", func() {
sk.KeyValue = "tok-1"
req = httptest.NewRequest("DELETE", "/listenbrainz/link", nil)
r.unlink(resp, req)
Expect(resp.Code).To(Equal(http.StatusOK))
Expect(sk.KeyValue).To(Equal(""))
})
})
})
type fakeSessionKeys struct {
KeyName string
KeyValue string
}
func (sk *fakeSessionKeys) Put(ctx context.Context, userId, sessionKey string) error {
sk.KeyValue = sessionKey
return nil
}
func (sk *fakeSessionKeys) Get(ctx context.Context, userId string) (string, error) {
return sk.KeyValue, nil
}
func (sk *fakeSessionKeys) Delete(ctx context.Context, userId string) error {
sk.KeyValue = ""
return nil
}

View File

@ -0,0 +1,177 @@
package listenbrainz
import (
"bytes"
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"path"
"github.com/navidrome/navidrome/log"
)
type listenBrainzError struct {
Code int
Message string
}
func (e *listenBrainzError) Error() string {
return fmt.Sprintf("ListenBrainz error(%d): %s", e.Code, e.Message)
}
type httpDoer interface {
Do(req *http.Request) (*http.Response, error)
}
func newClient(baseURL string, hc httpDoer) *client {
return &client{baseURL, hc}
}
type client struct {
baseURL string
hc httpDoer
}
type listenBrainzResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Error string `json:"error"`
Status string `json:"status"`
Valid bool `json:"valid"`
UserName string `json:"user_name"`
}
type listenBrainzRequest struct {
ApiKey string
Body listenBrainzRequestBody
}
type listenBrainzRequestBody struct {
ListenType listenType `json:"listen_type,omitempty"`
Payload []listenInfo `json:"payload,omitempty"`
}
type listenType string
const (
Single listenType = "single"
PlayingNow listenType = "playing_now"
)
type listenInfo struct {
ListenedAt int `json:"listened_at,omitempty"`
TrackMetadata trackMetadata `json:"track_metadata,omitempty"`
}
type trackMetadata struct {
ArtistName string `json:"artist_name,omitempty"`
TrackName string `json:"track_name,omitempty"`
ReleaseName string `json:"release_name,omitempty"`
AdditionalInfo additionalInfo `json:"additional_info,omitempty"`
}
type additionalInfo struct {
SubmissionClient string `json:"submission_client,omitempty"`
SubmissionClientVersion string `json:"submission_client_version,omitempty"`
TrackNumber int `json:"tracknumber,omitempty"`
RecordingMbzID string `json:"recording_mbid,omitempty"`
ArtistMbzIDs []string `json:"artist_mbids,omitempty"`
ReleaseMbID string `json:"release_mbid,omitempty"`
DurationMs int `json:"duration_ms,omitempty"`
}
func (c *client) validateToken(ctx context.Context, apiKey string) (*listenBrainzResponse, error) {
r := &listenBrainzRequest{
ApiKey: apiKey,
}
response, err := c.makeRequest(ctx, http.MethodGet, "validate-token", r)
if err != nil {
return nil, err
}
return response, nil
}
func (c *client) updateNowPlaying(ctx context.Context, apiKey string, li listenInfo) error {
r := &listenBrainzRequest{
ApiKey: apiKey,
Body: listenBrainzRequestBody{
ListenType: PlayingNow,
Payload: []listenInfo{li},
},
}
resp, err := c.makeRequest(ctx, http.MethodPost, "submit-listens", r)
if err != nil {
return err
}
if resp.Status != "ok" {
log.Warn(ctx, "ListenBrainz: NowPlaying was not accepted", "status", resp.Status)
}
return nil
}
func (c *client) scrobble(ctx context.Context, apiKey string, li listenInfo) error {
r := &listenBrainzRequest{
ApiKey: apiKey,
Body: listenBrainzRequestBody{
ListenType: Single,
Payload: []listenInfo{li},
},
}
resp, err := c.makeRequest(ctx, http.MethodPost, "submit-listens", r)
if err != nil {
return err
}
if resp.Status != "ok" {
log.Warn(ctx, "ListenBrainz: Scrobble was not accepted", "status", resp.Status)
}
return nil
}
func (c *client) path(endpoint string) (string, error) {
u, err := url.Parse(c.baseURL)
if err != nil {
return "", err
}
u.Path = path.Join(u.Path, endpoint)
return u.String(), nil
}
func (c *client) makeRequest(ctx context.Context, method string, endpoint string, r *listenBrainzRequest) (*listenBrainzResponse, error) {
b, _ := json.Marshal(r.Body)
uri, err := c.path(endpoint)
if err != nil {
return nil, err
}
req, _ := http.NewRequestWithContext(ctx, method, uri, bytes.NewBuffer(b))
req.Header.Add("Content-Type", "application/json; charset=UTF-8")
if r.ApiKey != "" {
req.Header.Add("Authorization", fmt.Sprintf("Token %s", r.ApiKey))
}
log.Trace(ctx, fmt.Sprintf("Sending ListenBrainz %s request", req.Method), "url", req.URL)
resp, err := c.hc.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
decoder := json.NewDecoder(resp.Body)
var response listenBrainzResponse
jsonErr := decoder.Decode(&response)
if resp.StatusCode != 200 && jsonErr != nil {
return nil, fmt.Errorf("ListenBrainz: HTTP Error, Status: (%d)", resp.StatusCode)
}
if jsonErr != nil {
return nil, jsonErr
}
if response.Code != 0 && response.Code != 200 {
return &response, &listenBrainzError{Code: response.Code, Message: response.Error}
}
return &response, nil
}

View File

@ -0,0 +1,119 @@
package listenbrainz
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"os"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("client", func() {
var httpClient *tests.FakeHttpClient
var client *client
BeforeEach(func() {
httpClient = &tests.FakeHttpClient{}
client = newClient("BASE_URL/", httpClient)
})
Describe("listenBrainzResponse", func() {
It("parses a response properly", func() {
var response listenBrainzResponse
err := json.Unmarshal([]byte(`{"code": 200, "message": "Message", "user_name": "UserName", "valid": true, "status": "ok", "error": "Error"}`), &response)
Expect(err).ToNot(HaveOccurred())
Expect(response.Code).To(Equal(200))
Expect(response.Message).To(Equal("Message"))
Expect(response.UserName).To(Equal("UserName"))
Expect(response.Valid).To(BeTrue())
Expect(response.Status).To(Equal("ok"))
Expect(response.Error).To(Equal("Error"))
})
})
Describe("validateToken", func() {
BeforeEach(func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"code": 200, "message": "Token valid.", "user_name": "ListenBrainzUser", "valid": true}`)),
StatusCode: 200,
}
})
It("formats the request properly", func() {
_, err := client.validateToken(context.Background(), "LB-TOKEN")
Expect(err).ToNot(HaveOccurred())
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodGet))
Expect(httpClient.SavedRequest.URL.String()).To(Equal("BASE_URL/validate-token"))
Expect(httpClient.SavedRequest.Header.Get("Authorization")).To(Equal("Token LB-TOKEN"))
Expect(httpClient.SavedRequest.Header.Get("Content-Type")).To(Equal("application/json; charset=UTF-8"))
})
It("parses and returns the response", func() {
res, err := client.validateToken(context.Background(), "LB-TOKEN")
Expect(err).ToNot(HaveOccurred())
Expect(res.Valid).To(Equal(true))
Expect(res.UserName).To(Equal("ListenBrainzUser"))
})
})
Context("with listenInfo", func() {
var li listenInfo
BeforeEach(func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"status": "ok"}`)),
StatusCode: 200,
}
li = listenInfo{
TrackMetadata: trackMetadata{
ArtistName: "Track Artist",
TrackName: "Track Title",
ReleaseName: "Track Album",
AdditionalInfo: additionalInfo{
TrackNumber: 1,
RecordingMbzID: "mbz-123",
ArtistMbzIDs: []string{"mbz-789"},
ReleaseMbID: "mbz-456",
DurationMs: 142200,
},
},
}
})
Describe("updateNowPlaying", func() {
It("formats the request properly", func() {
Expect(client.updateNowPlaying(context.Background(), "LB-TOKEN", li)).To(Succeed())
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodPost))
Expect(httpClient.SavedRequest.URL.String()).To(Equal("BASE_URL/submit-listens"))
Expect(httpClient.SavedRequest.Header.Get("Authorization")).To(Equal("Token LB-TOKEN"))
Expect(httpClient.SavedRequest.Header.Get("Content-Type")).To(Equal("application/json; charset=UTF-8"))
body, _ := io.ReadAll(httpClient.SavedRequest.Body)
f, _ := os.ReadFile("tests/fixtures/listenbrainz.nowplaying.request.json")
Expect(body).To(MatchJSON(f))
})
})
Describe("scrobble", func() {
BeforeEach(func() {
li.ListenedAt = 1635000000
})
It("formats the request properly", func() {
Expect(client.scrobble(context.Background(), "LB-TOKEN", li)).To(Succeed())
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodPost))
Expect(httpClient.SavedRequest.URL.String()).To(Equal("BASE_URL/submit-listens"))
Expect(httpClient.SavedRequest.Header.Get("Authorization")).To(Equal("Token LB-TOKEN"))
Expect(httpClient.SavedRequest.Header.Get("Content-Type")).To(Equal("application/json; charset=UTF-8"))
body, _ := io.ReadAll(httpClient.SavedRequest.Body)
f, _ := os.ReadFile("tests/fixtures/listenbrainz.scrobble.request.json")
Expect(body).To(MatchJSON(f))
})
})
})
})

View File

@ -0,0 +1,17 @@
package listenbrainz
import (
"testing"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestListenBrainz(t *testing.T) {
tests.Init(t, false)
log.SetLevel(log.LevelFatal)
RegisterFailHandler(Fail)
RunSpecs(t, "ListenBrainz Test Suite")
}

View File

@ -0,0 +1,52 @@
package agents
import (
"context"
"github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/model"
)
const LocalAgentName = "local"
type localAgent struct {
ds model.DataStore
}
func localsConstructor(ds model.DataStore) Interface {
return &localAgent{ds}
}
func (p *localAgent) AgentName() string {
return LocalAgentName
}
func (p *localAgent) GetArtistTopSongs(ctx context.Context, id, artistName, mbid string, count int) ([]Song, error) {
top, err := p.ds.MediaFile(ctx).GetAll(model.QueryOptions{
Sort: "playCount",
Order: "desc",
Max: count,
Filters: squirrel.And{
squirrel.Eq{"artist_id": id},
squirrel.Or{
squirrel.Eq{"starred": true},
squirrel.Eq{"rating": 5},
},
},
})
if err != nil {
return nil, err
}
var result []Song
for _, s := range top {
result = append(result, Song{
Name: s.Title,
MBID: s.MbzReleaseTrackID,
})
}
return result, nil
}
func init() {
Register(LocalAgentName, localsConstructor)
}

View File

@ -0,0 +1,25 @@
package agents
import (
"context"
"github.com/navidrome/navidrome/model"
)
// SessionKeys is a simple wrapper around the UserPropsRepository
type SessionKeys struct {
model.DataStore
KeyName string
}
func (sk *SessionKeys) Put(ctx context.Context, userId, sessionKey string) error {
return sk.DataStore.UserProps(ctx).Put(userId, sk.KeyName, sessionKey)
}
func (sk *SessionKeys) Get(ctx context.Context, userId string) (string, error) {
return sk.DataStore.UserProps(ctx).Get(userId, sk.KeyName)
}
func (sk *SessionKeys) Delete(ctx context.Context, userId string) error {
return sk.DataStore.UserProps(ctx).Delete(userId, sk.KeyName)
}

View File

@ -0,0 +1,37 @@
package agents
import (
"context"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("SessionKeys", func() {
ctx := context.Background()
user := model.User{ID: "u-1"}
ds := &tests.MockDataStore{MockedUserProps: &tests.MockedUserPropsRepo{}}
sk := SessionKeys{DataStore: ds, KeyName: "fakeSessionKey"}
It("uses the assigned key name", func() {
Expect(sk.KeyName).To(Equal("fakeSessionKey"))
})
It("stores a value in the DB", func() {
Expect(sk.Put(ctx, user.ID, "test-stored-value")).To(BeNil())
})
It("fetches the stored value", func() {
value, err := sk.Get(ctx, user.ID)
Expect(err).ToNot(HaveOccurred())
Expect(value).To(Equal("test-stored-value"))
})
It("deletes the stored value", func() {
Expect(sk.Delete(ctx, user.ID)).To(BeNil())
})
It("handles a not found value", func() {
_, err := sk.Get(ctx, "u-2")
Expect(err).To(MatchError(model.ErrNotFound))
})
})

View File

@ -0,0 +1,116 @@
package spotify
import (
"context"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"github.com/navidrome/navidrome/log"
)
const apiBaseUrl = "https://api.spotify.com/v1/"
var (
ErrNotFound = errors.New("spotify: not found")
)
type httpDoer interface {
Do(req *http.Request) (*http.Response, error)
}
func newClient(id, secret string, hc httpDoer) *client {
return &client{id, secret, hc}
}
type client struct {
id string
secret string
hc httpDoer
}
func (c *client) searchArtists(ctx context.Context, name string, limit int) ([]Artist, error) {
token, err := c.authorize(ctx)
if err != nil {
return nil, err
}
params := url.Values{}
params.Add("type", "artist")
params.Add("q", name)
params.Add("offset", "0")
params.Add("limit", strconv.Itoa(limit))
req, _ := http.NewRequestWithContext(ctx, "GET", apiBaseUrl+"search", nil)
req.URL.RawQuery = params.Encode()
req.Header.Add("Authorization", "Bearer "+token)
var results SearchResults
err = c.makeRequest(req, &results)
if err != nil {
return nil, err
}
if len(results.Artists.Items) == 0 {
return nil, ErrNotFound
}
return results.Artists.Items, err
}
func (c *client) authorize(ctx context.Context) (string, error) {
payload := url.Values{}
payload.Add("grant_type", "client_credentials")
encodePayload := payload.Encode()
req, _ := http.NewRequestWithContext(ctx, "POST", "https://accounts.spotify.com/api/token", strings.NewReader(encodePayload))
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
req.Header.Add("Content-Length", strconv.Itoa(len(encodePayload)))
auth := c.id + ":" + c.secret
req.Header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
response := map[string]interface{}{}
err := c.makeRequest(req, &response)
if err != nil {
return "", err
}
if v, ok := response["access_token"]; ok {
return v.(string), nil
}
log.Error(ctx, "Invalid spotify response", "resp", response)
return "", errors.New("invalid response")
}
func (c *client) makeRequest(req *http.Request, response interface{}) error {
log.Trace(req.Context(), fmt.Sprintf("Sending Spotify %s request", req.Method), "url", req.URL)
resp, err := c.hc.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != 200 {
return c.parseError(data)
}
return json.Unmarshal(data, response)
}
func (c *client) parseError(data []byte) error {
var e Error
err := json.Unmarshal(data, &e)
if err != nil {
return err
}
return fmt.Errorf("spotify error(%s): %s", e.Code, e.Message)
}

View File

@ -0,0 +1,131 @@
package spotify
import (
"bytes"
"context"
"io"
"net/http"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("client", func() {
var httpClient *fakeHttpClient
var client *client
BeforeEach(func() {
httpClient = &fakeHttpClient{}
client = newClient("SPOTIFY_ID", "SPOTIFY_SECRET", httpClient)
})
Describe("ArtistImages", func() {
It("returns artist images from a successful request", func() {
f, _ := os.Open("tests/fixtures/spotify.search.artist.json")
httpClient.mock("https://api.spotify.com/v1/search", http.Response{Body: f, StatusCode: 200})
httpClient.mock("https://accounts.spotify.com/api/token", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(`{"access_token": "NEW_ACCESS_TOKEN","token_type": "Bearer","expires_in": 3600}`)),
})
artists, err := client.searchArtists(context.TODO(), "U2", 10)
Expect(err).To(BeNil())
Expect(artists).To(HaveLen(20))
Expect(artists[0].Popularity).To(Equal(82))
images := artists[0].Images
Expect(images).To(HaveLen(3))
Expect(images[0].Width).To(Equal(640))
Expect(images[1].Width).To(Equal(320))
Expect(images[2].Width).To(Equal(160))
})
It("fails if artist was not found", func() {
httpClient.mock("https://api.spotify.com/v1/search", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(`{
"artists" : {
"href" : "https://api.spotify.com/v1/search?query=dasdasdas%2Cdna&type=artist&offset=0&limit=20",
"items" : [ ], "limit" : 20, "next" : null, "offset" : 0, "previous" : null, "total" : 0
}}`)),
})
httpClient.mock("https://accounts.spotify.com/api/token", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(`{"access_token": "NEW_ACCESS_TOKEN","token_type": "Bearer","expires_in": 3600}`)),
})
_, err := client.searchArtists(context.TODO(), "U2", 10)
Expect(err).To(MatchError(ErrNotFound))
})
It("fails if not able to authorize", func() {
f, _ := os.Open("tests/fixtures/spotify.search.artist.json")
httpClient.mock("https://api.spotify.com/v1/search", http.Response{Body: f, StatusCode: 200})
httpClient.mock("https://accounts.spotify.com/api/token", http.Response{
StatusCode: 400,
Body: io.NopCloser(bytes.NewBufferString(`{"error":"invalid_client","error_description":"Invalid client"}`)),
})
_, err := client.searchArtists(context.TODO(), "U2", 10)
Expect(err).To(MatchError("spotify error(invalid_client): Invalid client"))
})
})
Describe("authorize", func() {
It("returns an access_token on successful authorization", func() {
httpClient.mock("https://accounts.spotify.com/api/token", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(`{"access_token": "NEW_ACCESS_TOKEN","token_type": "Bearer","expires_in": 3600}`)),
})
token, err := client.authorize(context.TODO())
Expect(err).To(BeNil())
Expect(token).To(Equal("NEW_ACCESS_TOKEN"))
auth := httpClient.lastRequest.Header.Get("Authorization")
Expect(auth).To(Equal("Basic U1BPVElGWV9JRDpTUE9USUZZX1NFQ1JFVA=="))
})
It("fails on unsuccessful authorization", func() {
httpClient.mock("https://accounts.spotify.com/api/token", http.Response{
StatusCode: 400,
Body: io.NopCloser(bytes.NewBufferString(`{"error":"invalid_client","error_description":"Invalid client"}`)),
})
_, err := client.authorize(context.TODO())
Expect(err).To(MatchError("spotify error(invalid_client): Invalid client"))
})
It("fails on invalid JSON response", func() {
httpClient.mock("https://accounts.spotify.com/api/token", http.Response{
StatusCode: 200,
Body: io.NopCloser(bytes.NewBufferString(`{NOT_VALID}`)),
})
_, err := client.authorize(context.TODO())
Expect(err).To(MatchError("invalid character 'N' looking for beginning of object key string"))
})
})
})
type fakeHttpClient struct {
responses map[string]*http.Response
lastRequest *http.Request
}
func (c *fakeHttpClient) mock(url string, response http.Response) {
if c.responses == nil {
c.responses = make(map[string]*http.Response)
}
c.responses[url] = &response
}
func (c *fakeHttpClient) Do(req *http.Request) (*http.Response, error) {
c.lastRequest = req
u := req.URL
u.RawQuery = ""
if resp, ok := c.responses[u.String()]; ok {
return resp, nil
}
panic("URL not mocked: " + u.String())
}

View File

@ -0,0 +1,30 @@
package spotify
type SearchResults struct {
Artists ArtistsResult `json:"artists"`
}
type ArtistsResult struct {
HRef string `json:"href"`
Items []Artist `json:"items"`
}
type Artist struct {
Genres []string `json:"genres"`
HRef string `json:"href"`
ID string `json:"id"`
Popularity int `json:"popularity"`
Images []Image `json:"images"`
Name string `json:"name"`
}
type Image struct {
URL string `json:"url"`
Width int `json:"width"`
Height int `json:"height"`
}
type Error struct {
Code string `json:"error"`
Message string `json:"error_description"`
}

View File

@ -0,0 +1,48 @@
package spotify
import (
"encoding/json"
"os"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Responses", func() {
Describe("Search type=artist", func() {
It("parses the artist search result correctly ", func() {
var resp SearchResults
body, _ := os.ReadFile("tests/fixtures/spotify.search.artist.json")
err := json.Unmarshal(body, &resp)
Expect(err).To(BeNil())
Expect(resp.Artists.Items).To(HaveLen(20))
u2 := resp.Artists.Items[0]
Expect(u2.Name).To(Equal("U2"))
Expect(u2.Genres).To(ContainElements("irish rock", "permanent wave", "rock"))
Expect(u2.ID).To(Equal("51Blml2LZPmy7TTiAg47vQ"))
Expect(u2.HRef).To(Equal("https://api.spotify.com/v1/artists/51Blml2LZPmy7TTiAg47vQ"))
Expect(u2.Images[0].URL).To(Equal("https://i.scdn.co/image/e22d5c0c8139b8439440a69854ed66efae91112d"))
Expect(u2.Images[0].Width).To(Equal(640))
Expect(u2.Images[0].Height).To(Equal(640))
Expect(u2.Images[1].URL).To(Equal("https://i.scdn.co/image/40d6c5c14355cfc127b70da221233315497ec91d"))
Expect(u2.Images[1].Width).To(Equal(320))
Expect(u2.Images[1].Height).To(Equal(320))
Expect(u2.Images[2].URL).To(Equal("https://i.scdn.co/image/7293d6752ae8a64e34adee5086858e408185b534"))
Expect(u2.Images[2].Width).To(Equal(160))
Expect(u2.Images[2].Height).To(Equal(160))
})
})
Describe("Error", func() {
It("parses the error response correctly", func() {
var errorResp Error
body := []byte(`{"error":"invalid_client","error_description":"Invalid client"}`)
err := json.Unmarshal(body, &errorResp)
Expect(err).To(BeNil())
Expect(errorResp.Code).To(Equal("invalid_client"))
Expect(errorResp.Message).To(Equal("Invalid client"))
})
})
})

View File

@ -0,0 +1,95 @@
package spotify
import (
"context"
"errors"
"fmt"
"net/http"
"sort"
"strings"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core/agents"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils/cache"
"github.com/xrash/smetrics"
)
const spotifyAgentName = "spotify"
type spotifyAgent struct {
ds model.DataStore
id string
secret string
client *client
}
func spotifyConstructor(ds model.DataStore) agents.Interface {
l := &spotifyAgent{
ds: ds,
id: conf.Server.Spotify.ID,
secret: conf.Server.Spotify.Secret,
}
hc := &http.Client{
Timeout: consts.DefaultHttpClientTimeOut,
}
chc := cache.NewHTTPClient(hc, consts.DefaultHttpClientTimeOut)
l.client = newClient(l.id, l.secret, chc)
return l
}
func (s *spotifyAgent) AgentName() string {
return spotifyAgentName
}
func (s *spotifyAgent) GetArtistImages(ctx context.Context, id, name, mbid string) ([]agents.ExternalImage, error) {
a, err := s.searchArtist(ctx, name)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
log.Warn(ctx, "Artist not found in Spotify", "artist", name)
} else {
log.Error(ctx, "Error calling Spotify", "artist", name, err)
}
return nil, err
}
var res []agents.ExternalImage
for _, img := range a.Images {
res = append(res, agents.ExternalImage{
URL: img.URL,
Size: img.Width,
})
}
return res, nil
}
func (s *spotifyAgent) searchArtist(ctx context.Context, name string) (*Artist, error) {
artists, err := s.client.searchArtists(ctx, name, 40)
if err != nil || len(artists) == 0 {
return nil, model.ErrNotFound
}
name = strings.ToLower(name)
// Sort results, prioritizing artists with images, with similar names and with high popularity, in this order
sort.Slice(artists, func(i, j int) bool {
ai := fmt.Sprintf("%-5t-%03d-%04d", len(artists[i].Images) == 0, smetrics.WagnerFischer(name, strings.ToLower(artists[i].Name), 1, 1, 2), 1000-artists[i].Popularity)
aj := fmt.Sprintf("%-5t-%03d-%04d", len(artists[j].Images) == 0, smetrics.WagnerFischer(name, strings.ToLower(artists[j].Name), 1, 1, 2), 1000-artists[j].Popularity)
return ai < aj
})
// If the first one has the same name, that's the one
if strings.ToLower(artists[0].Name) != name {
return nil, model.ErrNotFound
}
return &artists[0], err
}
func init() {
conf.AddHook(func() {
if conf.Server.Spotify.ID != "" && conf.Server.Spotify.Secret != "" {
agents.Register(spotifyAgentName, spotifyConstructor)
}
})
}

View File

@ -0,0 +1,17 @@
package spotify
import (
"testing"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestSpotify(t *testing.T) {
tests.Init(t, false)
log.SetLevel(log.LevelFatal)
RegisterFailHandler(Fail)
RunSpecs(t, "Spotify Test Suite")
}

View File

@ -7,50 +7,116 @@ import (
"io"
"os"
"path/filepath"
"strings"
"github.com/Masterminds/squirrel"
"github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils/slice"
)
type Archiver interface {
ZipAlbum(ctx context.Context, id string, w io.Writer) error
ZipArtist(ctx context.Context, id string, w io.Writer) error
ZipAlbum(ctx context.Context, id string, format string, bitrate int, w io.Writer) error
ZipArtist(ctx context.Context, id string, format string, bitrate int, w io.Writer) error
ZipShare(ctx context.Context, id string, w io.Writer) error
ZipPlaylist(ctx context.Context, id string, format string, bitrate int, w io.Writer) error
}
func NewArchiver(ds model.DataStore) Archiver {
return &archiver{ds: ds}
func NewArchiver(ms MediaStreamer, ds model.DataStore, shares Share) Archiver {
return &archiver{ds: ds, ms: ms, shares: shares}
}
type archiver struct {
ds model.DataStore
ds model.DataStore
ms MediaStreamer
shares Share
}
func (a *archiver) ZipAlbum(ctx context.Context, id string, out io.Writer) error {
mfs, err := a.ds.MediaFile(ctx).FindByAlbum(id)
if err != nil {
log.Error(ctx, "Error loading mediafiles from album", "id", id, err)
return err
}
return a.zipTracks(ctx, id, out, mfs)
func (a *archiver) ZipAlbum(ctx context.Context, id string, format string, bitrate int, out io.Writer) error {
return a.zipAlbums(ctx, id, format, bitrate, out, squirrel.Eq{"album_id": id})
}
func (a *archiver) ZipArtist(ctx context.Context, id string, out io.Writer) error {
mfs, err := a.ds.MediaFile(ctx).GetAll(model.QueryOptions{
Sort: "album",
Filters: squirrel.Eq{"album_artist_id": id},
})
func (a *archiver) ZipArtist(ctx context.Context, id string, format string, bitrate int, out io.Writer) error {
return a.zipAlbums(ctx, id, format, bitrate, out, squirrel.Eq{"album_artist_id": id})
}
func (a *archiver) zipAlbums(ctx context.Context, id string, format string, bitrate int, out io.Writer, filters squirrel.Sqlizer) error {
mfs, err := a.ds.MediaFile(ctx).GetAll(model.QueryOptions{Filters: filters, Sort: "album"})
if err != nil {
log.Error(ctx, "Error loading mediafiles from artist", "id", id, err)
return err
}
return a.zipTracks(ctx, id, out, mfs)
z := createZipWriter(out, format, bitrate)
albums := slice.Group(mfs, func(mf model.MediaFile) string {
return mf.AlbumID
})
for _, album := range albums {
discs := slice.Group(album, func(mf model.MediaFile) int { return mf.DiscNumber })
isMultDisc := len(discs) > 1
log.Debug(ctx, "Zipping album", "name", album[0].Album, "artist", album[0].AlbumArtist,
"format", format, "bitrate", bitrate, "isMultDisc", isMultDisc, "numTracks", len(album))
for _, mf := range album {
file := a.albumFilename(mf, format, isMultDisc)
_ = a.addFileToZip(ctx, z, mf, format, bitrate, file)
}
}
err = z.Close()
if err != nil {
log.Error(ctx, "Error closing zip file", "id", id, err)
}
return err
}
func (a *archiver) zipTracks(ctx context.Context, id string, out io.Writer, mfs model.MediaFiles) error {
func createZipWriter(out io.Writer, format string, bitrate int) *zip.Writer {
z := zip.NewWriter(out)
for _, mf := range mfs {
_ = a.addFileToZip(ctx, z, mf)
comment := "Downloaded from Navidrome"
if format != "raw" && format != "" {
comment = fmt.Sprintf("%s, transcoded to %s %dbps", comment, format, bitrate)
}
_ = z.SetComment(comment)
return z
}
func (a *archiver) albumFilename(mf model.MediaFile, format string, isMultDisc bool) string {
_, file := filepath.Split(mf.Path)
if format != "raw" {
file = strings.TrimSuffix(file, mf.Suffix) + format
}
if isMultDisc {
file = fmt.Sprintf("Disc %02d/%s", mf.DiscNumber, file)
}
return fmt.Sprintf("%s/%s", sanitizeName(mf.Album), file)
}
func (a *archiver) ZipShare(ctx context.Context, id string, out io.Writer) error {
s, err := a.shares.Load(ctx, id)
if !s.Downloadable {
return model.ErrNotAuthorized
}
if err != nil {
return err
}
log.Debug(ctx, "Zipping share", "name", s.ID, "format", s.Format, "bitrate", s.MaxBitRate, "numTracks", len(s.Tracks))
return a.zipMediaFiles(ctx, id, s.Format, s.MaxBitRate, out, s.Tracks)
}
func (a *archiver) ZipPlaylist(ctx context.Context, id string, format string, bitrate int, out io.Writer) error {
pls, err := a.ds.Playlist(ctx).GetWithTracks(id, true)
if err != nil {
log.Error(ctx, "Error loading mediafiles from playlist", "id", id, err)
return err
}
mfs := pls.MediaFiles()
log.Debug(ctx, "Zipping playlist", "name", pls.Name, "format", format, "bitrate", bitrate, "numTracks", len(mfs))
return a.zipMediaFiles(ctx, id, format, bitrate, out, mfs)
}
func (a *archiver) zipMediaFiles(ctx context.Context, id string, format string, bitrate int, out io.Writer, mfs model.MediaFiles) error {
z := createZipWriter(out, format, bitrate)
for idx, mf := range mfs {
file := a.playlistFilename(mf, format, idx)
_ = a.addFileToZip(ctx, z, mf, format, bitrate, file)
}
err := z.Close()
if err != nil {
@ -59,10 +125,21 @@ func (a *archiver) zipTracks(ctx context.Context, id string, out io.Writer, mfs
return err
}
func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.MediaFile) error {
_, file := filepath.Split(mf.Path)
func (a *archiver) playlistFilename(mf model.MediaFile, format string, idx int) string {
ext := mf.Suffix
if format != "" && format != "raw" {
ext = format
}
return fmt.Sprintf("%02d - %s - %s.%s", idx+1, sanitizeName(mf.Artist), sanitizeName(mf.Title), ext)
}
func sanitizeName(target string) string {
return strings.ReplaceAll(target, "/", "_")
}
func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.MediaFile, format string, bitrate int, filename string) error {
w, err := z.CreateHeader(&zip.FileHeader{
Name: fmt.Sprintf("%s/%s", mf.Album, file),
Name: filename,
Modified: mf.UpdatedAt,
Method: zip.Store,
})
@ -70,16 +147,29 @@ func (a *archiver) addFileToZip(ctx context.Context, z *zip.Writer, mf model.Med
log.Error(ctx, "Error creating zip entry", "file", mf.Path, err)
return err
}
f, err := os.Open(mf.Path)
defer func() { _ = f.Close() }()
var r io.ReadCloser
if format != "raw" && format != "" {
r, err = a.ms.DoStream(ctx, &mf, format, bitrate, 0)
} else {
r, err = os.Open(mf.Path)
}
if err != nil {
log.Error(ctx, "Error opening file for zipping", "file", mf.Path, err)
log.Error(ctx, "Error opening file for zipping", "file", mf.Path, "format", format, err)
return err
}
_, err = io.Copy(w, f)
defer func() {
if err := r.Close(); err != nil && log.IsGreaterOrEqualTo(log.LevelDebug) {
log.Error(ctx, "Error closing stream", "id", mf.ID, "file", mf.Path, err)
}
}()
_, err = io.Copy(w, r)
if err != nil {
log.Error(ctx, "Error zipping file", "file", mf.Path, err)
return err
}
return nil
}

211
core/archiver_test.go Normal file
View File

@ -0,0 +1,211 @@
package core_test
import (
"archive/zip"
"bytes"
"context"
"io"
"strings"
"github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/model"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/stretchr/testify/mock"
)
var _ = Describe("Archiver", func() {
var (
arch core.Archiver
ms *mockMediaStreamer
ds *mockDataStore
sh *mockShare
)
BeforeEach(func() {
ms = &mockMediaStreamer{}
ds = &mockDataStore{}
sh = &mockShare{}
arch = core.NewArchiver(ms, ds, sh)
})
Context("ZipAlbum", func() {
It("zips an album correctly", func() {
mfs := model.MediaFiles{
{Path: "test_data/01 - track1.mp3", Suffix: "mp3", AlbumID: "1", Album: "Album/Promo", DiscNumber: 1},
{Path: "test_data/02 - track2.mp3", Suffix: "mp3", AlbumID: "1", Album: "Album/Promo", DiscNumber: 1},
}
mfRepo := &mockMediaFileRepository{}
mfRepo.On("GetAll", []model.QueryOptions{{
Filters: squirrel.Eq{"album_id": "1"},
Sort: "album",
}}).Return(mfs, nil)
ds.On("MediaFile", mock.Anything).Return(mfRepo)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(3)
out := new(bytes.Buffer)
err := arch.ZipAlbum(context.Background(), "1", "mp3", 128, out)
Expect(err).To(BeNil())
zr, err := zip.NewReader(bytes.NewReader(out.Bytes()), int64(out.Len()))
Expect(err).To(BeNil())
Expect(len(zr.File)).To(Equal(2))
Expect(zr.File[0].Name).To(Equal("Album_Promo/01 - track1.mp3"))
Expect(zr.File[1].Name).To(Equal("Album_Promo/02 - track2.mp3"))
})
})
Context("ZipArtist", func() {
It("zips an artist's albums correctly", func() {
mfs := model.MediaFiles{
{Path: "test_data/01 - track1.mp3", Suffix: "mp3", AlbumArtistID: "1", AlbumID: "1", Album: "Album 1", DiscNumber: 1},
{Path: "test_data/02 - track2.mp3", Suffix: "mp3", AlbumArtistID: "1", AlbumID: "1", Album: "Album 1", DiscNumber: 1},
}
mfRepo := &mockMediaFileRepository{}
mfRepo.On("GetAll", []model.QueryOptions{{
Filters: squirrel.Eq{"album_artist_id": "1"},
Sort: "album",
}}).Return(mfs, nil)
ds.On("MediaFile", mock.Anything).Return(mfRepo)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2)
out := new(bytes.Buffer)
err := arch.ZipArtist(context.Background(), "1", "mp3", 128, out)
Expect(err).To(BeNil())
zr, err := zip.NewReader(bytes.NewReader(out.Bytes()), int64(out.Len()))
Expect(err).To(BeNil())
Expect(len(zr.File)).To(Equal(2))
Expect(zr.File[0].Name).To(Equal("Album 1/01 - track1.mp3"))
Expect(zr.File[1].Name).To(Equal("Album 1/02 - track2.mp3"))
})
})
Context("ZipShare", func() {
It("zips a share correctly", func() {
mfs := model.MediaFiles{
{ID: "1", Path: "test_data/01 - track1.mp3", Suffix: "mp3", Artist: "Artist 1", Title: "track1"},
{ID: "2", Path: "test_data/02 - track2.mp3", Suffix: "mp3", Artist: "Artist 2", Title: "track2"},
}
share := &model.Share{
ID: "1",
Downloadable: true,
Format: "mp3",
MaxBitRate: 128,
Tracks: mfs,
}
sh.On("Load", mock.Anything, "1").Return(share, nil)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2)
out := new(bytes.Buffer)
err := arch.ZipShare(context.Background(), "1", out)
Expect(err).To(BeNil())
zr, err := zip.NewReader(bytes.NewReader(out.Bytes()), int64(out.Len()))
Expect(err).To(BeNil())
Expect(len(zr.File)).To(Equal(2))
Expect(zr.File[0].Name).To(Equal("01 - Artist 1 - track1.mp3"))
Expect(zr.File[1].Name).To(Equal("02 - Artist 2 - track2.mp3"))
})
})
Context("ZipPlaylist", func() {
It("zips a playlist correctly", func() {
tracks := []model.PlaylistTrack{
{MediaFile: model.MediaFile{Path: "test_data/01 - track1.mp3", Suffix: "mp3", AlbumID: "1", Album: "Album 1", DiscNumber: 1, Artist: "AC/DC", Title: "track1"}},
{MediaFile: model.MediaFile{Path: "test_data/02 - track2.mp3", Suffix: "mp3", AlbumID: "1", Album: "Album 1", DiscNumber: 1, Artist: "Artist 2", Title: "track2"}},
}
pls := &model.Playlist{
ID: "1",
Name: "Test Playlist",
Tracks: tracks,
}
plRepo := &mockPlaylistRepository{}
plRepo.On("GetWithTracks", "1", true).Return(pls, nil)
ds.On("Playlist", mock.Anything).Return(plRepo)
ms.On("DoStream", mock.Anything, mock.Anything, "mp3", 128, 0).Return(io.NopCloser(strings.NewReader("test")), nil).Times(2)
out := new(bytes.Buffer)
err := arch.ZipPlaylist(context.Background(), "1", "mp3", 128, out)
Expect(err).To(BeNil())
zr, err := zip.NewReader(bytes.NewReader(out.Bytes()), int64(out.Len()))
Expect(err).To(BeNil())
Expect(len(zr.File)).To(Equal(2))
Expect(zr.File[0].Name).To(Equal("01 - AC_DC - track1.mp3"))
Expect(zr.File[1].Name).To(Equal("02 - Artist 2 - track2.mp3"))
})
})
})
type mockDataStore struct {
mock.Mock
model.DataStore
}
func (m *mockDataStore) MediaFile(ctx context.Context) model.MediaFileRepository {
args := m.Called(ctx)
return args.Get(0).(model.MediaFileRepository)
}
func (m *mockDataStore) Playlist(ctx context.Context) model.PlaylistRepository {
args := m.Called(ctx)
return args.Get(0).(model.PlaylistRepository)
}
type mockMediaFileRepository struct {
mock.Mock
model.MediaFileRepository
}
func (m *mockMediaFileRepository) GetAll(options ...model.QueryOptions) (model.MediaFiles, error) {
args := m.Called(options)
return args.Get(0).(model.MediaFiles), args.Error(1)
}
type mockPlaylistRepository struct {
mock.Mock
model.PlaylistRepository
}
func (m *mockPlaylistRepository) GetWithTracks(id string, includeTracks bool) (*model.Playlist, error) {
args := m.Called(id, includeTracks)
return args.Get(0).(*model.Playlist), args.Error(1)
}
type mockMediaStreamer struct {
mock.Mock
core.MediaStreamer
}
func (m *mockMediaStreamer) DoStream(ctx context.Context, mf *model.MediaFile, reqFormat string, reqBitRate int, reqOffset int) (*core.Stream, error) {
args := m.Called(ctx, mf, reqFormat, reqBitRate, reqOffset)
if args.Error(1) != nil {
return nil, args.Error(1)
}
return &core.Stream{ReadCloser: args.Get(0).(io.ReadCloser)}, nil
}
type mockShare struct {
mock.Mock
core.Share
}
func (m *mockShare) Load(ctx context.Context, id string) (*model.Share, error) {
args := m.Called(ctx, id)
return args.Get(0).(*model.Share), args.Error(1)
}

View File

@ -1,206 +0,0 @@
package core
import (
"bytes"
"context"
"errors"
"fmt"
"image"
_ "image/gif"
"image/jpeg"
_ "image/png"
"io"
"os"
"strings"
"time"
"github.com/deluan/navidrome/conf"
"github.com/deluan/navidrome/consts"
"github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model"
"github.com/deluan/navidrome/resources"
"github.com/deluan/navidrome/utils"
"github.com/dhowden/tag"
"github.com/disintegration/imaging"
)
type Artwork interface {
Get(ctx context.Context, id string, size int, out io.Writer) error
}
type ArtworkCache FileCache
func NewArtwork(ds model.DataStore, cache ArtworkCache) Artwork {
return &artwork{ds: ds, cache: cache}
}
type artwork struct {
ds model.DataStore
cache FileCache
}
type imageInfo struct {
c *artwork
path string
size int
lastUpdate time.Time
}
func (ci *imageInfo) String() string {
return fmt.Sprintf("%s.%d.%s.%d", ci.path, ci.size, ci.lastUpdate.Format(time.RFC3339Nano), conf.Server.CoverJpegQuality)
}
func (c *artwork) Get(ctx context.Context, id string, size int, out io.Writer) error {
path, lastUpdate, err := c.getImagePath(ctx, id)
if err != nil && err != model.ErrNotFound {
return err
}
info := &imageInfo{
c: c,
path: path,
size: size,
lastUpdate: lastUpdate,
}
r, err := c.cache.Get(ctx, info)
if err != nil {
log.Error(ctx, "Error accessing image cache", "path", path, "size", size, err)
return err
}
_, err = io.Copy(out, r)
return err
}
func (c *artwork) getImagePath(ctx context.Context, id string) (path string, lastUpdated time.Time, err error) {
// If id is an album cover ID
if strings.HasPrefix(id, "al-") {
log.Trace(ctx, "Looking for album art", "id", id)
id = strings.TrimPrefix(id, "al-")
var al *model.Album
al, err = c.ds.Album(ctx).Get(id)
if err != nil {
return
}
if al.CoverArtId == "" {
err = model.ErrNotFound
}
return al.CoverArtPath, al.UpdatedAt, err
}
log.Trace(ctx, "Looking for media file art", "id", id)
// Check if id is a mediaFile cover id
var mf *model.MediaFile
mf, err = c.ds.MediaFile(ctx).Get(id)
// If it is not, may be an albumId
if err == model.ErrNotFound {
return c.getImagePath(ctx, "al-"+id)
}
if err != nil {
return
}
// If it is a mediaFile and it has cover art, return it
if mf.HasCoverArt {
return mf.Path, mf.UpdatedAt, nil
}
// if the mediaFile does not have a coverArt, fallback to the album cover
log.Trace(ctx, "Media file does not contain art. Falling back to album art", "id", id, "albumId", "al-"+mf.AlbumID)
return c.getImagePath(ctx, "al-"+mf.AlbumID)
}
func (c *artwork) getArtwork(ctx context.Context, path string, size int) (reader io.Reader, err error) {
defer func() {
if err != nil {
log.Warn(ctx, "Error extracting image", "path", path, "size", size, err)
reader, err = resources.AssetFile().Open(consts.PlaceholderAlbumArt)
}
}()
if path == "" {
return nil, errors.New("empty path given for artwork")
}
var data []byte
if utils.IsAudioFile(path) {
data, err = readFromTag(path)
} else {
data, err = readFromFile(path)
}
if err != nil {
return
} else if size > 0 {
data, err = resizeImage(bytes.NewReader(data), size)
}
// Confirm the image is valid. Costly, but necessary
_, _, err = image.Decode(bytes.NewReader(data))
if err == nil {
reader = bytes.NewReader(data)
}
return
}
func resizeImage(reader io.Reader, size int) ([]byte, error) {
img, _, err := image.Decode(reader)
if err != nil {
return nil, err
}
m := imaging.Resize(img, size, size, imaging.Lanczos)
buf := new(bytes.Buffer)
err = jpeg.Encode(buf, m, &jpeg.Options{Quality: conf.Server.CoverJpegQuality})
return buf.Bytes(), err
}
func readFromTag(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
m, err := tag.ReadFrom(f)
if err != nil {
return nil, err
}
picture := m.Picture()
if picture == nil {
return nil, errors.New("file does not contain embedded art")
}
return picture.Data, nil
}
func readFromFile(path string) ([]byte, error) {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
var buf bytes.Buffer
if _, err := buf.ReadFrom(f); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func NewImageCache() ArtworkCache {
return NewFileCache("Image", conf.Server.ImageCacheSize, consts.ImageCacheDir, consts.DefaultImageCacheMaxItems,
func(ctx context.Context, arg fmt.Stringer) (io.Reader, error) {
info := arg.(*imageInfo)
reader, err := info.c.getArtwork(ctx, info.path, info.size)
if err != nil {
log.Error(ctx, "Error loading artwork art", "path", info.path, "size", info.size, err)
return nil, err
}
return reader, nil
})
}

130
core/artwork/artwork.go Normal file
View File

@ -0,0 +1,130 @@
package artwork
import (
"context"
"errors"
_ "image/gif"
"io"
"time"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/ffmpeg"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/resources"
"github.com/navidrome/navidrome/utils/cache"
_ "golang.org/x/image/webp"
)
var ErrUnavailable = errors.New("artwork unavailable")
type Artwork interface {
Get(ctx context.Context, artID model.ArtworkID, size int) (io.ReadCloser, time.Time, error)
GetOrPlaceholder(ctx context.Context, id string, size int) (io.ReadCloser, time.Time, error)
}
func NewArtwork(ds model.DataStore, cache cache.FileCache, ffmpeg ffmpeg.FFmpeg, em core.ExternalMetadata) Artwork {
return &artwork{ds: ds, cache: cache, ffmpeg: ffmpeg, em: em}
}
type artwork struct {
ds model.DataStore
cache cache.FileCache
ffmpeg ffmpeg.FFmpeg
em core.ExternalMetadata
}
type artworkReader interface {
cache.Item
LastUpdated() time.Time
Reader(ctx context.Context) (io.ReadCloser, string, error)
}
func (a *artwork) GetOrPlaceholder(ctx context.Context, id string, size int) (reader io.ReadCloser, lastUpdate time.Time, err error) {
artID, err := a.getArtworkId(ctx, id)
if err == nil {
reader, lastUpdate, err = a.Get(ctx, artID, size)
}
if errors.Is(err, ErrUnavailable) {
if artID.Kind == model.KindArtistArtwork {
reader, _ = resources.FS().Open(consts.PlaceholderArtistArt)
} else {
reader, _ = resources.FS().Open(consts.PlaceholderAlbumArt)
}
return reader, consts.ServerStart, nil
}
return reader, lastUpdate, err
}
func (a *artwork) Get(ctx context.Context, artID model.ArtworkID, size int) (reader io.ReadCloser, lastUpdate time.Time, err error) {
artReader, err := a.getArtworkReader(ctx, artID, size)
if err != nil {
return nil, time.Time{}, err
}
r, err := a.cache.Get(ctx, artReader)
if err != nil {
if !errors.Is(err, context.Canceled) && !errors.Is(err, ErrUnavailable) {
log.Error(ctx, "Error accessing image cache", "id", artID, "size", size, err)
}
return nil, time.Time{}, err
}
return r, artReader.LastUpdated(), nil
}
type coverArtGetter interface {
CoverArtID() model.ArtworkID
}
func (a *artwork) getArtworkId(ctx context.Context, id string) (model.ArtworkID, error) {
if id == "" {
return model.ArtworkID{}, ErrUnavailable
}
artID, err := model.ParseArtworkID(id)
if err == nil {
return artID, nil
}
log.Trace(ctx, "ArtworkID invalid. Trying to figure out kind based on the ID", "id", id)
entity, err := model.GetEntityByID(ctx, a.ds, id)
if err != nil {
return model.ArtworkID{}, err
}
if e, ok := entity.(coverArtGetter); ok {
artID = e.CoverArtID()
}
switch e := entity.(type) {
case *model.Artist:
log.Trace(ctx, "ID is for an Artist", "id", id, "name", e.Name, "artist", e.Name)
case *model.Album:
log.Trace(ctx, "ID is for an Album", "id", id, "name", e.Name, "artist", e.AlbumArtist)
case *model.MediaFile:
log.Trace(ctx, "ID is for a MediaFile", "id", id, "title", e.Title, "album", e.Album)
case *model.Playlist:
log.Trace(ctx, "ID is for a Playlist", "id", id, "name", e.Name)
}
return artID, nil
}
func (a *artwork) getArtworkReader(ctx context.Context, artID model.ArtworkID, size int) (artworkReader, error) {
var artReader artworkReader
var err error
if size > 0 {
artReader, err = resizedFromOriginal(ctx, a, artID, size)
} else {
switch artID.Kind {
case model.KindArtistArtwork:
artReader, err = newArtistReader(ctx, a, artID, a.em)
case model.KindAlbumArtwork:
artReader, err = newAlbumArtworkReader(ctx, a, artID, a.em)
case model.KindMediaFileArtwork:
artReader, err = newMediafileArtworkReader(ctx, a, artID)
case model.KindPlaylistArtwork:
artReader, err = newPlaylistArtworkReader(ctx, a, artID)
default:
return nil, ErrUnavailable
}
}
return artReader, err
}

View File

@ -0,0 +1,237 @@
package artwork
import (
"context"
"errors"
"image"
"io"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/conf/configtest"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Artwork", func() {
var aw *artwork
var ds model.DataStore
var ffmpeg *tests.MockFFmpeg
ctx := log.NewContext(context.TODO())
var alOnlyEmbed, alEmbedNotFound, alOnlyExternal, alExternalNotFound, alMultipleCovers model.Album
var arMultipleCovers model.Artist
var mfWithEmbed, mfAnotherWithEmbed, mfWithoutEmbed, mfCorruptedCover model.MediaFile
BeforeEach(func() {
DeferCleanup(configtest.SetupConfig())
conf.Server.ImageCacheSize = "0" // Disable cache
conf.Server.CoverArtPriority = "folder.*, cover.*, embedded , front.*"
ds = &tests.MockDataStore{MockedTranscoding: &tests.MockTranscodingRepo{}}
alOnlyEmbed = model.Album{ID: "222", Name: "Only embed", EmbedArtPath: "tests/fixtures/artist/an-album/test.mp3"}
alEmbedNotFound = model.Album{ID: "333", Name: "Embed not found", EmbedArtPath: "tests/fixtures/NON_EXISTENT.mp3"}
alOnlyExternal = model.Album{ID: "444", Name: "Only external", ImageFiles: "tests/fixtures/artist/an-album/front.png"}
alExternalNotFound = model.Album{ID: "555", Name: "External not found", ImageFiles: "tests/fixtures/NON_EXISTENT.png"}
arMultipleCovers = model.Artist{ID: "777", Name: "All options"}
alMultipleCovers = model.Album{
ID: "666",
Name: "All options",
EmbedArtPath: "tests/fixtures/artist/an-album/test.mp3",
Paths: "tests/fixtures/artist/an-album",
ImageFiles: "tests/fixtures/artist/an-album/cover.jpg" + consts.Zwsp +
"tests/fixtures/artist/an-album/front.png" + consts.Zwsp +
"tests/fixtures/artist/an-album/artist.png",
AlbumArtistID: "777",
}
mfWithEmbed = model.MediaFile{ID: "22", Path: "tests/fixtures/test.mp3", HasCoverArt: true, AlbumID: "222"}
mfAnotherWithEmbed = model.MediaFile{ID: "23", Path: "tests/fixtures/artist/an-album/test.mp3", HasCoverArt: true, AlbumID: "666"}
mfWithoutEmbed = model.MediaFile{ID: "44", Path: "tests/fixtures/test.ogg", AlbumID: "444"}
mfCorruptedCover = model.MediaFile{ID: "45", Path: "tests/fixtures/test.ogg", HasCoverArt: true, AlbumID: "444"}
cache := GetImageCache()
ffmpeg = tests.NewMockFFmpeg("content from ffmpeg")
aw = NewArtwork(ds, cache, ffmpeg, nil).(*artwork)
})
Describe("albumArtworkReader", func() {
Context("ID not found", func() {
It("returns ErrNotFound if album is not in the DB", func() {
_, err := newAlbumArtworkReader(ctx, aw, model.MustParseArtworkID("al-NOT-FOUND"), nil)
Expect(err).To(MatchError(model.ErrNotFound))
})
})
Context("Embed images", func() {
BeforeEach(func() {
ds.Album(ctx).(*tests.MockAlbumRepo).SetData(model.Albums{
alOnlyEmbed,
alEmbedNotFound,
})
})
It("returns embed cover", func() {
aw, err := newAlbumArtworkReader(ctx, aw, alOnlyEmbed.CoverArtID(), nil)
Expect(err).ToNot(HaveOccurred())
_, path, err := aw.Reader(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal("tests/fixtures/artist/an-album/test.mp3"))
})
It("returns ErrUnavailable if embed path is not available", func() {
ffmpeg.Error = errors.New("not available")
aw, err := newAlbumArtworkReader(ctx, aw, alEmbedNotFound.CoverArtID(), nil)
Expect(err).ToNot(HaveOccurred())
_, _, err = aw.Reader(ctx)
Expect(err).To(MatchError(ErrUnavailable))
})
})
Context("External images", func() {
BeforeEach(func() {
ds.Album(ctx).(*tests.MockAlbumRepo).SetData(model.Albums{
alOnlyExternal,
alExternalNotFound,
})
})
It("returns external cover", func() {
aw, err := newAlbumArtworkReader(ctx, aw, alOnlyExternal.CoverArtID(), nil)
Expect(err).ToNot(HaveOccurred())
_, path, err := aw.Reader(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal("tests/fixtures/artist/an-album/front.png"))
})
It("returns ErrUnavailable if external file is not available", func() {
aw, err := newAlbumArtworkReader(ctx, aw, alExternalNotFound.CoverArtID(), nil)
Expect(err).ToNot(HaveOccurred())
_, _, err = aw.Reader(ctx)
Expect(err).To(MatchError(ErrUnavailable))
})
})
Context("Multiple covers", func() {
BeforeEach(func() {
ds.Album(ctx).(*tests.MockAlbumRepo).SetData(model.Albums{
alMultipleCovers,
})
})
DescribeTable("CoverArtPriority",
func(priority string, expected string) {
conf.Server.CoverArtPriority = priority
aw, err := newAlbumArtworkReader(ctx, aw, alMultipleCovers.CoverArtID(), nil)
Expect(err).ToNot(HaveOccurred())
_, path, err := aw.Reader(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal(expected))
},
Entry(nil, " folder.* , cover.*,embedded,front.*", "tests/fixtures/artist/an-album/cover.jpg"),
Entry(nil, "front.* , cover.*, embedded ,folder.*", "tests/fixtures/artist/an-album/front.png"),
Entry(nil, " embedded , front.* , cover.*,folder.*", "tests/fixtures/artist/an-album/test.mp3"),
)
})
})
Describe("artistArtworkReader", func() {
Context("Multiple covers", func() {
BeforeEach(func() {
ds.Artist(ctx).(*tests.MockArtistRepo).SetData(model.Artists{
arMultipleCovers,
})
ds.Album(ctx).(*tests.MockAlbumRepo).SetData(model.Albums{
alMultipleCovers,
})
ds.MediaFile(ctx).(*tests.MockMediaFileRepo).SetData(model.MediaFiles{
mfAnotherWithEmbed,
})
})
DescribeTable("ArtistArtPriority",
func(priority string, expected string) {
conf.Server.ArtistArtPriority = priority
aw, err := newArtistReader(ctx, aw, arMultipleCovers.CoverArtID(), nil)
Expect(err).ToNot(HaveOccurred())
_, path, err := aw.Reader(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal(expected))
},
Entry(nil, " folder.* , artist.*,album/artist.*", "tests/fixtures/artist/artist.jpg"),
Entry(nil, "album/artist.*, folder.*,artist.*", "tests/fixtures/artist/an-album/artist.png"),
)
})
})
Describe("mediafileArtworkReader", func() {
Context("ID not found", func() {
It("returns ErrNotFound if mediafile is not in the DB", func() {
_, err := newAlbumArtworkReader(ctx, aw, alMultipleCovers.CoverArtID(), nil)
Expect(err).To(MatchError(model.ErrNotFound))
})
})
Context("Embed images", func() {
BeforeEach(func() {
ds.Album(ctx).(*tests.MockAlbumRepo).SetData(model.Albums{
alOnlyEmbed,
alOnlyExternal,
})
ds.MediaFile(ctx).(*tests.MockMediaFileRepo).SetData(model.MediaFiles{
mfWithEmbed,
mfWithoutEmbed,
mfCorruptedCover,
})
})
It("returns embed cover", func() {
aw, err := newMediafileArtworkReader(ctx, aw, mfWithEmbed.CoverArtID())
Expect(err).ToNot(HaveOccurred())
_, path, err := aw.Reader(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal("tests/fixtures/test.mp3"))
})
It("returns embed cover if successfully extracted by ffmpeg", func() {
aw, err := newMediafileArtworkReader(ctx, aw, mfCorruptedCover.CoverArtID())
Expect(err).ToNot(HaveOccurred())
r, path, err := aw.Reader(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(io.ReadAll(r)).To(Equal([]byte("content from ffmpeg")))
Expect(path).To(Equal("tests/fixtures/test.ogg"))
})
It("returns album cover if cannot read embed artwork", func() {
ffmpeg.Error = errors.New("not available")
aw, err := newMediafileArtworkReader(ctx, aw, mfCorruptedCover.CoverArtID())
Expect(err).ToNot(HaveOccurred())
_, path, err := aw.Reader(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal("al-444_0"))
})
It("returns album cover if media file has no cover art", func() {
aw, err := newMediafileArtworkReader(ctx, aw, model.MustParseArtworkID("mf-"+mfWithoutEmbed.ID))
Expect(err).ToNot(HaveOccurred())
_, path, err := aw.Reader(ctx)
Expect(err).ToNot(HaveOccurred())
Expect(path).To(Equal("al-444_0"))
})
})
})
Describe("resizedArtworkReader", func() {
BeforeEach(func() {
ds.Album(ctx).(*tests.MockAlbumRepo).SetData(model.Albums{
alMultipleCovers,
})
})
It("returns a PNG if original image is a PNG", func() {
conf.Server.CoverArtPriority = "front.png"
r, _, err := aw.Get(context.Background(), alMultipleCovers.CoverArtID(), 15)
Expect(err).ToNot(HaveOccurred())
img, format, err := image.Decode(r)
Expect(err).ToNot(HaveOccurred())
Expect(format).To(Equal("png"))
Expect(img.Bounds().Size().X).To(Equal(15))
Expect(img.Bounds().Size().Y).To(Equal(15))
})
It("returns a JPEG if original image is not a PNG", func() {
conf.Server.CoverArtPriority = "cover.jpg"
r, _, err := aw.Get(context.Background(), alMultipleCovers.CoverArtID(), 200)
Expect(err).ToNot(HaveOccurred())
img, format, err := image.Decode(r)
Expect(format).To(Equal("jpeg"))
Expect(err).ToNot(HaveOccurred())
Expect(img.Bounds().Size().X).To(Equal(200))
Expect(img.Bounds().Size().Y).To(Equal(200))
})
})
})

View File

@ -0,0 +1,17 @@
package artwork
import (
"testing"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestArtwork(t *testing.T) {
tests.Init(t, false)
log.SetLevel(log.LevelFatal)
RegisterFailHandler(Fail)
RunSpecs(t, "Artwork Suite")
}

View File

@ -0,0 +1,57 @@
package artwork_test
import (
"context"
"io"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/conf/configtest"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core/artwork"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/resources"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("Artwork", func() {
var aw artwork.Artwork
var ds model.DataStore
var ffmpeg *tests.MockFFmpeg
BeforeEach(func() {
DeferCleanup(configtest.SetupConfig())
conf.Server.ImageCacheSize = "0" // Disable cache
cache := artwork.GetImageCache()
ffmpeg = tests.NewMockFFmpeg("content from ffmpeg")
aw = artwork.NewArtwork(ds, cache, ffmpeg, nil)
})
Context("GetOrPlaceholder", func() {
Context("Empty ID", func() {
It("returns placeholder if album is not in the DB", func() {
r, _, err := aw.GetOrPlaceholder(context.Background(), "", 0)
Expect(err).ToNot(HaveOccurred())
ph, err := resources.FS().Open(consts.PlaceholderAlbumArt)
Expect(err).ToNot(HaveOccurred())
phBytes, err := io.ReadAll(ph)
Expect(err).ToNot(HaveOccurred())
result, err := io.ReadAll(r)
Expect(err).ToNot(HaveOccurred())
Expect(result).To(Equal(phBytes))
})
})
})
Context("Get", func() {
Context("Empty ID", func() {
It("returns an ErrUnavailable error", func() {
_, _, err := aw.Get(context.Background(), model.ArtworkID{}, 0)
Expect(err).To(MatchError(artwork.ErrUnavailable))
})
})
})
})

View File

@ -0,0 +1,146 @@
package artwork
import (
"context"
"fmt"
"io"
"sync"
"time"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/model/request"
"github.com/navidrome/navidrome/utils/cache"
"github.com/navidrome/navidrome/utils/pl"
"golang.org/x/exp/maps"
)
type CacheWarmer interface {
PreCache(artID model.ArtworkID)
}
func NewCacheWarmer(artwork Artwork, cache cache.FileCache) CacheWarmer {
// If image cache is disabled, return a NOOP implementation
if conf.Server.ImageCacheSize == "0" || !conf.Server.EnableArtworkPrecache {
return &noopCacheWarmer{}
}
a := &cacheWarmer{
artwork: artwork,
cache: cache,
buffer: make(map[model.ArtworkID]struct{}),
wakeSignal: make(chan struct{}, 1),
}
// Create a context with a fake admin user, to be able to pre-cache Playlist CoverArts
ctx := request.WithUser(context.TODO(), model.User{IsAdmin: true})
go a.run(ctx)
return a
}
type cacheWarmer struct {
artwork Artwork
buffer map[model.ArtworkID]struct{}
mutex sync.Mutex
cache cache.FileCache
wakeSignal chan struct{}
}
var ignoredIds = map[string]struct{}{
consts.VariousArtistsID: {},
consts.UnknownArtistID: {},
}
func (a *cacheWarmer) PreCache(artID model.ArtworkID) {
if _, shouldIgnore := ignoredIds[artID.ID]; shouldIgnore {
return
}
a.mutex.Lock()
defer a.mutex.Unlock()
a.buffer[artID] = struct{}{}
a.sendWakeSignal()
}
func (a *cacheWarmer) sendWakeSignal() {
// Don't block if the previous signal was not read yet
select {
case a.wakeSignal <- struct{}{}:
default:
}
}
func (a *cacheWarmer) run(ctx context.Context) {
for {
a.waitSignal(ctx, 10*time.Second)
if ctx.Err() != nil {
break
}
// If cache not available, keep waiting
if !a.cache.Available(ctx) {
if len(a.buffer) > 0 {
log.Trace(ctx, "Cache not available, buffering precache request", "bufferLen", len(a.buffer))
}
continue
}
a.mutex.Lock()
// If there's nothing to send, keep waiting
if len(a.buffer) == 0 {
a.mutex.Unlock()
continue
}
batch := maps.Keys(a.buffer)
a.buffer = make(map[model.ArtworkID]struct{})
a.mutex.Unlock()
a.processBatch(ctx, batch)
}
}
func (a *cacheWarmer) waitSignal(ctx context.Context, timeout time.Duration) {
var to <-chan time.Time
if !a.cache.Available(ctx) {
tmr := time.NewTimer(timeout)
defer tmr.Stop()
to = tmr.C
}
select {
case <-to:
case <-a.wakeSignal:
case <-ctx.Done():
}
}
func (a *cacheWarmer) processBatch(ctx context.Context, batch []model.ArtworkID) {
log.Trace(ctx, "PreCaching a new batch of artwork", "batchSize", len(batch))
input := pl.FromSlice(ctx, batch)
errs := pl.Sink(ctx, 2, input, a.doCacheImage)
for err := range errs {
log.Warn(ctx, "Error warming cache", err)
}
}
func (a *cacheWarmer) doCacheImage(ctx context.Context, id model.ArtworkID) error {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
r, _, err := a.artwork.Get(ctx, id, consts.UICoverArtSize)
if err != nil {
return fmt.Errorf("error caching id='%s': %w", id, err)
}
defer r.Close()
_, err = io.Copy(io.Discard, r)
if err != nil {
return err
}
return nil
}
type noopCacheWarmer struct{}
func (a *noopCacheWarmer) PreCache(model.ArtworkID) {}

View File

@ -0,0 +1,44 @@
package artwork
import (
"context"
"fmt"
"io"
"time"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils/cache"
"github.com/navidrome/navidrome/utils/singleton"
)
type cacheKey struct {
artID model.ArtworkID
lastUpdate time.Time
}
func (k *cacheKey) Key() string {
return fmt.Sprintf(
"%s-%s.%d",
k.artID.Kind,
k.artID.ID,
k.lastUpdate.UnixMilli(),
)
}
type imageCache struct {
cache.FileCache
}
func GetImageCache() cache.FileCache {
return singleton.GetInstance(func() *imageCache {
return &imageCache{
FileCache: cache.NewFileCache("Image", conf.Server.ImageCacheSize, consts.ImageCacheDir, consts.DefaultImageCacheMaxItems,
func(ctx context.Context, arg cache.Item) (io.Reader, error) {
r, _, err := arg.(artworkReader).Reader(ctx)
return r, err
}),
}
})
}

View File

@ -0,0 +1,74 @@
package artwork
import (
"context"
"crypto/md5"
"fmt"
"io"
"strings"
"time"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/ffmpeg"
"github.com/navidrome/navidrome/model"
)
type albumArtworkReader struct {
cacheKey
a *artwork
em core.ExternalMetadata
album model.Album
}
func newAlbumArtworkReader(ctx context.Context, artwork *artwork, artID model.ArtworkID, em core.ExternalMetadata) (*albumArtworkReader, error) {
al, err := artwork.ds.Album(ctx).Get(artID.ID)
if err != nil {
return nil, err
}
a := &albumArtworkReader{
a: artwork,
em: em,
album: *al,
}
a.cacheKey.artID = artID
a.cacheKey.lastUpdate = al.UpdatedAt
return a, nil
}
func (a *albumArtworkReader) Key() string {
var hash [16]byte
if conf.Server.EnableExternalServices {
hash = md5.Sum([]byte(conf.Server.Agents + conf.Server.CoverArtPriority))
}
return fmt.Sprintf(
"%s.%x.%t",
a.cacheKey.Key(),
hash,
conf.Server.EnableExternalServices,
)
}
func (a *albumArtworkReader) LastUpdated() time.Time {
return a.album.UpdatedAt
}
func (a *albumArtworkReader) Reader(ctx context.Context) (io.ReadCloser, string, error) {
var ff = a.fromCoverArtPriority(ctx, a.a.ffmpeg, conf.Server.CoverArtPriority)
return selectImageReader(ctx, a.artID, ff...)
}
func (a *albumArtworkReader) fromCoverArtPriority(ctx context.Context, ffmpeg ffmpeg.FFmpeg, priority string) []sourceFunc {
var ff []sourceFunc
for _, pattern := range strings.Split(strings.ToLower(priority), ",") {
pattern = strings.TrimSpace(pattern)
switch {
case pattern == "embedded":
ff = append(ff, fromTag(a.album.EmbedArtPath), fromFFmpegTag(ctx, ffmpeg, a.album.EmbedArtPath))
case pattern == "external":
ff = append(ff, fromAlbumExternalSource(ctx, a.album, a.em))
case a.album.ImageFiles != "":
ff = append(ff, fromExternalFile(ctx, a.album.ImageFiles, pattern))
}
}
return ff
}

View File

@ -0,0 +1,127 @@
package artwork
import (
"context"
"crypto/md5"
"fmt"
"io"
"io/fs"
"os"
"path/filepath"
"strings"
"time"
"github.com/Masterminds/squirrel"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils"
)
type artistReader struct {
cacheKey
a *artwork
em core.ExternalMetadata
artist model.Artist
artistFolder string
files string
}
func newArtistReader(ctx context.Context, artwork *artwork, artID model.ArtworkID, em core.ExternalMetadata) (*artistReader, error) {
ar, err := artwork.ds.Artist(ctx).Get(artID.ID)
if err != nil {
return nil, err
}
als, err := artwork.ds.Album(ctx).GetAll(model.QueryOptions{Filters: squirrel.Eq{"album_artist_id": artID.ID}})
if err != nil {
return nil, err
}
a := &artistReader{
a: artwork,
em: em,
artist: *ar,
}
// TODO Find a way to factor in the ExternalUpdateInfoAt in the cache key. Problem is that it can
// change _after_ retrieving from external sources, making the key invalid
//a.cacheKey.lastUpdate = ar.ExternalInfoUpdatedAt
var files []string
var paths []string
for _, al := range als {
files = append(files, al.ImageFiles)
paths = append(paths, splitList(al.Paths)...)
if a.cacheKey.lastUpdate.Before(al.UpdatedAt) {
a.cacheKey.lastUpdate = al.UpdatedAt
}
}
a.files = strings.Join(files, consts.Zwsp)
a.artistFolder = utils.LongestCommonPrefix(paths)
if !strings.HasSuffix(a.artistFolder, string(filepath.Separator)) {
a.artistFolder, _ = filepath.Split(a.artistFolder)
}
a.cacheKey.artID = artID
return a, nil
}
func (a *artistReader) Key() string {
hash := md5.Sum([]byte(conf.Server.Agents + conf.Server.Spotify.ID))
return fmt.Sprintf(
"%s.%t.%x",
a.cacheKey.Key(),
conf.Server.EnableExternalServices,
hash,
)
}
func (a *artistReader) LastUpdated() time.Time {
return a.lastUpdate
}
func (a *artistReader) Reader(ctx context.Context) (io.ReadCloser, string, error) {
var ff = a.fromArtistArtPriority(ctx, conf.Server.ArtistArtPriority)
return selectImageReader(ctx, a.artID, ff...)
}
func (a *artistReader) fromArtistArtPriority(ctx context.Context, priority string) []sourceFunc {
var ff []sourceFunc
for _, pattern := range strings.Split(strings.ToLower(priority), ",") {
pattern = strings.TrimSpace(pattern)
switch {
case pattern == "external":
ff = append(ff, fromArtistExternalSource(ctx, a.artist, a.em))
case strings.HasPrefix(pattern, "album/"):
ff = append(ff, fromExternalFile(ctx, a.files, strings.TrimPrefix(pattern, "album/")))
default:
ff = append(ff, fromArtistFolder(ctx, a.artistFolder, pattern))
}
}
return ff
}
func fromArtistFolder(ctx context.Context, artistFolder string, pattern string) sourceFunc {
return func() (io.ReadCloser, string, error) {
fsys := os.DirFS(artistFolder)
matches, err := fs.Glob(fsys, pattern)
if err != nil {
log.Warn(ctx, "Error matching artist image pattern", "pattern", pattern, "folder", artistFolder)
return nil, "", err
}
if len(matches) == 0 {
return nil, "", fmt.Errorf(`no matches for '%s' in '%s'`, pattern, artistFolder)
}
for _, m := range matches {
filePath := filepath.Join(artistFolder, m)
if !model.IsImageFile(m) {
continue
}
f, err := os.Open(filePath)
if err != nil {
log.Warn(ctx, "Could not open cover art file", "file", filePath, err)
return nil, "", err
}
return f, filePath, nil
}
return nil, "", nil
}
}

View File

@ -0,0 +1,64 @@
package artwork
import (
"context"
"fmt"
"io"
"time"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/model"
)
type mediafileArtworkReader struct {
cacheKey
a *artwork
mediafile model.MediaFile
album model.Album
}
func newMediafileArtworkReader(ctx context.Context, artwork *artwork, artID model.ArtworkID) (*mediafileArtworkReader, error) {
mf, err := artwork.ds.MediaFile(ctx).Get(artID.ID)
if err != nil {
return nil, err
}
al, err := artwork.ds.Album(ctx).Get(mf.AlbumID)
if err != nil {
return nil, err
}
a := &mediafileArtworkReader{
a: artwork,
mediafile: *mf,
album: *al,
}
a.cacheKey.artID = artID
if al.UpdatedAt.After(mf.UpdatedAt) {
a.cacheKey.lastUpdate = al.UpdatedAt
} else {
a.cacheKey.lastUpdate = mf.UpdatedAt
}
return a, nil
}
func (a *mediafileArtworkReader) Key() string {
return fmt.Sprintf(
"%s.%t",
a.cacheKey.Key(),
conf.Server.EnableMediaFileCoverArt,
)
}
func (a *mediafileArtworkReader) LastUpdated() time.Time {
return a.lastUpdate
}
func (a *mediafileArtworkReader) Reader(ctx context.Context) (io.ReadCloser, string, error) {
var ff []sourceFunc
if a.mediafile.CoverArtID().Kind == model.KindMediaFileArtwork {
ff = []sourceFunc{
fromTag(a.mediafile.Path),
fromFFmpegTag(ctx, a.a.ffmpeg, a.mediafile.Path),
}
}
ff = append(ff, fromAlbum(ctx, a.a, a.mediafile.AlbumCoverArtID()))
return selectImageReader(ctx, a.artID, ff...)
}

View File

@ -0,0 +1,150 @@
package artwork
import (
"bytes"
"context"
"errors"
"image"
"image/draw"
"image/png"
"io"
"time"
"github.com/disintegration/imaging"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/utils/slice"
)
type playlistArtworkReader struct {
cacheKey
a *artwork
pl model.Playlist
}
const tileSize = 600
func newPlaylistArtworkReader(ctx context.Context, artwork *artwork, artID model.ArtworkID) (*playlistArtworkReader, error) {
pl, err := artwork.ds.Playlist(ctx).Get(artID.ID)
if err != nil {
return nil, err
}
a := &playlistArtworkReader{
a: artwork,
pl: *pl,
}
a.cacheKey.artID = artID
a.cacheKey.lastUpdate = pl.UpdatedAt
return a, nil
}
func (a *playlistArtworkReader) LastUpdated() time.Time {
return a.lastUpdate
}
func (a *playlistArtworkReader) Reader(ctx context.Context) (io.ReadCloser, string, error) {
ff := []sourceFunc{
a.fromGeneratedTiledCover(ctx),
fromAlbumPlaceholder(),
}
return selectImageReader(ctx, a.artID, ff...)
}
func (a *playlistArtworkReader) fromGeneratedTiledCover(ctx context.Context) sourceFunc {
return func() (io.ReadCloser, string, error) {
tiles, err := a.loadTiles(ctx)
if err != nil {
return nil, "", err
}
r, err := a.createTiledImage(ctx, tiles)
return r, "", err
}
}
func toArtworkIDs(albumIDs []string) []model.ArtworkID {
return slice.Map(albumIDs, func(id string) model.ArtworkID {
al := model.Album{ID: id}
return al.CoverArtID()
})
}
func (a *playlistArtworkReader) loadTiles(ctx context.Context) ([]image.Image, error) {
tracksRepo := a.a.ds.Playlist(ctx).Tracks(a.pl.ID, false)
albumIds, err := tracksRepo.GetAlbumIDs(model.QueryOptions{Max: 4, Sort: "random()"})
if err != nil {
log.Error(ctx, "Error getting album IDs for playlist", "id", a.pl.ID, "name", a.pl.Name, err)
return nil, err
}
ids := toArtworkIDs(albumIds)
var tiles []image.Image
for len(tiles) < 4 {
if len(ids) == 0 {
break
}
id := ids[len(ids)-1]
ids = ids[0 : len(ids)-1]
r, _, err := fromAlbum(ctx, a.a, id)()
if err != nil {
continue
}
tile, err := a.createTile(ctx, r)
if err == nil {
tiles = append(tiles, tile)
}
_ = r.Close()
}
switch len(tiles) {
case 0:
return nil, errors.New("could not find any eligible cover")
case 2:
tiles = append(tiles, tiles[1], tiles[0])
case 3:
tiles = append(tiles, tiles[0])
}
return tiles, nil
}
func (a *playlistArtworkReader) createTile(_ context.Context, r io.ReadCloser) (image.Image, error) {
img, _, err := image.Decode(r)
if err != nil {
return nil, err
}
return imaging.Fill(img, tileSize/2, tileSize/2, imaging.Center, imaging.Lanczos), nil
}
func (a *playlistArtworkReader) createTiledImage(_ context.Context, tiles []image.Image) (io.ReadCloser, error) {
buf := new(bytes.Buffer)
var rgba draw.Image
var err error
if len(tiles) == 4 {
rgba = image.NewRGBA(image.Rectangle{Max: image.Point{X: tileSize - 1, Y: tileSize - 1}})
draw.Draw(rgba, rect(0), tiles[0], image.Point{}, draw.Src)
draw.Draw(rgba, rect(1), tiles[1], image.Point{}, draw.Src)
draw.Draw(rgba, rect(2), tiles[2], image.Point{}, draw.Src)
draw.Draw(rgba, rect(3), tiles[3], image.Point{}, draw.Src)
err = png.Encode(buf, rgba)
} else {
err = png.Encode(buf, tiles[0])
}
if err != nil {
return nil, err
}
return io.NopCloser(buf), nil
}
func rect(pos int) image.Rectangle {
r := image.Rectangle{}
switch pos {
case 1:
r.Min.X = tileSize / 2
case 2:
r.Min.Y = tileSize / 2
case 3:
r.Min.X = tileSize / 2
r.Min.Y = tileSize / 2
}
r.Max.X = r.Min.X + tileSize/2
r.Max.Y = r.Min.Y + tileSize/2
return r
}

View File

@ -0,0 +1,107 @@
package artwork
import (
"bytes"
"context"
"fmt"
"image"
"image/jpeg"
"image/png"
"io"
"time"
"github.com/disintegration/imaging"
"github.com/navidrome/navidrome/conf"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
)
type resizedArtworkReader struct {
artID model.ArtworkID
cacheKey string
lastUpdate time.Time
size int
a *artwork
}
func resizedFromOriginal(ctx context.Context, a *artwork, artID model.ArtworkID, size int) (*resizedArtworkReader, error) {
r := &resizedArtworkReader{a: a}
r.artID = artID
r.size = size
// Get lastUpdated and cacheKey from original artwork
original, err := a.getArtworkReader(ctx, artID, 0)
if err != nil {
return nil, err
}
r.cacheKey = original.Key()
r.lastUpdate = original.LastUpdated()
return r, nil
}
func (a *resizedArtworkReader) Key() string {
return fmt.Sprintf(
"%s.%d.%d",
a.cacheKey,
a.size,
conf.Server.CoverJpegQuality,
)
}
func (a *resizedArtworkReader) LastUpdated() time.Time {
return a.lastUpdate
}
func (a *resizedArtworkReader) Reader(ctx context.Context) (io.ReadCloser, string, error) {
// Get artwork in original size, possibly from cache
orig, _, err := a.a.Get(ctx, a.artID, 0)
if err != nil {
return nil, "", err
}
// Keep a copy of the original data. In case we can't resize it, send it as is
buf := new(bytes.Buffer)
r := io.TeeReader(orig, buf)
defer orig.Close()
resized, origSize, err := resizeImage(r, a.size)
if resized == nil {
log.Trace(ctx, "Image smaller than requested size", "artID", a.artID, "original", origSize, "resized", a.size)
} else {
log.Trace(ctx, "Resizing artwork", "artID", a.artID, "original", origSize, "resized", a.size)
}
if err != nil {
log.Warn(ctx, "Could not resize image. Will return image as is", "artID", a.artID, "size", a.size, err)
}
if err != nil || resized == nil {
// Force finish reading any remaining data
_, _ = io.Copy(io.Discard, r)
return io.NopCloser(buf), "", nil //nolint:nilerr
}
return io.NopCloser(resized), fmt.Sprintf("%s@%d", a.artID, a.size), nil
}
func resizeImage(reader io.Reader, size int) (io.Reader, int, error) {
original, format, err := image.Decode(reader)
if err != nil {
return nil, 0, err
}
bounds := original.Bounds()
originalSize := max(bounds.Max.X, bounds.Max.Y)
// Don't upscale the image
if originalSize <= size {
return nil, originalSize, nil
}
resized := imaging.Fit(original, size, size, imaging.Lanczos)
buf := new(bytes.Buffer)
if format == "png" {
err = png.Encode(buf, resized)
} else {
err = jpeg.Encode(buf, resized, &jpeg.Options{Quality: conf.Server.CoverJpegQuality})
}
return buf, originalSize, err
}

175
core/artwork/sources.go Normal file
View File

@ -0,0 +1,175 @@
package artwork
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"net/url"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"time"
"github.com/dhowden/tag"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/core"
"github.com/navidrome/navidrome/core/ffmpeg"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/resources"
)
func selectImageReader(ctx context.Context, artID model.ArtworkID, extractFuncs ...sourceFunc) (io.ReadCloser, string, error) {
for _, f := range extractFuncs {
if ctx.Err() != nil {
return nil, "", ctx.Err()
}
start := time.Now()
r, path, err := f()
if r != nil {
msg := fmt.Sprintf("Found %s artwork", artID.Kind)
log.Debug(ctx, msg, "artID", artID, "path", path, "source", f, "elapsed", time.Since(start))
return r, path, nil
}
log.Trace(ctx, "Failed trying to extract artwork", "artID", artID, "source", f, "elapsed", time.Since(start), err)
}
return nil, "", fmt.Errorf("could not get a cover art for %s: %w", artID, ErrUnavailable)
}
type sourceFunc func() (r io.ReadCloser, path string, err error)
func (f sourceFunc) String() string {
name := runtime.FuncForPC(reflect.ValueOf(f).Pointer()).Name()
name = strings.TrimPrefix(name, "github.com/navidrome/navidrome/core/artwork.")
if _, after, found := strings.Cut(name, ")."); found {
name = after
}
name = strings.TrimSuffix(name, ".func1")
return name
}
func splitList(s string) []string {
return strings.Split(s, consts.Zwsp)
}
func fromExternalFile(ctx context.Context, files string, pattern string) sourceFunc {
return func() (io.ReadCloser, string, error) {
for _, file := range splitList(files) {
_, name := filepath.Split(file)
match, err := filepath.Match(pattern, strings.ToLower(name))
if err != nil {
log.Warn(ctx, "Error matching cover art file to pattern", "pattern", pattern, "file", file)
continue
}
if !match {
continue
}
f, err := os.Open(file)
if err != nil {
log.Warn(ctx, "Could not open cover art file", "file", file, err)
continue
}
return f, file, err
}
return nil, "", fmt.Errorf("pattern '%s' not matched by files %v", pattern, files)
}
}
func fromTag(path string) sourceFunc {
return func() (io.ReadCloser, string, error) {
if path == "" {
return nil, "", nil
}
f, err := os.Open(path)
if err != nil {
return nil, "", err
}
defer f.Close()
m, err := tag.ReadFrom(f)
if err != nil {
return nil, "", err
}
picture := m.Picture()
if picture == nil {
return nil, "", fmt.Errorf("no embedded image found in %s", path)
}
return io.NopCloser(bytes.NewReader(picture.Data)), path, nil
}
}
func fromFFmpegTag(ctx context.Context, ffmpeg ffmpeg.FFmpeg, path string) sourceFunc {
return func() (io.ReadCloser, string, error) {
if path == "" {
return nil, "", nil
}
r, err := ffmpeg.ExtractImage(ctx, path)
if err != nil {
return nil, "", err
}
defer r.Close()
buf := new(bytes.Buffer)
_, err = io.Copy(buf, r)
if err != nil {
return nil, "", err
}
return io.NopCloser(buf), path, nil
}
}
func fromAlbum(ctx context.Context, a *artwork, id model.ArtworkID) sourceFunc {
return func() (io.ReadCloser, string, error) {
r, _, err := a.Get(ctx, id, 0)
if err != nil {
return nil, "", err
}
return r, id.String(), nil
}
}
func fromAlbumPlaceholder() sourceFunc {
return func() (io.ReadCloser, string, error) {
r, _ := resources.FS().Open(consts.PlaceholderAlbumArt)
return r, consts.PlaceholderAlbumArt, nil
}
}
func fromArtistExternalSource(ctx context.Context, ar model.Artist, em core.ExternalMetadata) sourceFunc {
return func() (io.ReadCloser, string, error) {
imageUrl, err := em.ArtistImage(ctx, ar.ID)
if err != nil {
return nil, "", err
}
return fromURL(ctx, imageUrl)
}
}
func fromAlbumExternalSource(ctx context.Context, al model.Album, em core.ExternalMetadata) sourceFunc {
return func() (io.ReadCloser, string, error) {
imageUrl, err := em.AlbumImage(ctx, al.ID)
if err != nil {
return nil, "", err
}
return fromURL(ctx, imageUrl)
}
}
func fromURL(ctx context.Context, imageUrl *url.URL) (io.ReadCloser, string, error) {
hc := http.Client{Timeout: 5 * time.Second}
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, imageUrl.String(), nil)
resp, err := hc.Do(req)
if err != nil {
return nil, "", err
}
if resp.StatusCode != http.StatusOK {
resp.Body.Close()
return nil, "", fmt.Errorf("error retrieveing artwork from %s: %s", imageUrl, resp.Status)
}
return resp.Body, imageUrl.String(), nil
}

View File

@ -0,0 +1,11 @@
package artwork
import (
"github.com/google/wire"
)
var Set = wire.NewSet(
NewArtwork,
GetImageCache,
NewCacheWarmer,
)

View File

@ -1,142 +0,0 @@
package core
import (
"bytes"
"context"
"image"
"io/ioutil"
"os"
"github.com/deluan/navidrome/conf"
"github.com/deluan/navidrome/log"
"github.com/deluan/navidrome/model"
"github.com/deluan/navidrome/persistence"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Artwork", func() {
var artwork Artwork
var ds model.DataStore
ctx := log.NewContext(context.TODO())
BeforeEach(func() {
ds = &persistence.MockDataStore{MockedTranscoding: &mockTranscodingRepository{}}
ds.Album(ctx).(*persistence.MockAlbum).SetData(`[{"id": "222", "coverArtId": "123", "coverArtPath":"tests/fixtures/test.mp3"}, {"id": "333", "coverArtId": ""}, {"id": "444", "coverArtId": "444", "coverArtPath": "tests/fixtures/cover.jpg"}]`)
ds.MediaFile(ctx).(*persistence.MockMediaFile).SetData(`[{"id": "123", "albumId": "222", "path": "tests/fixtures/test.mp3", "hasCoverArt": true, "updatedAt":"2020-04-02T21:29:31.6377Z"},{"id": "456", "albumId": "222", "path": "tests/fixtures/test.ogg", "hasCoverArt": false, "updatedAt":"2020-04-02T21:29:31.6377Z"}]`)
})
Context("Cache is configured", func() {
BeforeEach(func() {
conf.Server.DataFolder, _ = ioutil.TempDir("", "file_caches")
conf.Server.ImageCacheSize = "100MB"
cache := NewImageCache()
Eventually(func() bool { return cache.Ready() }).Should(BeTrue())
artwork = NewArtwork(ds, cache)
})
AfterEach(func() {
os.RemoveAll(conf.Server.DataFolder)
})
It("retrieves the external artwork art for an album", func() {
buf := new(bytes.Buffer)
Expect(artwork.Get(ctx, "al-444", 0, buf)).To(BeNil())
_, format, err := image.Decode(bytes.NewReader(buf.Bytes()))
Expect(err).To(BeNil())
Expect(format).To(Equal("jpeg"))
})
It("retrieves the embedded artwork art for an album", func() {
buf := new(bytes.Buffer)
Expect(artwork.Get(ctx, "al-222", 0, buf)).To(BeNil())
_, format, err := image.Decode(bytes.NewReader(buf.Bytes()))
Expect(err).To(BeNil())
Expect(format).To(Equal("jpeg"))
})
It("returns the default artwork if album does not have artwork", func() {
buf := new(bytes.Buffer)
Expect(artwork.Get(ctx, "al-333", 0, buf)).To(BeNil())
_, format, err := image.Decode(bytes.NewReader(buf.Bytes()))
Expect(err).To(BeNil())
Expect(format).To(Equal("png"))
})
It("returns the default artwork if album is not found", func() {
buf := new(bytes.Buffer)
Expect(artwork.Get(ctx, "al-0101", 0, buf)).To(BeNil())
_, format, err := image.Decode(bytes.NewReader(buf.Bytes()))
Expect(err).To(BeNil())
Expect(format).To(Equal("png"))
})
It("retrieves the original artwork art from a media_file", func() {
buf := new(bytes.Buffer)
Expect(artwork.Get(ctx, "123", 0, buf)).To(BeNil())
img, format, err := image.Decode(bytes.NewReader(buf.Bytes()))
Expect(err).To(BeNil())
Expect(format).To(Equal("jpeg"))
Expect(img.Bounds().Size().X).To(Equal(600))
Expect(img.Bounds().Size().Y).To(Equal(600))
})
It("retrieves the album artwork art if media_file does not have one", func() {
buf := new(bytes.Buffer)
Expect(artwork.Get(ctx, "456", 0, buf)).To(BeNil())
_, format, err := image.Decode(bytes.NewReader(buf.Bytes()))
Expect(err).To(BeNil())
Expect(format).To(Equal("jpeg"))
})
It("retrieves the album artwork by album id", func() {
buf := new(bytes.Buffer)
Expect(artwork.Get(ctx, "222", 0, buf)).To(BeNil())
_, format, err := image.Decode(bytes.NewReader(buf.Bytes()))
Expect(err).To(BeNil())
Expect(format).To(Equal("jpeg"))
})
It("resized artwork art as requested", func() {
buf := new(bytes.Buffer)
Expect(artwork.Get(ctx, "123", 200, buf)).To(BeNil())
img, format, err := image.Decode(bytes.NewReader(buf.Bytes()))
Expect(err).To(BeNil())
Expect(format).To(Equal("jpeg"))
Expect(img.Bounds().Size().X).To(Equal(200))
Expect(img.Bounds().Size().Y).To(Equal(200))
})
Context("Errors", func() {
It("returns err if gets error from album table", func() {
ds.Album(ctx).(*persistence.MockAlbum).SetError(true)
buf := new(bytes.Buffer)
Expect(artwork.Get(ctx, "al-222", 0, buf)).To(MatchError("Error!"))
})
It("returns err if gets error from media_file table", func() {
ds.MediaFile(ctx).(*persistence.MockMediaFile).SetError(true)
buf := new(bytes.Buffer)
Expect(artwork.Get(ctx, "123", 0, buf)).To(MatchError("Error!"))
})
})
})
})

Some files were not shown because too many files have changed in this diff Show More