From 4e1b422e3130e29753d4433b8bf1c6a5e4e572d3 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 22 Sep 2024 09:44:17 +0200 Subject: [PATCH 01/13] Fixed last join phrase not being displayed in UI, fixes #528 --- src/libs/metadata/test/Parser.cpp | 17 +++++++++++++++++ src/lms/ui/Utils.cpp | 10 ++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/libs/metadata/test/Parser.cpp b/src/libs/metadata/test/Parser.cpp index fd8ff770..f683c80f 100644 --- a/src/libs/metadata/test/Parser.cpp +++ b/src/libs/metadata/test/Parser.cpp @@ -337,6 +337,23 @@ namespace lms::metadata EXPECT_EQ(track->artistDisplayName, "Artist1, Artist2"); // reconstruct artist display name since multiple entries are found and nothing is set in artist } + TEST(Parser, multipleArtistsInArtistsWithEndDelimiter) + { + const TestTagReader testTags{ + { + { TagType::Artist, { "Artist1 & (CV. Artist2)" } }, + { TagType::Artists, { "Artist1", "Artist2" } }, + } + }; + + std::unique_ptr track{ Parser{}.parse(testTags) }; + + ASSERT_EQ(track->artists.size(), 2); + EXPECT_EQ(track->artists[0].name, "Artist1"); + EXPECT_EQ(track->artists[1].name, "Artist2"); + EXPECT_EQ(track->artistDisplayName, "Artist1 & (CV. Artist2)"); + } + TEST(Parser, singleArtistInAlbumArtists) { const TestTagReader testTags{ diff --git a/src/lms/ui/Utils.cpp b/src/lms/ui/Utils.cpp index 1fef4158..216dc518 100644 --- a/src/lms/ui/Utils.cpp +++ b/src/lms/ui/Utils.cpp @@ -212,8 +212,14 @@ namespace lms::ui::utils matchCount += 1; } - if (matchCount != artistIds.size()) - return createArtistAnchorList(artistIds, cssAnchorClass); + if (matchCount == artistIds.size()) + { + const std::string_view remainingStr{ displayName.substr(currentOffset) }; + if (!remainingStr.empty()) + result->addNew(std::string{ remainingStr }, Wt::TextFormat::Plain); + } + else + result = createArtistAnchorList(artistIds, cssAnchorClass); return result; } From 3f6177cedd4dc20d295948b9b6b37ae979bdd9f2 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 29 Sep 2024 16:21:46 +0200 Subject: [PATCH 02/13] Changed the way album covers are associated, fixes #503 --- approot/messages.xml | 1 + approot/messages_fr.xml | 1 + approot/messages_it.xml | 1 + approot/messages_pl.xml | 1 + approot/messages_zh.xml | 1 + conf/lms.conf | 5 +- src/libs/database/impl/Artist.cpp | 1 - src/libs/database/impl/Image.cpp | 3 +- src/libs/database/impl/Migration.cpp | 51 +++- src/libs/database/impl/Release.cpp | 13 +- src/libs/database/impl/Session.cpp | 5 +- src/libs/database/impl/Track.cpp | 3 + src/libs/database/include/database/Image.hpp | 15 +- .../database/include/database/Release.hpp | 5 + src/libs/database/include/database/Track.hpp | 6 + src/libs/services/cover/impl/CoverService.cpp | 212 ++-------------- src/libs/services/cover/impl/CoverService.hpp | 10 +- .../include/services/cover/ICoverService.hpp | 8 +- src/libs/services/scanner/CMakeLists.txt | 1 + .../impl/ScanStepAssociateReleaseImages.cpp | 234 ++++++++++++++++++ .../impl/ScanStepAssociateReleaseImages.hpp | 41 +++ .../services/scanner/impl/ScannerService.cpp | 2 + .../include/services/scanner/ScannerStats.hpp | 1 + src/libs/subsonic/impl/responses/Album.cpp | 18 +- src/libs/subsonic/impl/responses/Artist.cpp | 2 +- src/libs/subsonic/impl/responses/Playlist.cpp | 13 +- src/libs/subsonic/impl/responses/Song.cpp | 3 +- src/lms/ui/Utils.cpp | 53 +++- src/lms/ui/admin/ScannerController.cpp | 5 + src/lms/ui/resource/CoverResource.cpp | 17 +- src/lms/ui/resource/CoverResource.hpp | 1 + 31 files changed, 502 insertions(+), 231 deletions(-) create mode 100644 src/libs/services/scanner/impl/ScanStepAssociateReleaseImages.cpp create mode 100644 src/libs/services/scanner/impl/ScanStepAssociateReleaseImages.hpp diff --git a/approot/messages.xml b/approot/messages.xml index 2cfb7606..c42f4514 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -110,6 +110,7 @@ Scheduled on {1} Scanning: step {1}/{2} Associating artist images: {1}%... +Associating release images: {1}%... Checking for duplicate files... {1} files Checking for removed files... {1}% Compacting database... diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index 1f216c86..d160d292 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -110,6 +110,7 @@ Planifié le {1} En cours de scan : étape {1}/{2} Association des images des artistes: {1}%... +Association des images des albums: {1}%... Vérification des fichiers dupliqués... {1} fichiers Vérification des fichiers supprimés... {1}% Compactage de la base de données... diff --git a/approot/messages_it.xml b/approot/messages_it.xml index a1daeb59..30127191 100644 --- a/approot/messages_it.xml +++ b/approot/messages_it.xml @@ -110,6 +110,7 @@ Pianificato il {1} Scansione: passo {1}/{2} Associando immagini degli artisti: {1}%... +Associando immagini degli album: {1}%... Controllo duplicati... {1} files Controllo file... {1}% Compattazione del database... diff --git a/approot/messages_pl.xml b/approot/messages_pl.xml index a28bcb89..90c1c269 100644 --- a/approot/messages_pl.xml +++ b/approot/messages_pl.xml @@ -119,6 +119,7 @@ Zaplanowano na {1} Skanowanie: krok {1}/{2} Kojarzenie obrazów artystów: {1}%... +Kojarzenie obrazów albumów: {1}%... Sprawdzanie duplikatów... {1} plik Sprawdzanie duplikatów... {1} pliki diff --git a/approot/messages_zh.xml b/approot/messages_zh.xml index 97f1b500..62206fe4 100644 --- a/approot/messages_zh.xml +++ b/approot/messages_zh.xml @@ -114,6 +114,7 @@ + 检索文件中: {1} 文件 从 AcousticBrainz 获取音轨特征: {1}/{2} 音轨 ({3}%)... diff --git a/conf/lms.conf b/conf/lms.conf index 4fcca840..82b565ce 100644 --- a/conf/lms.conf +++ b/conf/lms.conf @@ -74,9 +74,6 @@ api-open-subsonic-disabled-clients = ("DSub"); # Turn on this option to allow the demo account creation/use demo = false; -# Max external cover file size in MBytes -cover-max-file-size = 10; - # Max cover cache size in MBytes cover-max-cache-size = 30; @@ -84,7 +81,7 @@ cover-max-cache-size = 30; cover-jpeg-quality = 75; # Preferred file names for covers (order is important) -cover-preferred-file-names = ("cover", "front"); +cover-preferred-file-names = ("cover", "front", "folder", "default"); # File names for artist images (order is important) # Note: files whose name is the artist's MBID are always searched before the names in this list. You can place the MBID files anywhere in your libraries. diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index 64d1a03c..cd49ff94 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -379,5 +379,4 @@ namespace lms::db { _image = getDboPtr(image); } - } // namespace lms::db diff --git a/src/libs/database/impl/Image.cpp b/src/libs/database/impl/Image.cpp index 62db5af7..e2a0d02b 100644 --- a/src/libs/database/impl/Image.cpp +++ b/src/libs/database/impl/Image.cpp @@ -23,6 +23,7 @@ #include "database/Artist.hpp" #include "database/Directory.hpp" +#include "database/Release.hpp" #include "database/Session.hpp" #include "IdTypeTraits.hpp" @@ -40,7 +41,7 @@ namespace lms::db if (params.directory.isValid()) query.where("i.directory_id = ?").bind(params.directory); if (!params.fileStem.empty()) - query.where("i.stem = ?").bind(params.fileStem); + query.where("i.stem = ? COLLATE NOCASE").bind(params.fileStem); return query; } diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index b75f5c4b..5d869de5 100644 --- a/src/libs/database/impl/Migration.cpp +++ b/src/libs/database/impl/Migration.cpp @@ -35,7 +35,7 @@ namespace lms::db { namespace { - static constexpr Version LMS_DATABASE_VERSION{ 67 }; + static constexpr Version LMS_DATABASE_VERSION{ 68 }; } VersionInfo::VersionInfo() @@ -761,6 +761,54 @@ SELECT session.getDboSession()->execute("ALTER TABLE user DROP COLUMN cur_playing_track_pos"); } + void migrateFromV67(Session& session) + { + // Add a ref to release in image + session.getDboSession()->execute(R"(CREATE TABLE "image_backup" ( + "id" integer primary key autoincrement, + "version" integer not null, + "absolute_file_path" text not null, + "stem" text not null, + "file_last_write" text, + "file_size" integer not null, + "width" integer not null, + "height" integer not null, + "artist_id" bigint, + "release_id" bigint, + "directory_id" bigint, + constraint "fk_image_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred, + constraint "fk_image_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred, + constraint "fk_image_directory" foreign key ("directory_id") references "directory" ("id") on delete cascade deferrable initially deferred +))"); + + // Migrate data, with the new release_id field set to null + session.getDboSession()->execute(R"(INSERT INTO image_backup +SELECT + id, + version, + absolute_file_path, + stem, + file_last_write, + file_size, + width, + height, + artist_id, + NULL, + directory_id + FROM image + )"); + session.getDboSession()->execute("DROP TABLE image"); + session.getDboSession()->execute("ALTER TABLE image_backup RENAME TO image"); + + // Changed some indexes for the image table -> remove all the previoulsy created index, the createIndexesIfNeeded will recreate them all + std::vector indexeNames{ utils::fetchQueryResults(session.getDboSession()->query(R"(SELECT name FROM sqlite_master WHERE type = 'index' AND name LIKE '%_idx')")) }; + for (const auto& indexName : indexeNames) + session.getDboSession()->execute("DROP INDEX " + indexName); + + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } + bool doDbMigration(Session& session) { static const std::string outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" }; @@ -804,6 +852,7 @@ SELECT { 64, migrateFromV64 }, { 65, migrateFromV65 }, { 66, migrateFromV66 }, + { 67, migrateFromV67 }, }; bool migrationPerformed{}; diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/Release.cpp index 16f723de..687a4353 100644 --- a/src/libs/database/impl/Release.cpp +++ b/src/libs/database/impl/Release.cpp @@ -24,6 +24,8 @@ #include "core/ILogger.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" +#include "database/Directory.hpp" +#include "database/Image.hpp" #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" @@ -540,6 +542,11 @@ namespace lms::db return utils::fetchQueryResults(query); } + ObjectPtr Release::getImage() const + { + return ObjectPtr{ _image.lock() }; + } + void Release::clearLabels() { _labels.clear(); @@ -560,6 +567,11 @@ namespace lms::db _releaseTypes.insert(getDboPtr(releaseType)); } + void Release::setImage(ObjectPtr image) + { + _image = getDboPtr(image); + } + bool Release::hasVariousArtists() const { // TODO optimize @@ -665,5 +677,4 @@ namespace lms::db return res; } - } // namespace lms::db diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 14106053..c3380bcb 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -196,10 +196,11 @@ namespace lms::db _session.execute("CREATE INDEX IF NOT EXISTS directory_media_library_idx ON directory(media_library_id)"); _session.execute("CREATE INDEX IF NOT EXISTS image_artist_idx ON image(artist_id)"); - _session.execute("CREATE INDEX IF NOT EXISTS image_directory_idx ON image(directory_id)"); + _session.execute("CREATE INDEX IF NOT EXISTS image_directory_stem_idx ON image(directory_id, stem COLLATE NOCASE)"); _session.execute("CREATE INDEX IF NOT EXISTS image_id_idx ON image(id)"); _session.execute("CREATE INDEX IF NOT EXISTS image_path_idx ON image(absolute_file_path)"); - _session.execute("CREATE INDEX IF NOT EXISTS image_stem_idx ON image(stem)"); + _session.execute("CREATE INDEX IF NOT EXISTS image_release_idx ON image(release_id)"); + _session.execute("CREATE INDEX IF NOT EXISTS image_stem_idx ON image(stem COLLATE NOCASE)"); _session.execute("CREATE INDEX IF NOT EXISTS label_name_idx ON label(name)"); diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp index cefcb539..c60e946d 100644 --- a/src/libs/database/impl/Track.cpp +++ b/src/libs/database/impl/Track.cpp @@ -157,6 +157,9 @@ namespace lms::db if (params.directory.isValid()) query.where("t.directory_id = ?").bind(params.directory); + if (params.hasEmbeddedImage.has_value()) + query.where("t.has_cover = ?").bind(params.hasEmbeddedImage.value()); + switch (params.sortMethod) { case TrackSortMethod::None: diff --git a/src/libs/database/include/database/Image.hpp b/src/libs/database/include/database/Image.hpp index d2081835..6985e6ab 100644 --- a/src/libs/database/include/database/Image.hpp +++ b/src/libs/database/include/database/Image.hpp @@ -25,7 +25,6 @@ #include #include -#include "database/ArtistId.hpp" #include "database/DirectoryId.hpp" #include "database/ImageId.hpp" #include "database/Object.hpp" @@ -35,6 +34,7 @@ namespace lms::db { class Artist; class Directory; + class Release; class Session; class Image final : public Object @@ -87,7 +87,16 @@ namespace lms::db void setFileSize(std::size_t fileSize) { _fileSize = fileSize; } void setWidth(std::size_t width) { _width = width; } void setHeight(std::size_t height) { _height = height; } - void setArtist(const ObjectPtr& artist) { _artist = getDboPtr(artist); } + void setArtist(const ObjectPtr& artist) + { + _artist = getDboPtr(artist); + _release = nullptr; + } + void setRelease(const ObjectPtr& release) + { + _release = getDboPtr(release); + _artist = nullptr; + } void setDirectory(const ObjectPtr& directory) { _directory = getDboPtr(directory); } template @@ -102,6 +111,7 @@ namespace lms::db Wt::Dbo::field(a, _height, "height"); Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade); Wt::Dbo::belongsTo(a, _directory, "directory", Wt::Dbo::OnDeleteCascade); } @@ -118,6 +128,7 @@ namespace lms::db int _height{}; Wt::Dbo::ptr _artist; + Wt::Dbo::ptr _release; Wt::Dbo::ptr _directory; }; } // namespace lms::db diff --git a/src/libs/database/include/database/Release.hpp b/src/libs/database/include/database/Release.hpp index 45db9fbe..11a22069 100644 --- a/src/libs/database/include/database/Release.hpp +++ b/src/libs/database/include/database/Release.hpp @@ -47,6 +47,7 @@ namespace lms::db class Artist; class Cluster; class ClusterType; + class Image; class Release; class Session; class Track; @@ -245,6 +246,7 @@ namespace lms::db std::vector getLabelNames() const; std::vector getReleaseTypeNames() const; void visitLabels(const std::function& _func) const; + ObjectPtr getImage() const; // Setters void setName(std::string_view name) { _name = name; } @@ -258,6 +260,7 @@ namespace lms::db void clearReleaseTypes(); void addLabel(ObjectPtr