From c4b05dac28067c8c47e8fd40f09b1c5a1235ce21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Deluan=20Quint=C3=A3o?= Date: Thu, 9 May 2024 07:08:15 -0400 Subject: [PATCH] Make sorting lists by name/title case-insensitive (#2993) * Make sort by order_* fields case-insensitive. * Sort internet radios by name case-insensitive --- core/external_metadata.go | 3 +-- persistence/album_repository.go | 1 + persistence/mediafile_repository.go | 1 + persistence/playlist_track_repository.go | 2 ++ persistence/radio_repository.go | 3 +++ scanner/mapping.go | 14 ++++-------- scanner/mapping_internal_test.go | 16 ------------- utils/strings.go | 11 +++++++++ utils/strings_test.go | 29 ++++++++++++++++++++++++ 9 files changed, 52 insertions(+), 28 deletions(-) diff --git a/core/external_metadata.go b/core/external_metadata.go index 33206bdeb..368e1cce1 100644 --- a/core/external_metadata.go +++ b/core/external_metadata.go @@ -9,7 +9,6 @@ import ( "time" "github.com/Masterminds/squirrel" - "github.com/deluan/sanitize" "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/core/agents" _ "github.com/navidrome/navidrome/core/agents/lastfm" @@ -414,7 +413,7 @@ func (e *externalMetadata) findMatchingTrack(ctx context.Context, mbid string, a squirrel.Eq{"artist_id": artistID}, squirrel.Eq{"album_artist_id": artistID}, }, - squirrel.Like{"order_title": strings.TrimSpace(sanitize.Accents(title))}, + squirrel.Like{"order_title": utils.SanitizeFieldForSorting(title)}, }, Sort: "starred desc, rating desc, year asc, compilation asc ", Max: 1, diff --git a/persistence/album_repository.go b/persistence/album_repository.go index fa9f7f1d8..e23d18974 100644 --- a/persistence/album_repository.go +++ b/persistence/album_repository.go @@ -85,6 +85,7 @@ func NewAlbumRepository(ctx context.Context, db dbx.Builder) model.AlbumReposito r.sortMappings = map[string]string{ "name": "order_album_name asc, order_album_artist_name asc", "artist": "compilation asc, order_album_artist_name asc, order_album_name asc", + "albumArtist": "compilation asc, order_album_artist_name asc, order_album_name asc", "max_year": "coalesce(nullif(original_date,''), cast(max_year as text)), release_date, name, order_album_name asc", "random": "RANDOM()", "recently_added": recentlyAddedSort(), diff --git a/persistence/mediafile_repository.go b/persistence/mediafile_repository.go index 9766e57d3..5c018f34a 100644 --- a/persistence/mediafile_repository.go +++ b/persistence/mediafile_repository.go @@ -41,6 +41,7 @@ func NewMediaFileRepository(ctx context.Context, db dbx.Builder) *mediaFileRepos } } else { r.sortMappings = map[string]string{ + "title": "order_title", "artist": "order_artist_name asc, order_album_name asc, release_date asc, disc_number asc, track_number asc", "album": "order_album_name asc, release_date asc, disc_number asc, track_number asc, order_artist_name asc, title asc", "random": "RANDOM()", diff --git a/persistence/playlist_track_repository.go b/persistence/playlist_track_repository.go index beaa749f4..79e722ea9 100644 --- a/persistence/playlist_track_repository.go +++ b/persistence/playlist_track_repository.go @@ -30,10 +30,12 @@ func (r *playlistRepository) Tracks(playlistId string, refreshSmartPlaylist bool "id": "playlist_tracks.id", "artist": "order_artist_name asc", "album": "order_album_name asc, order_album_artist_name asc", + "title": "order_title", } if conf.Server.PreferSortTags { p.sortMappings["artist"] = "COALESCE(NULLIF(sort_artist_name,''),order_artist_name) asc" p.sortMappings["album"] = "COALESCE(NULLIF(sort_album_name,''),order_album_name)" + p.sortMappings["title"] = "COALESCE(NULLIF(sort_title,''),title)" } pls, err := r.Get(playlistId) diff --git a/persistence/radio_repository.go b/persistence/radio_repository.go index 0c0f64d3c..19a6474bf 100644 --- a/persistence/radio_repository.go +++ b/persistence/radio_repository.go @@ -26,6 +26,9 @@ func NewRadioRepository(ctx context.Context, db dbx.Builder) model.RadioReposito r.filterMappings = map[string]filterFunc{ "name": containsFilter, } + r.sortMappings = map[string]string{ + "name": "(name collate nocase), name", + } return r } diff --git a/scanner/mapping.go b/scanner/mapping.go index 26c6adbc1..3e56f3bc5 100644 --- a/scanner/mapping.go +++ b/scanner/mapping.go @@ -7,7 +7,6 @@ import ( "path/filepath" "strings" - "github.com/deluan/sanitize" "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/consts" "github.com/navidrome/navidrome/model" @@ -56,10 +55,10 @@ func (s MediaFileMapper) ToMediaFile(md metadata.Tags) model.MediaFile { mf.SortAlbumName = md.SortAlbum() mf.SortArtistName = md.SortArtist() mf.SortAlbumArtistName = md.SortAlbumArtist() - mf.OrderTitle = strings.TrimSpace(sanitize.Accents(mf.Title)) - mf.OrderAlbumName = sanitizeFieldForSorting(mf.Album) - mf.OrderArtistName = sanitizeFieldForSorting(mf.Artist) - mf.OrderAlbumArtistName = sanitizeFieldForSorting(mf.AlbumArtist) + mf.OrderTitle = utils.SanitizeFieldForSorting(mf.Title) + mf.OrderAlbumName = utils.SanitizeFieldForSortingNoArticle(mf.Album) + mf.OrderArtistName = utils.SanitizeFieldForSortingNoArticle(mf.Artist) + mf.OrderAlbumArtistName = utils.SanitizeFieldForSortingNoArticle(mf.AlbumArtist) mf.CatalogNum = md.CatalogNum() mf.MbzRecordingID = md.MbzRecordingID() mf.MbzReleaseTrackID = md.MbzReleaseTrackID() @@ -81,11 +80,6 @@ func (s MediaFileMapper) ToMediaFile(md metadata.Tags) model.MediaFile { return *mf } -func sanitizeFieldForSorting(originalValue string) string { - v := strings.TrimSpace(sanitize.Accents(originalValue)) - return utils.NoArticle(v) -} - func (s MediaFileMapper) mapTrackTitle(md metadata.Tags) string { if md.Title() == "" { s := strings.TrimPrefix(md.FilePath(), s.rootFolder+string(os.PathSeparator)) diff --git a/scanner/mapping_internal_test.go b/scanner/mapping_internal_test.go index b44f2bcfb..882af1611 100644 --- a/scanner/mapping_internal_test.go +++ b/scanner/mapping_internal_test.go @@ -3,7 +3,6 @@ package scanner import ( "context" - "github.com/navidrome/navidrome/conf" "github.com/navidrome/navidrome/model" "github.com/navidrome/navidrome/scanner/metadata" "github.com/navidrome/navidrome/tests" @@ -161,19 +160,4 @@ var _ = Describe("mapping", func() { }) }) }) - - Describe("sanitizeFieldForSorting", func() { - BeforeEach(func() { - conf.Server.IgnoredArticles = "The O" - }) - It("sanitize accents", func() { - Expect(sanitizeFieldForSorting("Céu")).To(Equal("Ceu")) - }) - It("removes articles", func() { - Expect(sanitizeFieldForSorting("The Beatles")).To(Equal("Beatles")) - }) - It("removes accented articles", func() { - Expect(sanitizeFieldForSorting("Õ Blésq Blom")).To(Equal("Blesq Blom")) - }) - }) }) diff --git a/utils/strings.go b/utils/strings.go index 1bd26b10a..42866a48c 100644 --- a/utils/strings.go +++ b/utils/strings.go @@ -3,6 +3,7 @@ package utils import ( "strings" + "github.com/deluan/sanitize" "github.com/navidrome/navidrome/conf" ) @@ -32,3 +33,13 @@ func LongestCommonPrefix(list []string) string { } return list[0] } + +func SanitizeFieldForSorting(originalValue string) string { + v := strings.TrimSpace(sanitize.Accents(originalValue)) + return strings.ToLower(v) +} + +func SanitizeFieldForSortingNoArticle(originalValue string) string { + v := strings.TrimSpace(sanitize.Accents(originalValue)) + return strings.ToLower(NoArticle(v)) +} diff --git a/utils/strings_test.go b/utils/strings_test.go index 4b24dab0c..0d7b72e61 100644 --- a/utils/strings_test.go +++ b/utils/strings_test.go @@ -35,6 +35,35 @@ var _ = Describe("Strings", func() { }) }) + Describe("sanitizeFieldForSorting", func() { + BeforeEach(func() { + conf.Server.IgnoredArticles = "The O" + }) + It("sanitize accents", func() { + Expect(SanitizeFieldForSorting("Céu")).To(Equal("ceu")) + }) + It("removes articles", func() { + Expect(SanitizeFieldForSorting("The Beatles")).To(Equal("the beatles")) + }) + It("removes accented articles", func() { + Expect(SanitizeFieldForSorting("Õ Blésq Blom")).To(Equal("o blesq blom")) + }) + }) + Describe("SanitizeFieldForSortingNoArticle", func() { + BeforeEach(func() { + conf.Server.IgnoredArticles = "The O" + }) + It("sanitize accents", func() { + Expect(SanitizeFieldForSortingNoArticle("Céu")).To(Equal("ceu")) + }) + It("removes articles", func() { + Expect(SanitizeFieldForSortingNoArticle("The Beatles")).To(Equal("beatles")) + }) + It("removes accented articles", func() { + Expect(SanitizeFieldForSortingNoArticle("Õ Blésq Blom")).To(Equal("blesq blom")) + }) + }) + Describe("LongestCommonPrefix", func() { It("finds the longest common prefix", func() { Expect(LongestCommonPrefix(testPaths)).To(Equal("/Music/iTunes 1/iTunes Media/Music/"))