This commit is contained in:
emeric
2020-04-02 20:54:31 +02:00
parent 5e8e46e77f
commit 2bd4fb8c78
9 changed files with 122 additions and 48 deletions
+4 -2
View File
@@ -171,13 +171,13 @@ getQuery(Session& session,
} }
std::vector<Artist::pointer> std::vector<Artist::pointer>
Artist::getByClusters(Session& session, const std::set<IdType>& clusters) Artist::getByClusters(Session& session, const std::set<IdType>& clusters, NameSortMethod sortMethod)
{ {
assert(!clusters.empty()); assert(!clusters.empty());
session.checkSharedLocked(); session.checkSharedLocked();
bool more; bool more;
return getByFilter(session, clusters, {}, {}, {}, {}, more); return getByFilter(session, clusters, {}, {}, sortMethod, {}, {}, more);
} }
std::vector<Artist::pointer> std::vector<Artist::pointer>
@@ -185,6 +185,7 @@ Artist::getByFilter(Session& session,
const std::set<IdType>& clusters, const std::set<IdType>& clusters,
const std::vector<std::string>& keywords, const std::vector<std::string>& keywords,
std::optional<TrackArtistLink::Type> linkType, std::optional<TrackArtistLink::Type> linkType,
NameSortMethod sortMethod,
std::optional<std::size_t> offset, std::optional<std::size_t> offset,
std::optional<std::size_t> size, std::optional<std::size_t> size,
bool& moreResults) bool& moreResults)
@@ -405,6 +406,7 @@ void
Artist::setSortName(const std::string& sortName) Artist::setSortName(const std::string& sortName)
{ {
_sortName = std::string(sortName, 0 , _maxNameLength); _sortName = std::string(sortName, 0 , _maxNameLength);
LMS_LOG(DB, DEBUG) << "SORT NAME = '" << _sortName << "'";
} }
} // namespace Database } // namespace Database
+7 -1
View File
@@ -40,7 +40,7 @@
namespace Database { namespace Database {
#define LMS_DATABASE_VERSION 14 #define LMS_DATABASE_VERSION 15
using Version = std::size_t; 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 // Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(*this).modify()->incScanVersion(); 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 else
{ {
LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration"; LMS_LOG(DB, ERROR) << "Database version " << version << " cannot be handled using migration";
+16 -5
View File
@@ -45,6 +45,13 @@ class Artist : public Wt::Dbo::Dbo<Artist>
{ {
public: public:
enum class NameSortMethod
{
None,
ByName,
BySortName,
};
using pointer = Wt::Dbo::ptr<Artist>; using pointer = Wt::Dbo::ptr<Artist>;
Artist() {} Artist() {}
@@ -55,24 +62,28 @@ class Artist : public Wt::Dbo::Dbo<Artist>
static pointer getById(Session& session, IdType id); static pointer getById(Session& session, IdType id);
static std::vector<pointer> getByName(Session& session, const std::string& name); static std::vector<pointer> getByName(Session& session, const std::string& name);
static std::vector<pointer> getByClusters(Session& session, static std::vector<pointer> getByClusters(Session& session,
const std::set<IdType>& clusters); // at least one track that belongs to these clusters const std::set<IdType>& clusters, // at least one track that belongs to these clusters
NameSortMethod sortMethod
);
static std::vector<pointer> getByFilter(Session& session, static std::vector<pointer> getByFilter(Session& session,
const std::set<IdType>& clusters, // if non empty, at least one artist that belongs to these clusters const std::set<IdType>& clusters, // if non empty, at least one artist that belongs to these clusters
const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords const std::vector<std::string>& keywords, // if non empty, name must match all of these keywords
std::optional<TrackArtistLink::Type> linkType, // if set, only artists that have produced at least one track with this link type std::optional<TrackArtistLink::Type> linkType, // if set, only artists that have produced at least one track with this link type
NameSortMethod sortMethod,
std::optional<std::size_t> offset, std::optional<std::size_t> offset,
std::optional<std::size_t> size, std::optional<std::size_t> size,
bool& moreExpected); bool& moreExpected);
static std::vector<pointer> getAll(Session& session, std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {}); static std::vector<pointer> getAll(Session& session, NameSortMethod sortMethod, std::optional<std::size_t> offset = {}, std::optional<std::size_t> size = {});
static std::vector<IdType> getAllIds(Session& session); static std::vector<IdType> getAllIds(Session& session);
static std::vector<pointer> getAllOrphans(Session& session); // No track related static std::vector<pointer> getAllOrphans(Session& session); // No track related
static std::vector<pointer> getLastAdded(Session& session, Wt::WDateTime after, std::optional<std::size_t> size = {}); static std::vector<pointer> getLastAdded(Session& session, Wt::WDateTime after, std::optional<std::size_t> size = {});
static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {}); static std::vector<IdType> getAllIdsWithClusters(Session& session, std::optional<std::size_t> limit = {});
// Accessors // Accessors
const std::string& getName(void) const { return _name; } const std::string& getName() const { return _name; }
std::optional<UUID> getMBID(void) const { return UUID::fromString(_MBID); } const std::string& getSortName() const { return _sortName; }
std::optional<UUID> getMBID() const { return UUID::fromString(_MBID); }
std::vector<Wt::Dbo::ptr<Release>> getReleases(const std::set<IdType>& clusterIds = {}) const; // if non empty, get the releases that match all these clusters std::vector<Wt::Dbo::ptr<Release>> getReleases(const std::set<IdType>& clusterIds = {}) const; // if non empty, get the releases that match all these clusters
std::size_t getReleaseCount() const; std::size_t getReleaseCount() const;
@@ -97,7 +108,7 @@ class Artist : public Wt::Dbo::Dbo<Artist>
void persist(Action& a) void persist(Action& a)
{ {
Wt::Dbo::field(a, _name, "name"); 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::field(a, _MBID, "mbid");
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "artist"); Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "artist");
+3 -3
View File
@@ -94,7 +94,7 @@ getAlbumArtists(const MetadataMap& metadataMap)
auto mbid {findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"})}; auto mbid {findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"})};
return {Artist {*name, mbid} }; return {Artist {*name, std::nullopt, mbid} };
} }
static static
@@ -118,9 +118,9 @@ getArtists(const MetadataMap& metadataMap)
for (std::size_t i {}; i < artistNames.size(); ++i) for (std::size_t i {}; i < artistNames.size(); ++i)
{ {
if (artistMBIDs && artistNames.size() == artistMBIDs->size()) 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 else
artists.emplace_back(Artist {artistNames[i], {}}); artists.emplace_back(Artist {artistNames[i], std::nullopt, {}});
} }
return artists; return artists;
+45 -26
View File
@@ -87,55 +87,74 @@ static
std::vector<Artist> std::vector<Artist>
getArtists(const TagLib::PropertyMap& properties) getArtists(const TagLib::PropertyMap& properties)
{ {
std::vector<Artist> res;
std::vector<std::string> artistNames {getPropertyValuesAs<std::string>(properties, "ARTISTS")}; std::vector<std::string> artistNames {getPropertyValuesAs<std::string>(properties, "ARTISTS")};
if (artistNames.empty()) if (artistNames.empty())
artistNames = getPropertyValuesAs<std::string>(properties, "ARTIST"); artistNames = getPropertyValuesAs<std::string>(properties, "ARTIST");
if (artistNames.empty()) if (artistNames.empty())
return res; return {};
const std::vector<UUID> artistsMBID {getPropertyValuesFirstMatchAs<UUID>(properties, {"MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID"})}; std::vector<Artist> 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::vector<std::string> artistSortNames {getPropertyValuesAs<std::string>(properties, "ARTISTSORT")};
[&](const std::string& name, const UUID& mbid) { return Artist {name, mbid}; }); if (artistSortNames.size() == artists.size())
} {
else for (std::size_t i {}; i < artistSortNames.size(); ++i)
{ artists[i].sortName = artistSortNames[i];
std::transform(std::cbegin(artistNames), std::cend(artistNames), std::back_inserter(res), }
[&](const std::string& name) { return Artist{name, {}}; });
} }
return res; {
const std::vector<UUID> artistsMBID {getPropertyValuesFirstMatchAs<UUID>(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 static
std::vector<Artist> std::vector<Artist>
getAlbumArtists(const TagLib::PropertyMap& properties) getAlbumArtists(const TagLib::PropertyMap& properties)
{ {
std::vector<Artist> res;
std::vector<std::string> artistNames {getPropertyValuesAs<std::string>(properties, "ALBUMARTIST")}; std::vector<std::string> artistNames {getPropertyValuesAs<std::string>(properties, "ALBUMARTIST")};
if (artistNames.empty()) if (artistNames.empty())
return res; return {};
const std::vector<UUID> artistsMBID {getPropertyValuesFirstMatchAs<UUID>(properties, {"MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID"})}; std::vector<Artist> 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::vector<std::string> artistSortNames {getPropertyValuesAs<std::string>(properties, "ALBUMARTISTSORT")};
[&](const std::string& name, const UUID& mbid) { return Artist{name, mbid}; }); if (artistSortNames.size() == artists.size())
} {
else for (std::size_t i {}; i < artistSortNames.size(); ++i)
{ artists[i].sortName = artistSortNames[i];
std::transform(std::cbegin(artistNames), std::cend(artistNames), std::back_inserter(res), }
[&](const std::string& name) { return Artist{name, {}}; });
} }
return res; {
const std::vector<UUID> artistsMBID {getPropertyValuesFirstMatchAs<UUID>(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 static
@@ -24,6 +24,7 @@
#include <map> #include <map>
#include <optional> #include <optional>
#include <set> #include <set>
#include <string_view>
#include <vector> #include <vector>
#include "utils/UUID.hpp" #include "utils/UUID.hpp"
@@ -35,7 +36,11 @@ namespace MetaData
struct Artist struct Artist
{ {
std::string name; std::string name;
std::optional<std::string> sortName;
std::optional<UUID> musicBrainzArtistID; std::optional<UUID> musicBrainzArtistID;
Artist(std::string_view _name) : name {_name} {}
Artist(std::string_view _name, std::optional<std::string> _sortName, std::optional<UUID> _musicBrainzArtistID) : name {_name}, sortName {_sortName}, musicBrainzArtistID {_musicBrainzArtistID} {}
}; };
struct Album struct Album
+38 -11
View File
@@ -84,6 +84,38 @@ isPathInParentPath(const std::filesystem::path& path, const std::filesystem::pat
return false; 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<Artist::pointer> std::vector<Artist::pointer>
getOrCreateArtists(Session& session, const std::vector<MetaData::Artist>& artistsInfo) getOrCreateArtists(Session& session, const std::vector<MetaData::Artist>& artistsInfo)
{ {
@@ -98,14 +130,9 @@ getOrCreateArtists(Session& session, const std::vector<MetaData::Artist>& artist
{ {
artist = Artist::getByMBID(session, *artistInfo.musicBrainzArtistID); artist = Artist::getByMBID(session, *artistInfo.musicBrainzArtistID);
if (!artist) if (!artist)
{ artist = createArtist(session, artistInfo);
artist = Artist::create(session, artistInfo.name, artistInfo.musicBrainzArtistID); else
} updateArtistIfNeeded(artist, artistInfo);
else if (artist->getName() != artistInfo.name)
{
// Name may have been updated
artist.modify()->setName(artistInfo.name);
}
artists.emplace_back(std::move(artist)); artists.emplace_back(std::move(artist));
continue; continue;
@@ -126,7 +153,9 @@ getOrCreateArtists(Session& session, const std::vector<MetaData::Artist>& artist
// No Artist found with the same name and without MBID -> creating // No Artist found with the same name and without MBID -> creating
if (!artist) if (!artist)
artist = Artist::create(session, artistInfo.name); artist = createArtist(session, artistInfo);
else
updateArtistIfNeeded(artist, artistInfo);
artists.emplace_back(std::move(artist)); artists.emplace_back(std::move(artist));
continue; 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) << "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(); _dbSession.optimize();
LMS_LOG(DBUPDATER, INFO) << "Optimize db done!";
if (_running) if (_running)
{ {
+1
View File
@@ -5,6 +5,7 @@ add_executable(lms-metadata
target_link_libraries(lms-metadata PRIVATE target_link_libraries(lms-metadata PRIVATE
lmsmetadata lmsmetadata
lmsutils
) )
install(TARGETS lms-metadata DESTINATION bin) install(TARGETS lms-metadata DESTINATION bin)
+3
View File
@@ -36,6 +36,9 @@ std::ostream& operator<<(std::ostream& os, const MetaData::Artist& artist)
if (artist.musicBrainzArtistID) if (artist.musicBrainzArtistID)
os << " (" << artist.musicBrainzArtistID->getAsString() << ")"; os << " (" << artist.musicBrainzArtistID->getAsString() << ")";
if (artist.sortName)
os << " '" << *artist.sortName << "'";
return os; return os;
} }