From a45f953b68247c17b29a254e64fd6736c3cf2f2a Mon Sep 17 00:00:00 2001 From: emeric Date: Mon, 30 Mar 2020 22:56:12 +0200 Subject: [PATCH 01/10] CMake >= 3.12 to properly work --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 69b4c9b6..1faefee5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.12) project(lms) From 5e8e46e77f263977e90ce9ea60ed0b7c47a8efa6 Mon Sep 17 00:00:00 2001 From: emeric Date: Wed, 1 Apr 2020 13:50:29 +0200 Subject: [PATCH 02/10] Force optimizing the database in case of crash/forced exit during scan --- src/libs/database/impl/Session.cpp | 9 ++++++--- src/lms/main.cpp | 1 + 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 63586f17..041bfc41 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -318,9 +318,12 @@ Session::prepareTables() void Session::optimize() { - auto uniqueTransaction {createUniqueTransaction()}; - - _session.execute("ANALYZE"); + LMS_LOG(DB, DEBUG) << "Optimizing db..."; + { + auto uniqueTransaction {createUniqueTransaction()}; + _session.execute("ANALYZE"); + } + LMS_LOG(DB, DEBUG) << "Optimized db!"; } } // namespace Database diff --git a/src/lms/main.cpp b/src/lms/main.cpp index 18dcd7f2..39d7eb15 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -135,6 +135,7 @@ int main(int argc, char* argv[]) { Database::Session session {database}; session.prepareTables(); + session.optimize(); } UserInterface::LmsApplicationGroupContainer appGroups; From 2bd4fb8c78b6b5482ec43099dedc2ddc4725e1a8 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 2 Apr 2020 20:54:31 +0200 Subject: [PATCH 03/10] WIP --- src/libs/database/impl/Artist.cpp | 6 +- src/libs/database/impl/Session.cpp | 8 ++- src/libs/database/include/database/Artist.hpp | 21 ++++-- src/libs/metadata/impl/AvFormatParser.cpp | 6 +- src/libs/metadata/impl/TagLibParser.cpp | 71 ++++++++++++------- .../metadata/include/metadata/IParser.hpp | 5 ++ src/libs/scanner/impl/MediaScanner.cpp | 49 ++++++++++--- src/tools/metadata/CMakeLists.txt | 1 + src/tools/metadata/LmsMetadata.cpp | 3 + 9 files changed, 122 insertions(+), 48 deletions(-) diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index b5c6d221..d2571e91 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -171,13 +171,13 @@ getQuery(Session& session, } std::vector -Artist::getByClusters(Session& session, const std::set& clusters) +Artist::getByClusters(Session& session, const std::set& clusters, NameSortMethod sortMethod) { assert(!clusters.empty()); session.checkSharedLocked(); bool more; - return getByFilter(session, clusters, {}, {}, {}, {}, more); + return getByFilter(session, clusters, {}, {}, sortMethod, {}, {}, more); } std::vector @@ -185,6 +185,7 @@ Artist::getByFilter(Session& session, const std::set& clusters, const std::vector& keywords, std::optional linkType, + NameSortMethod sortMethod, std::optional offset, std::optional size, bool& moreResults) @@ -405,6 +406,7 @@ void Artist::setSortName(const std::string& sortName) { _sortName = std::string(sortName, 0 , _maxNameLength); + LMS_LOG(DB, DEBUG) << "SORT NAME = '" << _sortName << "'"; } } // namespace Database diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 041bfc41..750fc32f 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -40,7 +40,7 @@ namespace Database { -#define LMS_DATABASE_VERSION 14 +#define LMS_DATABASE_VERSION 15 using Version = std::size_t; @@ -163,6 +163,12 @@ CREATE TABLE IF NOT EXISTS "track_bookmark" ( // Just increment the scan version of the settings to make the next scheduled scan rescan everything ScanSettings::get(*this).modify()->incScanVersion(); } + else if (version == 14) + { + // SortName now set from metadata + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + ScanSettings::get(*this).modify()->incScanVersion(); + } else { LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration"; diff --git a/src/libs/database/include/database/Artist.hpp b/src/libs/database/include/database/Artist.hpp index 4d515c7f..44fa339c 100644 --- a/src/libs/database/include/database/Artist.hpp +++ b/src/libs/database/include/database/Artist.hpp @@ -45,6 +45,13 @@ class Artist : public Wt::Dbo::Dbo { public: + enum class NameSortMethod + { + None, + ByName, + BySortName, + }; + using pointer = Wt::Dbo::ptr; Artist() {} @@ -55,24 +62,28 @@ class Artist : public Wt::Dbo::Dbo static pointer getById(Session& session, IdType id); static std::vector getByName(Session& session, const std::string& name); static std::vector getByClusters(Session& session, - const std::set& clusters); // at least one track that belongs to these clusters + const std::set& clusters, // at least one track that belongs to these clusters + NameSortMethod sortMethod + ); static std::vector getByFilter(Session& session, const std::set& clusters, // if non empty, at least one artist that belongs to these clusters const std::vector& keywords, // if non empty, name must match all of these keywords std::optional linkType, // if set, only artists that have produced at least one track with this link type + NameSortMethod sortMethod, std::optional offset, std::optional size, bool& moreExpected); - static std::vector getAll(Session& session, std::optional offset = {}, std::optional size = {}); + static std::vector getAll(Session& session, NameSortMethod sortMethod, std::optional offset = {}, std::optional size = {}); static std::vector getAllIds(Session& session); static std::vector getAllOrphans(Session& session); // No track related static std::vector getLastAdded(Session& session, Wt::WDateTime after, std::optional size = {}); static std::vector getAllIdsWithClusters(Session& session, std::optional limit = {}); // Accessors - const std::string& getName(void) const { return _name; } - std::optional getMBID(void) const { return UUID::fromString(_MBID); } + const std::string& getName() const { return _name; } + const std::string& getSortName() const { return _sortName; } + std::optional getMBID() const { return UUID::fromString(_MBID); } std::vector> getReleases(const std::set& clusterIds = {}) const; // if non empty, get the releases that match all these clusters std::size_t getReleaseCount() const; @@ -97,7 +108,7 @@ class Artist : public Wt::Dbo::Dbo void persist(Action& a) { Wt::Dbo::field(a, _name, "name"); - Wt::Dbo::field(a, _name, "sort_name"); + Wt::Dbo::field(a, _sortName, "sort_name"); Wt::Dbo::field(a, _MBID, "mbid"); Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "artist"); diff --git a/src/libs/metadata/impl/AvFormatParser.cpp b/src/libs/metadata/impl/AvFormatParser.cpp index 7dfd8af4..ae2d9495 100644 --- a/src/libs/metadata/impl/AvFormatParser.cpp +++ b/src/libs/metadata/impl/AvFormatParser.cpp @@ -94,7 +94,7 @@ getAlbumArtists(const MetadataMap& metadataMap) auto mbid {findFirstValueOfAs(metadataMap, {"MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"})}; - return {Artist {*name, mbid} }; + return {Artist {*name, std::nullopt, mbid} }; } static @@ -118,9 +118,9 @@ getArtists(const MetadataMap& metadataMap) for (std::size_t i {}; i < artistNames.size(); ++i) { if (artistMBIDs && artistNames.size() == artistMBIDs->size()) - artists.emplace_back(Artist {artistNames[i], (*artistMBIDs)[i]}); + artists.emplace_back(Artist {artistNames[i], std::nullopt, (*artistMBIDs)[i]}); else - artists.emplace_back(Artist {artistNames[i], {}}); + artists.emplace_back(Artist {artistNames[i], std::nullopt, {}}); } return artists; diff --git a/src/libs/metadata/impl/TagLibParser.cpp b/src/libs/metadata/impl/TagLibParser.cpp index fee48f25..2fe31d73 100644 --- a/src/libs/metadata/impl/TagLibParser.cpp +++ b/src/libs/metadata/impl/TagLibParser.cpp @@ -87,55 +87,74 @@ static std::vector getArtists(const TagLib::PropertyMap& properties) { - std::vector res; - std::vector artistNames {getPropertyValuesAs(properties, "ARTISTS")}; if (artistNames.empty()) artistNames = getPropertyValuesAs(properties, "ARTIST"); if (artistNames.empty()) - return res; + return {}; - const std::vector artistsMBID {getPropertyValuesFirstMatchAs(properties, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID"})}; + std::vector artists; + artists.reserve(artistNames.size()); + std::transform(std::cbegin(artistNames), std::cend(artistNames), std::back_inserter(artists), + [&](const std::string& name) { return Artist {name}; }); - if (artistNames.size() == artistsMBID.size()) { - std::transform(std::cbegin(artistNames), std::cend(artistNames), std::cbegin(artistsMBID), std::back_inserter(res), - [&](const std::string& name, const UUID& mbid) { return Artist {name, mbid}; }); - } - else - { - std::transform(std::cbegin(artistNames), std::cend(artistNames), std::back_inserter(res), - [&](const std::string& name) { return Artist{name, {}}; }); + const std::vector artistSortNames {getPropertyValuesAs(properties, "ARTISTSORT")}; + if (artistSortNames.size() == artists.size()) + { + for (std::size_t i {}; i < artistSortNames.size(); ++i) + artists[i].sortName = artistSortNames[i]; + } } - return res; + { + const std::vector artistsMBID {getPropertyValuesFirstMatchAs(properties, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID"})}; + + if (artistNames.size() == artistsMBID.size()) + { + for (std::size_t i {}; i < artistsMBID.size(); ++i) + artists[i].musicBrainzArtistID = artistsMBID[i]; + } + } + + + return artists; } static std::vector getAlbumArtists(const TagLib::PropertyMap& properties) { - std::vector res; - std::vector artistNames {getPropertyValuesAs(properties, "ALBUMARTIST")}; if (artistNames.empty()) - return res; + return {}; - const std::vector artistsMBID {getPropertyValuesFirstMatchAs(properties, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID"})}; + std::vector artists; + artists.reserve(artistNames.size()); + std::transform(std::cbegin(artistNames), std::cend(artistNames), std::back_inserter(artists), + [&](const std::string& name) { return Artist {name}; }); - if (artistNames.size() == artistsMBID.size()) { - std::transform(std::cbegin(artistNames), std::cend(artistNames), std::cbegin(artistsMBID), std::back_inserter(res), - [&](const std::string& name, const UUID& mbid) { return Artist{name, mbid}; }); - } - else - { - std::transform(std::cbegin(artistNames), std::cend(artistNames), std::back_inserter(res), - [&](const std::string& name) { return Artist{name, {}}; }); + const std::vector artistSortNames {getPropertyValuesAs(properties, "ALBUMARTISTSORT")}; + if (artistSortNames.size() == artists.size()) + { + for (std::size_t i {}; i < artistSortNames.size(); ++i) + artists[i].sortName = artistSortNames[i]; + } } - return res; + { + const std::vector artistsMBID {getPropertyValuesFirstMatchAs(properties, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID"})}; + + if (artistsMBID.size() == artists.size()) + { + for (std::size_t i {}; i < artistsMBID.size(); ++i) + artists[i].musicBrainzArtistID = artistsMBID[i]; + } + } + + return artists; } static diff --git a/src/libs/metadata/include/metadata/IParser.hpp b/src/libs/metadata/include/metadata/IParser.hpp index abb12216..014fed3e 100644 --- a/src/libs/metadata/include/metadata/IParser.hpp +++ b/src/libs/metadata/include/metadata/IParser.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "utils/UUID.hpp" @@ -35,7 +36,11 @@ namespace MetaData struct Artist { std::string name; + std::optional sortName; std::optional musicBrainzArtistID; + + Artist(std::string_view _name) : name {_name} {} + Artist(std::string_view _name, std::optional _sortName, std::optional _musicBrainzArtistID) : name {_name}, sortName {_sortName}, musicBrainzArtistID {_musicBrainzArtistID} {} }; struct Album diff --git a/src/libs/scanner/impl/MediaScanner.cpp b/src/libs/scanner/impl/MediaScanner.cpp index e70b1c43..ca0c4715 100644 --- a/src/libs/scanner/impl/MediaScanner.cpp +++ b/src/libs/scanner/impl/MediaScanner.cpp @@ -84,6 +84,38 @@ isPathInParentPath(const std::filesystem::path& path, const std::filesystem::pat return false; } +static +Artist::pointer +createArtist(Session& session, const MetaData::Artist& artistInfo) +{ + Artist::pointer artist {Artist::create(session, artistInfo.name)}; + + if (artistInfo.musicBrainzArtistID) + artist.modify()->setMBID(*artistInfo.musicBrainzArtistID); + if (artistInfo.sortName) + artist.modify()->setSortName(*artistInfo.sortName); + + return artist; +} + +static +void +updateArtistIfNeeded(const Artist::pointer& artist, const MetaData::Artist& artistInfo) +{ + // Name may have been updated + if (artist->getName() != artistInfo.name) + { + artist.modify()->setName(artistInfo.name); + } + + // Sortname may have been updated + if (artistInfo.sortName && *artistInfo.sortName != artist->getSortName() ) + { + LMS_LOG(DBUPDATER, INFO) << "Setting sort name = '" << *artistInfo.sortName << "'"; + artist.modify()->setSortName(*artistInfo.sortName); + } +} + std::vector getOrCreateArtists(Session& session, const std::vector& artistsInfo) { @@ -98,14 +130,9 @@ getOrCreateArtists(Session& session, const std::vector& artist { artist = Artist::getByMBID(session, *artistInfo.musicBrainzArtistID); if (!artist) - { - artist = Artist::create(session, artistInfo.name, artistInfo.musicBrainzArtistID); - } - else if (artist->getName() != artistInfo.name) - { - // Name may have been updated - artist.modify()->setName(artistInfo.name); - } + artist = createArtist(session, artistInfo); + else + updateArtistIfNeeded(artist, artistInfo); artists.emplace_back(std::move(artist)); continue; @@ -126,7 +153,9 @@ getOrCreateArtists(Session& session, const std::vector& artist // No Artist found with the same name and without MBID -> creating if (!artist) - artist = Artist::create(session, artistInfo.name); + artist = createArtist(session, artistInfo); + else + updateArtistIfNeeded(artist, artistInfo); artists.emplace_back(std::move(artist)); continue; @@ -430,9 +459,7 @@ MediaScanner::scan(boost::system::error_code err) LMS_LOG(DBUPDATER, INFO) << "Scan " << (_running ? "complete" : "aborted") << ". Changes = " << stats.nbChanges() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.errors.size() << "), features fetched = " << stats.featuresFetched << "/" << stats.featuresToFetch <<", duplicates = " << stats.duplicates.size(); - LMS_LOG(DBUPDATER, INFO) << "Optimizing db..."; _dbSession.optimize(); - LMS_LOG(DBUPDATER, INFO) << "Optimize db done!"; if (_running) { diff --git a/src/tools/metadata/CMakeLists.txt b/src/tools/metadata/CMakeLists.txt index 4c0706cf..0a831918 100644 --- a/src/tools/metadata/CMakeLists.txt +++ b/src/tools/metadata/CMakeLists.txt @@ -5,6 +5,7 @@ add_executable(lms-metadata target_link_libraries(lms-metadata PRIVATE lmsmetadata + lmsutils ) install(TARGETS lms-metadata DESTINATION bin) diff --git a/src/tools/metadata/LmsMetadata.cpp b/src/tools/metadata/LmsMetadata.cpp index 7a04e178..b3af8687 100644 --- a/src/tools/metadata/LmsMetadata.cpp +++ b/src/tools/metadata/LmsMetadata.cpp @@ -36,6 +36,9 @@ std::ostream& operator<<(std::ostream& os, const MetaData::Artist& artist) if (artist.musicBrainzArtistID) os << " (" << artist.musicBrainzArtistID->getAsString() << ")"; + if (artist.sortName) + os << " '" << *artist.sortName << "'"; + return os; } From 9d06b02acc8fc93f0cbf147b79a03649af5af87e Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 3 Apr 2020 14:43:04 +0200 Subject: [PATCH 04/10] Fixed some relative paths to ease deployment on non a root path. Ref #41 --- README.md | 11 +++++++++++ conf/lms.conf | 7 +++---- src/lms/main.cpp | 4 +++- src/lms/ui/LmsApplication.cpp | 6 +++--- 4 files changed, 20 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 2738d002..01d92b0b 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,17 @@ All other settings are set using the web interface (user management, scan settin If a setting is not present in the configuration file, a hardcoded default value is used (the same as in the [default.conf](https://github.com/epoupon/lms/blob/master/conf/lms.conf) file) +### Deploy on non root path +If you want to deploy on non root path (e.g. https://mydomain.com/newroot/), you have to do the following steps: +* Create a new intermediary `newroot` directory in `/usr/share/lms/docroot` +* Symlink `/usr/share/lms/docroot/newroot/resources` to `/usr/share/Wt/resources` +* Edit `lms.conf` and set: +``` +wt-resources = "" +docroot = "/usr/share/lms/docroot/;/newroot/resources,/newroot/css,/newroot/images,/newroot/js,/newroot/favicon.ico";` +deploy-path = "/newroot/"; +``` + ### Reverse proxy settings _LMS_ is shipped with an embedded web server, but it is recommended to deploy behind a reverse proxy. You have to set the _behind-reverse-proxy_ option to _true_ in the `lms.conf` configuration file. diff --git a/conf/lms.conf b/conf/lms.conf index 04d48d54..e1666f60 100644 --- a/conf/lms.conf +++ b/conf/lms.conf @@ -18,19 +18,18 @@ listen-port = 5082; listen-addr = "0.0.0.0"; behind-reverse-proxy = false; -# Location for deployment -deploy-path = "/"; - # If enabled, these files have to exist and have correct permissions tls-enable = false; tls-cert = "/var/lms/cert.pem"; tls-key = "/var/lms/privkey.pem"; tls-dh = "/var/lms/dh2048.pem"; -# Path to the resources used by the web interface +# Path to the resources used by the web interface. wt-resources = "/usr/share/Wt/resources"; docroot = "/usr/share/lms/docroot/;/resources,/css,/images,/js,/favicon.ico"; approot = "/usr/share/lms/approot"; +# Location for deployment (See README if you want to deploy on a non root path) +deploy-path = "/"; # Acoustic brainz's root API acousticbrainz-api-url = "https://acousticbrainz.org/api/v1/"; diff --git a/src/lms/main.cpp b/src/lms/main.cpp index 39d7eb15..bbaa1985 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -43,13 +43,15 @@ std::vector generateWtConfig(std::string execPath) const std::filesystem::path wtConfigPath {ServiceProvider::get()->getPath("working-dir") / "wt_config.xml"}; const std::filesystem::path wtLogFilePath {ServiceProvider::get()->getPath("log-file", "/var/log/lms.log")}; const std::filesystem::path wtAccessLogFilePath {ServiceProvider::get()->getPath("access-log-file", "/var/log/lms.access.log")}; + const std::filesystem::path wtResourcesPath {ServiceProvider::get()->getPath("wt-resources", "/usr/share/Wt/resources")}; args.push_back(execPath); args.push_back("--config=" + wtConfigPath.string()); args.push_back("--docroot=" + ServiceProvider::get()->getString("docroot")); args.push_back("--approot=" + ServiceProvider::get()->getString("approot")); args.push_back("--deploy-path=" + ServiceProvider::get()->getString("deploy-path", "/")); - args.push_back("--resources-dir=" + ServiceProvider::get()->getString("wt-resources")); + if (!wtResourcesPath.empty()) + args.push_back("--resources-dir=" + wtResourcesPath.string()); if (ServiceProvider::get()->getBool("tls-enable", false)) { diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index 1dfb5b30..a0cbf299 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -153,9 +153,9 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, messageResourceBundle().use(appRoot() + "tracksinfo"); // Require js here to avoid async problems - requireJQuery("/js/jquery-1.10.2.min.js"); - require("/js/mediaplayer.js"); - require("/js/bootstrap-notify.js"); + requireJQuery("js/jquery-1.10.2.min.js"); + require("js/mediaplayer.js"); + require("js/bootstrap-notify.js"); setTitle("LMS"); From d4fb8659ff323ad4934691e5d5f6676108a7f40b Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 3 Apr 2020 16:21:14 +0200 Subject: [PATCH 05/10] Fixed the source installation process --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 01d92b0b..018dfe54 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ Get the latest stable release and build it: git clone https://github.com/epoupon/lms.git lms cd lms mkdir build +cd build cmake .. -DCMAKE_BUILD_TYPE=Release ``` __Note__: in order to customize the installation directory, you can use the _-DCMAKE_INSTALL_PREFIX_ option (defaults to `/usr/local`). From c79d03b04ebe85dc04b66cd93257b0f6ad3cbe82 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 4 Apr 2020 13:52:31 +0200 Subject: [PATCH 06/10] Fixed the logout redirect path --- src/lms/ui/LmsApplication.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index a0cbf299..f4435008 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -293,7 +293,7 @@ LmsApplication::handleException(LmsApplicationException& e) btn->clicked().connect([this]() { setConfirmCloseMessage(""); - redirect("/"); + redirect("."); }); } @@ -302,7 +302,7 @@ LmsApplication::goHomeAndQuit() { setConfirmCloseMessage(""); WApplication::quit(""); - redirect("/"); + redirect("."); } enum IdxRoot From 19146e4d335da5dd8ce7f4a7bbf783d989aa3502 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 4 Apr 2020 14:06:07 +0200 Subject: [PATCH 07/10] Clarifications on non root deploy path --- README.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 018dfe54..2d4a0c30 100644 --- a/README.md +++ b/README.md @@ -165,14 +165,16 @@ All other settings are set using the web interface (user management, scan settin If a setting is not present in the configuration file, a hardcoded default value is used (the same as in the [default.conf](https://github.com/epoupon/lms/blob/master/conf/lms.conf) file) ### Deploy on non root path -If you want to deploy on non root path (e.g. https://mydomain.com/newroot/), you have to do the following steps: -* Create a new intermediary `newroot` directory in `/usr/share/lms/docroot` -* Symlink `/usr/share/lms/docroot/newroot/resources` to `/usr/share/Wt/resources` +If you want to deploy on non root path (e.g. https://mydomain.com/newroot/), you have to set the `deploy-path` option accordingly in `lms.conf`. + +As static resources are __not__ related to the `deploy-path` option, you have to perform the following steps if you want them to be on a non root path too: +* Create a new intermediary `newroot` directory in `/usr/share/lms/docroot` and move everything in it. +* Symlink `/usr/share/lms/docroot/newroot/resources` to `/usr/share/Wt/resources`. * Edit `lms.conf` and set: ``` -wt-resources = "" +wt-resources = "" # do not comment the whole line docroot = "/usr/share/lms/docroot/;/newroot/resources,/newroot/css,/newroot/images,/newroot/js,/newroot/favicon.ico";` -deploy-path = "/newroot/"; +deploy-path = "/newroot/"; # ending slash is important ``` ### Reverse proxy settings From 5d07dcbc3b68f2fc12a474e8fd1896bf15194170 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 4 Apr 2020 14:47:39 +0200 Subject: [PATCH 08/10] Handle sorting method for artists --- src/libs/database/impl/Artist.cpp | 106 +++++++++++--------- src/libs/subsonic/impl/SubsonicResource.cpp | 8 +- src/lms/ui/explore/ArtistsView.cpp | 1 + src/test/database/DatabaseTest.cpp | 37 +++---- 4 files changed, 82 insertions(+), 70 deletions(-) diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index d2571e91..3c3f3f9b 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -74,57 +74,13 @@ Artist::create(Session& session, const std::string& name, const std::optional -Artist::getAll(Session& session, std::optional offset, std::optional size) -{ - session.checkSharedLocked(); - Wt::Dbo::collection res = session.getDboSession().find() - .offset(offset ? static_cast(*offset) : -1) - .limit(size ? static_cast(*size) : -1) - .orderBy("sort_name COLLATE NOCASE"); - - return std::vector(res.begin(), res.end()); -} - -std::vector -Artist::getAllIds(Session& session) -{ - session.checkSharedLocked(); - - Wt::Dbo::collection res = session.getDboSession().query("SELECT id FROM artist"); - return std::vector(res.begin(), res.end()); -} - -std::vector -Artist::getAllOrphans(Session& session) -{ - session.checkSharedLocked(); - Wt::Dbo::collection> res {session.getDboSession().query>("SELECT DISTINCT a FROM artist a WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id WHERE t.id = t_a_l.track_id)")}; - - return std::vector(res.begin(), res.end()); -} - -std::vector -Artist::getAllIdsWithClusters(Session& session, std::optional limit) -{ - session.checkSharedLocked(); - - Wt::Dbo::collection res = session.getDboSession().query - ("SELECT DISTINCT a.id FROM artist a" - " INNER JOIN track t ON t.id = t_a_l.track_id INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id") - .limit(limit ? static_cast(*limit) : -1); - - return std::vector(res.begin(), res.end()); -} - - static Wt::Dbo::Query getQuery(Session& session, const std::set& clusterIds, const std::vector& keywords, - std::optional linkType) + std::optional linkType, + Artist::NameSortMethod sortMethod) { session.checkSharedLocked(); @@ -158,7 +114,17 @@ getQuery(Session& session, if (!clusterIds.empty()) oss << " GROUP BY t.id HAVING COUNT(DISTINCT c.id) = " << clusterIds.size(); - oss << " ORDER BY a.sort_name COLLATE NOCASE"; + switch (sortMethod) + { + case Artist::NameSortMethod::None: + break; + case Artist::NameSortMethod::ByName: + oss << " ORDER BY a.name COLLATE NOCASE"; + break; + case Artist::NameSortMethod::BySortName: + oss << " ORDER BY a.sort_name COLLATE NOCASE"; + break; + } Wt::Dbo::Query query = session.getDboSession().query( oss.str() ); @@ -170,6 +136,50 @@ getQuery(Session& session, return query; } +std::vector +Artist::getAll(Session& session, NameSortMethod sortMethod, std::optional offset, std::optional size) +{ + session.checkSharedLocked(); + + Wt::Dbo::collection res = getQuery(session, {}, {}, std::nullopt, sortMethod) + .limit(size ? static_cast(*size) + 1 : -1) + .offset(offset ? static_cast(*offset) : -1); + + return std::vector(res.begin(), res.end()); +} + +std::vector +Artist::getAllIds(Session& session) +{ + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().query("SELECT id FROM artist"); + return std::vector(res.begin(), res.end()); +} + +std::vector +Artist::getAllOrphans(Session& session) +{ + session.checkSharedLocked(); + Wt::Dbo::collection> res {session.getDboSession().query>("SELECT DISTINCT a FROM artist a WHERE NOT EXISTS(SELECT 1 FROM track t INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id WHERE t.id = t_a_l.track_id)")}; + + return std::vector(res.begin(), res.end()); +} + +std::vector +Artist::getAllIdsWithClusters(Session& session, std::optional limit) +{ + session.checkSharedLocked(); + + Wt::Dbo::collection res = session.getDboSession().query + ("SELECT DISTINCT a.id FROM artist a" + " INNER JOIN track t ON t.id = t_a_l.track_id INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id") + .limit(limit ? static_cast(*limit) : -1); + + return std::vector(res.begin(), res.end()); +} + std::vector Artist::getByClusters(Session& session, const std::set& clusters, NameSortMethod sortMethod) { @@ -191,7 +201,7 @@ Artist::getByFilter(Session& session, bool& moreResults) { session.checkSharedLocked(); - Wt::Dbo::collection collection = getQuery(session, clusters, keywords, linkType) + Wt::Dbo::collection collection = getQuery(session, clusters, keywords, linkType, sortMethod) .limit(size ? static_cast(*size) + 1 : -1) .offset(offset ? static_cast(*offset) : -1); diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index 11391e36..0583db25 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -901,7 +901,7 @@ handleGetArtistsRequest(RequestContext& context) if (!user) throw UserNotAuthorizedError {}; - auto artists {Artist::getAll(context.dbSession)}; + auto artists {Artist::getAll(context.dbSession, Artist::NameSortMethod::ByName)}; for (const Artist::pointer& artist : artists) indexNode.addArrayChild("artist", artistToResponseNode(user, artist, true /* id3 */)); @@ -932,7 +932,7 @@ handleGetMusicDirectoryRequest(RequestContext& context) { directoryNode.setAttribute("name", "Music"); - auto artists {Artist::getAll(context.dbSession)}; + auto artists {Artist::getAll(context.dbSession, Artist::NameSortMethod::ByName)}; for (const Artist::pointer& artist : artists) directoryNode.addArrayChild("child", artistToResponseNode(user, artist, false /* no id3 */)); @@ -1028,7 +1028,7 @@ handleGetIndexesRequest(RequestContext& context) if (!user) throw UserNotAuthorizedError {}; - auto artists {Artist::getAll(context.dbSession)}; + auto artists {Artist::getAll(context.dbSession, Artist::NameSortMethod::ByName)}; for (const Artist::pointer& artist : artists) indexNode.addArrayChild("artist", artistToResponseNode(user, artist, false /* no id3 */)); @@ -1317,7 +1317,7 @@ handleSearchRequestCommon(RequestContext& context, bool id3) bool more; { - auto artists {Artist::getByFilter(context.dbSession, {}, keywords, {}, artistOffset, artistCount, more)}; + auto artists {Artist::getByFilter(context.dbSession, {}, keywords, std::nullopt, Artist::NameSortMethod::ByName, artistOffset, artistCount, more)}; for (const Artist::pointer& artist : artists) searchResult2Node.addArrayChild("artist", artistToResponseNode(user, artist, id3)); } diff --git a/src/lms/ui/explore/ArtistsView.cpp b/src/lms/ui/explore/ArtistsView.cpp index 2798a3a6..400b206a 100644 --- a/src/lms/ui/explore/ArtistsView.cpp +++ b/src/lms/ui/explore/ArtistsView.cpp @@ -95,6 +95,7 @@ Artists::addSome() clusterIds, searchKeywords, linkModel->getValue(_linkType->currentIndex()), + Artist::NameSortMethod::BySortName, _container->count(), 20, moreResults)}; for (const auto& artist : artists) diff --git a/src/test/database/DatabaseTest.cpp b/src/test/database/DatabaseTest.cpp index 5ad5b60b..84df0f3b 100644 --- a/src/test/database/DatabaseTest.cpp +++ b/src/test/database/DatabaseTest.cpp @@ -172,7 +172,7 @@ testSingleArtist(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getAll(session)}; + auto artists {Artist::getAll(session, Artist::NameSortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); @@ -311,11 +311,11 @@ testSingleTrackSingleArtistMultiRoles(Session& session) { auto transaction {session.createSharedTransaction()}; bool hasMore{}; - CHECK(Artist::getByFilter(session, {}, {}, {}, {}, {}, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Artist, {}, {}, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::ReleaseArtist, {}, {}, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Writer, {}, {}, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Composer, {}, {}, hasMore).empty()); + CHECK(Artist::getByFilter(session, {}, {}, std::nullopt, Artist::NameSortMethod::ByName, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Artist, Artist::NameSortMethod::ByName, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::ReleaseArtist, Artist::NameSortMethod::ByName, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Writer, Artist::NameSortMethod::ByName, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Composer, Artist::NameSortMethod::ByName, {}, {}, hasMore).empty()); } { @@ -369,7 +369,8 @@ testSingleTrackMultiArtists(Session& session) CHECK(track->getArtists(TrackArtistLink::Type::Artist).size() == 2); CHECK(track->getArtists(TrackArtistLink::Type::ReleaseArtist).empty()); - CHECK(Artist::getAll(session).size() == 2); + CHECK(Artist::getAll(session, Artist::NameSortMethod::ByName).size() == 2); + CHECK(Artist::getAllIds(session).size() == 2); } { @@ -699,12 +700,12 @@ testSingleTrackSingleArtistMultiClusters(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByClusters(session, {cluster1.getId()})}; + auto artists {Artist::getByClusters(session, {cluster1.getId()}, Artist::NameSortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); - CHECK(Artist::getByClusters(session, {cluster2.getId()}).empty()); - CHECK(Artist::getByClusters(session, {cluster3.getId()}).empty()); + CHECK(Artist::getByClusters(session, {cluster2.getId()}, Artist::NameSortMethod::ByName).empty()); + CHECK(Artist::getByClusters(session, {cluster3.getId()}, Artist::NameSortMethod::ByName).empty()); cluster2.get().modify()->addTrack(track.get()); } @@ -712,19 +713,19 @@ testSingleTrackSingleArtistMultiClusters(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByClusters(session, {cluster1.getId()})}; + auto artists {Artist::getByClusters(session, {cluster1.getId()}, Artist::NameSortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); - artists = Artist::getByClusters(session, {cluster2.getId()}); + artists = Artist::getByClusters(session, {cluster2.getId()}, Artist::NameSortMethod::ByName); CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); - artists = Artist::getByClusters(session, {cluster1.getId(), cluster2.getId()}); + artists = Artist::getByClusters(session, {cluster1.getId(), cluster2.getId()}, Artist::NameSortMethod::ByName); CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); - CHECK(Artist::getByClusters(session, {cluster3.getId()}).empty()); + CHECK(Artist::getByClusters(session, {cluster3.getId()}, Artist::NameSortMethod::ByName).empty()); } } @@ -755,7 +756,7 @@ testSingleTrackSingleArtistMultiRolesMultiClusters(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByClusters(session, {cluster.getId()})}; + auto artists {Artist::getByClusters(session, {cluster.getId()}, Artist::NameSortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); } @@ -799,7 +800,7 @@ testMultiTracksSingleArtistMultiClusters(Session& session) std::set clusterIds; std::transform(std::cbegin(clusters), std::cend(clusters), std::inserter(clusterIds, std::begin(clusterIds)), [](const ScopedCluster& cluster) { return cluster.getId(); }); - auto artists {Artist::getByClusters(session, clusterIds)}; + auto artists {Artist::getByClusters(session, clusterIds, Artist::NameSortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); } @@ -914,7 +915,7 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByClusters(session, {cluster.getId()})}; + auto artists {Artist::getByClusters(session, {cluster.getId()}, Artist::NameSortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); @@ -1346,7 +1347,7 @@ testDatabaseEmpty(Session& session) { auto uniqueTransaction {session.createUniqueTransaction()}; - CHECK(Artist::getAll(session).empty()); + CHECK(Artist::getAll(session, Artist::NameSortMethod::ByName).empty()); CHECK(Cluster::getAll(session).empty()); CHECK(ClusterType::getAll(session).empty()); CHECK(Release::getAll(session).empty()); From 06f9673b28b7f6cb7b642eb35bd754190c0316fb Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 4 Apr 2020 15:34:26 +0200 Subject: [PATCH 09/10] Sort artists using sortname tag + search by name also in this tag. Fixes #44 --- src/libs/database/impl/Artist.cpp | 29 ++++-- src/libs/database/include/database/Artist.hpp | 12 +-- src/libs/subsonic/impl/SubsonicResource.cpp | 8 +- src/lms/ui/explore/ArtistsView.cpp | 2 +- src/test/database/DatabaseTest.cpp | 98 +++++++++++++++---- 5 files changed, 111 insertions(+), 38 deletions(-) diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index 3c3f3f9b..8f310180 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -80,7 +80,7 @@ getQuery(Session& session, const std::set& clusterIds, const std::vector& keywords, std::optional linkType, - Artist::NameSortMethod sortMethod) + Artist::SortMethod sortMethod) { session.checkSharedLocked(); @@ -89,8 +89,19 @@ getQuery(Session& session, std::ostringstream oss; oss << "SELECT DISTINCT a FROM artist a"; - for (auto keyword : keywords) - where.And(WhereClause("a.name LIKE ?")).bind("%%" + keyword + "%%"); + if (!keywords.empty()) + { + WhereClause whereKeywordsName; + WhereClause whereKeywordsSortName; + + for (auto keyword : keywords) + { + whereKeywordsName.And(WhereClause("a.name LIKE ?")).bind("%%" + keyword + "%%"); + whereKeywordsSortName.And(WhereClause("a.sort_name LIKE ?")).bind("%%" + keyword + "%%"); + } + + where.And(whereKeywordsName.Or(whereKeywordsSortName)); + } if (!clusterIds.empty() || linkType) { @@ -116,12 +127,12 @@ getQuery(Session& session, switch (sortMethod) { - case Artist::NameSortMethod::None: + case Artist::SortMethod::None: break; - case Artist::NameSortMethod::ByName: + case Artist::SortMethod::ByName: oss << " ORDER BY a.name COLLATE NOCASE"; break; - case Artist::NameSortMethod::BySortName: + case Artist::SortMethod::BySortName: oss << " ORDER BY a.sort_name COLLATE NOCASE"; break; } @@ -137,7 +148,7 @@ getQuery(Session& session, } std::vector -Artist::getAll(Session& session, NameSortMethod sortMethod, std::optional offset, std::optional size) +Artist::getAll(Session& session, SortMethod sortMethod, std::optional offset, std::optional size) { session.checkSharedLocked(); @@ -181,7 +192,7 @@ Artist::getAllIdsWithClusters(Session& session, std::optional limit } std::vector -Artist::getByClusters(Session& session, const std::set& clusters, NameSortMethod sortMethod) +Artist::getByClusters(Session& session, const std::set& clusters, SortMethod sortMethod) { assert(!clusters.empty()); @@ -195,7 +206,7 @@ Artist::getByFilter(Session& session, const std::set& clusters, const std::vector& keywords, std::optional linkType, - NameSortMethod sortMethod, + SortMethod sortMethod, std::optional offset, std::optional size, bool& moreResults) diff --git a/src/libs/database/include/database/Artist.hpp b/src/libs/database/include/database/Artist.hpp index 44fa339c..f6f5df7b 100644 --- a/src/libs/database/include/database/Artist.hpp +++ b/src/libs/database/include/database/Artist.hpp @@ -45,7 +45,7 @@ class Artist : public Wt::Dbo::Dbo { public: - enum class NameSortMethod + enum class SortMethod { None, ByName, @@ -60,21 +60,21 @@ class Artist : public Wt::Dbo::Dbo // Accessors static pointer getByMBID(Session& session, const UUID& MBID); static pointer getById(Session& session, IdType id); - static std::vector getByName(Session& session, const std::string& name); + static std::vector getByName(Session& session, const std::string& name); // exact match on name field static std::vector getByClusters(Session& session, const std::set& clusters, // at least one track that belongs to these clusters - NameSortMethod sortMethod + SortMethod sortMethod ); static std::vector getByFilter(Session& session, const std::set& clusters, // if non empty, at least one artist that belongs to these clusters - const std::vector& keywords, // if non empty, name must match all of these keywords + const std::vector& keywords, // if non empty, name must match all of these keywords (name + sort name fields) std::optional linkType, // if set, only artists that have produced at least one track with this link type - NameSortMethod sortMethod, + SortMethod sortMethod, std::optional offset, std::optional size, bool& moreExpected); - static std::vector getAll(Session& session, NameSortMethod sortMethod, std::optional offset = {}, std::optional size = {}); + static std::vector getAll(Session& session, SortMethod sortMethod, std::optional offset = {}, std::optional size = {}); static std::vector getAllIds(Session& session); static std::vector getAllOrphans(Session& session); // No track related static std::vector getLastAdded(Session& session, Wt::WDateTime after, std::optional size = {}); diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index 0583db25..80c69a85 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -901,7 +901,7 @@ handleGetArtistsRequest(RequestContext& context) if (!user) throw UserNotAuthorizedError {}; - auto artists {Artist::getAll(context.dbSession, Artist::NameSortMethod::ByName)}; + auto artists {Artist::getAll(context.dbSession, Artist::SortMethod::BySortName)}; for (const Artist::pointer& artist : artists) indexNode.addArrayChild("artist", artistToResponseNode(user, artist, true /* id3 */)); @@ -932,7 +932,7 @@ handleGetMusicDirectoryRequest(RequestContext& context) { directoryNode.setAttribute("name", "Music"); - auto artists {Artist::getAll(context.dbSession, Artist::NameSortMethod::ByName)}; + auto artists {Artist::getAll(context.dbSession, Artist::SortMethod::BySortName)}; for (const Artist::pointer& artist : artists) directoryNode.addArrayChild("child", artistToResponseNode(user, artist, false /* no id3 */)); @@ -1028,7 +1028,7 @@ handleGetIndexesRequest(RequestContext& context) if (!user) throw UserNotAuthorizedError {}; - auto artists {Artist::getAll(context.dbSession, Artist::NameSortMethod::ByName)}; + auto artists {Artist::getAll(context.dbSession, Artist::SortMethod::BySortName)}; for (const Artist::pointer& artist : artists) indexNode.addArrayChild("artist", artistToResponseNode(user, artist, false /* no id3 */)); @@ -1317,7 +1317,7 @@ handleSearchRequestCommon(RequestContext& context, bool id3) bool more; { - auto artists {Artist::getByFilter(context.dbSession, {}, keywords, std::nullopt, Artist::NameSortMethod::ByName, artistOffset, artistCount, more)}; + auto artists {Artist::getByFilter(context.dbSession, {}, keywords, std::nullopt, Artist::SortMethod::BySortName, artistOffset, artistCount, more)}; for (const Artist::pointer& artist : artists) searchResult2Node.addArrayChild("artist", artistToResponseNode(user, artist, id3)); } diff --git a/src/lms/ui/explore/ArtistsView.cpp b/src/lms/ui/explore/ArtistsView.cpp index 400b206a..28d1bfc9 100644 --- a/src/lms/ui/explore/ArtistsView.cpp +++ b/src/lms/ui/explore/ArtistsView.cpp @@ -95,7 +95,7 @@ Artists::addSome() clusterIds, searchKeywords, linkModel->getValue(_linkType->currentIndex()), - Artist::NameSortMethod::BySortName, + Artist::SortMethod::BySortName, _container->count(), 20, moreResults)}; for (const auto& artist : artists) diff --git a/src/test/database/DatabaseTest.cpp b/src/test/database/DatabaseTest.cpp index 84df0f3b..68e19288 100644 --- a/src/test/database/DatabaseTest.cpp +++ b/src/test/database/DatabaseTest.cpp @@ -172,7 +172,7 @@ testSingleArtist(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getAll(session, Artist::NameSortMethod::ByName)}; + auto artists {Artist::getAll(session, Artist::SortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); @@ -311,11 +311,11 @@ testSingleTrackSingleArtistMultiRoles(Session& session) { auto transaction {session.createSharedTransaction()}; bool hasMore{}; - CHECK(Artist::getByFilter(session, {}, {}, std::nullopt, Artist::NameSortMethod::ByName, {}, {}, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Artist, Artist::NameSortMethod::ByName, {}, {}, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::ReleaseArtist, Artist::NameSortMethod::ByName, {}, {}, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Writer, Artist::NameSortMethod::ByName, {}, {}, hasMore).size() == 1); - CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Composer, Artist::NameSortMethod::ByName, {}, {}, hasMore).empty()); + CHECK(Artist::getByFilter(session, {}, {}, std::nullopt, Artist::SortMethod::ByName, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Artist, Artist::SortMethod::ByName, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::ReleaseArtist, Artist::SortMethod::ByName, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Writer, Artist::SortMethod::ByName, {}, {}, hasMore).size() == 1); + CHECK(Artist::getByFilter(session, {}, {}, TrackArtistLink::Type::Composer, Artist::SortMethod::ByName, {}, {}, hasMore).empty()); } { @@ -369,7 +369,7 @@ testSingleTrackMultiArtists(Session& session) CHECK(track->getArtists(TrackArtistLink::Type::Artist).size() == 2); CHECK(track->getArtists(TrackArtistLink::Type::ReleaseArtist).empty()); - CHECK(Artist::getAll(session, Artist::NameSortMethod::ByName).size() == 2); + CHECK(Artist::getAll(session, Artist::SortMethod::ByName).size() == 2); CHECK(Artist::getAllIds(session).size() == 2); } @@ -386,6 +386,65 @@ testSingleTrackMultiArtists(Session& session) } } +static +void +testSingleArtistSearchByName(Session& session) +{ + ScopedArtist artist {session, "AAA"}; + + { + auto transaction {session.createUniqueTransaction()}; + artist.get().modify()->setSortName("ZZZ"); + } + + { + auto transaction {session.createSharedTransaction()}; + + bool more {}; + CHECK(Artist::getByFilter(session, {}, {"N"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, std::nullopt, more).empty()); + + const auto artistsByAAA {Artist::Artist::getByFilter(session, {}, {"A"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, std::nullopt, more)}; + CHECK(artistsByAAA.size() == 1); + CHECK(artistsByAAA.front().id() == artist.getId()); + + const auto artistsByZZZ {Artist::Artist::getByFilter(session, {}, {"Z"}, std::nullopt, Artist::SortMethod::ByName, std::nullopt, std::nullopt, more)}; + CHECK(artistsByZZZ.size() == 1); + CHECK(artistsByZZZ.front().id() == artist.getId()); + + CHECK(Artist::getByName(session, "NNN").empty()); + } +} + +static +void +testMultiArtistsSortMethod(Session& session) +{ + ScopedArtist artistA {session, "artistA"}; + ScopedArtist artistB {session, "artistB"}; + + { + auto transaction {session.createUniqueTransaction()}; + + artistA.get().modify()->setSortName("sortNameB"); + artistB.get().modify()->setSortName("sortNameA"); + } + + { + auto transaction {session.createSharedTransaction()}; + + auto allArtistsByName {Artist::getAll(session, Artist::SortMethod::ByName)}; + auto allArtistsBySortName {Artist::getAll(session, Artist::SortMethod::BySortName)}; + + CHECK(allArtistsByName.size() == 2); + CHECK(allArtistsByName.front().id() == artistA.getId()); + CHECK(allArtistsByName.back().id() == artistB.getId()); + + CHECK(allArtistsBySortName.size() == 2); + CHECK(allArtistsBySortName.front().id() == artistB.getId()); + CHECK(allArtistsBySortName.back().id() == artistA.getId()); + } +} + static void testSingleTrackSingleRelease(Session& session) @@ -700,12 +759,12 @@ testSingleTrackSingleArtistMultiClusters(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByClusters(session, {cluster1.getId()}, Artist::NameSortMethod::ByName)}; + auto artists {Artist::getByClusters(session, {cluster1.getId()}, Artist::SortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); - CHECK(Artist::getByClusters(session, {cluster2.getId()}, Artist::NameSortMethod::ByName).empty()); - CHECK(Artist::getByClusters(session, {cluster3.getId()}, Artist::NameSortMethod::ByName).empty()); + CHECK(Artist::getByClusters(session, {cluster2.getId()}, Artist::SortMethod::ByName).empty()); + CHECK(Artist::getByClusters(session, {cluster3.getId()}, Artist::SortMethod::ByName).empty()); cluster2.get().modify()->addTrack(track.get()); } @@ -713,19 +772,19 @@ testSingleTrackSingleArtistMultiClusters(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByClusters(session, {cluster1.getId()}, Artist::NameSortMethod::ByName)}; + auto artists {Artist::getByClusters(session, {cluster1.getId()}, Artist::SortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); - artists = Artist::getByClusters(session, {cluster2.getId()}, Artist::NameSortMethod::ByName); + artists = Artist::getByClusters(session, {cluster2.getId()}, Artist::SortMethod::ByName); CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); - artists = Artist::getByClusters(session, {cluster1.getId(), cluster2.getId()}, Artist::NameSortMethod::ByName); + artists = Artist::getByClusters(session, {cluster1.getId(), cluster2.getId()}, Artist::SortMethod::ByName); CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); - CHECK(Artist::getByClusters(session, {cluster3.getId()}, Artist::NameSortMethod::ByName).empty()); + CHECK(Artist::getByClusters(session, {cluster3.getId()}, Artist::SortMethod::ByName).empty()); } } @@ -756,7 +815,7 @@ testSingleTrackSingleArtistMultiRolesMultiClusters(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByClusters(session, {cluster.getId()}, Artist::NameSortMethod::ByName)}; + auto artists {Artist::getByClusters(session, {cluster.getId()}, Artist::SortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); } @@ -800,7 +859,7 @@ testMultiTracksSingleArtistMultiClusters(Session& session) std::set clusterIds; std::transform(std::cbegin(clusters), std::cend(clusters), std::inserter(clusterIds, std::begin(clusterIds)), [](const ScopedCluster& cluster) { return cluster.getId(); }); - auto artists {Artist::getByClusters(session, clusterIds, Artist::NameSortMethod::ByName)}; + auto artists {Artist::getByClusters(session, clusterIds, Artist::SortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); } @@ -915,7 +974,7 @@ testSingleTrackSingleReleaseSingleArtistSingleCluster(Session& session) { auto transaction {session.createSharedTransaction()}; - auto artists {Artist::getByClusters(session, {cluster.getId()}, Artist::NameSortMethod::ByName)}; + auto artists {Artist::getByClusters(session, {cluster.getId()}, Artist::SortMethod::ByName)}; CHECK(artists.size() == 1); CHECK(artists.front().id() == artist.getId()); @@ -1347,7 +1406,7 @@ testDatabaseEmpty(Session& session) { auto uniqueTransaction {session.createUniqueTransaction()}; - CHECK(Artist::getAll(session, Artist::NameSortMethod::ByName).empty()); + CHECK(Artist::getAll(session, Artist::SortMethod::ByName).empty()); CHECK(Cluster::getAll(session).empty()); CHECK(ClusterType::getAll(session).empty()); CHECK(Release::getAll(session).empty()); @@ -1398,6 +1457,9 @@ int main() RUN_TEST(testSingleTrackSingleArtistMultiRoles); RUN_TEST(testSingleTrackMultiArtists); + RUN_TEST(testSingleArtistSearchByName); + RUN_TEST(testMultiArtistsSortMethod); + RUN_TEST(testSingleTrackSingleRelease); RUN_TEST(testSingleTrackSingleCluster); From e3ffd2361fb03168e3077630a014ee76f95c154a Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 10 Apr 2020 09:33:05 +0200 Subject: [PATCH 10/10] Restored metadata on files get via subsonic API. fixes #47 --- src/libs/subsonic/impl/Stream.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/subsonic/impl/Stream.cpp b/src/libs/subsonic/impl/Stream.cpp index e58f33cd..a87ad6e5 100644 --- a/src/libs/subsonic/impl/Stream.cpp +++ b/src/libs/subsonic/impl/Stream.cpp @@ -100,6 +100,7 @@ getStreamParameters(RequestContext& context) transcodeParameters.bitrate = *maxBitRate * 1000; transcodeParameters.encoding = userTranscodeFormatToAvEncoding(user->getAudioTranscodeFormat()); + transcodeParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.) parameters.transcodeParameters = std::move(transcodeParameters); }