Fixed bad fallback when hitting only a secondary release type as used by picard, fixes #766

This commit is contained in:
emeric
2025-11-10 16:56:17 +01:00
parent c4924d820c
commit f22f4f0a71
4 changed files with 188 additions and 162 deletions
+1 -5
View File
@@ -253,11 +253,7 @@ namespace lms::ui
{
Wt::WTemplate* releaseContainer{ releaseContainers->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artist.template.release-container")) };
if (releaseType.primaryType || !releaseType.customTypes.empty())
releaseContainer->bindString("release-type", releaseHelpers::buildReleaseTypeString(releaseType));
else
releaseContainer->bindString("release-type", Wt::WString::tr("Lms.Explore.releases")); // fallback when not tagged with MB or custom type
releaseContainer->bindString("release-type", releaseHelpers::buildReleaseTypeString(releaseType));
releases.container = releaseContainer->bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
releases.container->onRequestElements.connect(this, [this, &releases = releases] {
addSomeReleases(releases);
+99 -75
View File
@@ -24,10 +24,13 @@
#include <Wt/WText.h>
#include "core/EnumSet.hpp"
#include "core/Utils.hpp"
#include "database/objects/Artist.hpp"
#include "database/objects/Release.hpp"
#include "Utils.hpp"
#include "explore/ReleaseTypes.hpp"
namespace lms::ui::releaseListHelpers
{
@@ -110,85 +113,106 @@ namespace lms::ui::releaseListHelpers
namespace lms::ui::releaseHelpers
{
namespace
{
static const Wt::WString releaseTypeSeparator{ " · " };
Wt::WString buildPicardReleaseTypeString(const PicardReleaseType& releaseType)
{
Wt::WString res;
switch (releaseType.primaryType)
{
case PicardReleaseType::PrimaryType::Album:
res = Wt::WString::tr("Lms.Explore.Release.type-primary-album");
break;
case PicardReleaseType::PrimaryType::Broadcast:
res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast");
break;
case PicardReleaseType::PrimaryType::EP:
res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep");
break;
case PicardReleaseType::PrimaryType::Single:
res = Wt::WString::tr("Lms.Explore.Release.type-primary-single");
break;
case PicardReleaseType::PrimaryType::Other:
res = Wt::WString::tr("Lms.Explore.Release.type-primary-other");
break;
}
for (PicardReleaseType::SecondaryType secondaryType : releaseType.secondaryTypes)
{
if (!res.empty())
res += releaseTypeSeparator;
switch (secondaryType)
{
case PicardReleaseType::SecondaryType::Compilation:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation");
break;
case PicardReleaseType::SecondaryType::Spokenword:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword");
break;
case PicardReleaseType::SecondaryType::Soundtrack:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack");
break;
case PicardReleaseType::SecondaryType::Interview:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview");
break;
case PicardReleaseType::SecondaryType::Audiobook:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook");
break;
case PicardReleaseType::SecondaryType::AudioDrama:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama");
break;
case PicardReleaseType::SecondaryType::Live:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live");
break;
case PicardReleaseType::SecondaryType::Remix:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix");
break;
case PicardReleaseType::SecondaryType::DJMix:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix");
break;
case PicardReleaseType::SecondaryType::Mixtape_Street:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street");
break;
case PicardReleaseType::SecondaryType::Demo:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo");
break;
case PicardReleaseType::SecondaryType::FieldRecording:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-field-recording");
break;
}
}
return res;
}
Wt::WString buildCustomReleaseTypeString(const CustomReleaseType& releaseType)
{
Wt::WString res;
for (const std::string& customType : releaseType.types)
{
if (!res.empty())
res += releaseTypeSeparator;
res += customType;
}
return res;
}
} // namespace
Wt::WString buildReleaseTypeString(const ReleaseType& releaseType)
{
Wt::WString res;
if (releaseType.primaryType)
{
switch (*releaseType.primaryType)
{
case PrimaryReleaseType::Album:
res = Wt::WString::tr("Lms.Explore.Release.type-primary-album");
break;
case PrimaryReleaseType::Broadcast:
res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast");
break;
case PrimaryReleaseType::EP:
res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep");
break;
case PrimaryReleaseType::Single:
res = Wt::WString::tr("Lms.Explore.Release.type-primary-single");
break;
case PrimaryReleaseType::Other:
res = Wt::WString::tr("Lms.Explore.Release.type-primary-other");
break;
}
}
for (SecondaryReleaseType secondaryType : releaseType.secondaryTypes)
{
if (!res.empty())
res += Wt::WString{ " · " };
switch (secondaryType)
{
case SecondaryReleaseType::Compilation:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation");
break;
case SecondaryReleaseType::Spokenword:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword");
break;
case SecondaryReleaseType::Soundtrack:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack");
break;
case SecondaryReleaseType::Interview:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview");
break;
case SecondaryReleaseType::Audiobook:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook");
break;
case SecondaryReleaseType::AudioDrama:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama");
break;
case SecondaryReleaseType::Live:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live");
break;
case SecondaryReleaseType::Remix:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix");
break;
case SecondaryReleaseType::DJMix:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix");
break;
case SecondaryReleaseType::Mixtape_Street:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street");
break;
case SecondaryReleaseType::Demo:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo");
break;
case SecondaryReleaseType::FieldRecording:
res += Wt::WString::tr("Lms.Explore.Release.type-secondary-field-recording");
break;
}
}
for (const std::string& customType : releaseType.customTypes)
{
if (!res.empty())
res += Wt::WString{ " · " };
res += customType;
}
std::visit(core::utils::overloads{
[&](const PicardReleaseType& picardReleaseType) { res = buildPicardReleaseTypeString(picardReleaseType); },
[&](const CustomReleaseType& customReleaseType) { res = buildCustomReleaseTypeString(customReleaseType); } },
releaseType);
return res;
}
+51 -51
View File
@@ -17,24 +17,24 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ReleaseTypes.hpp"
#include <tuple>
#include <unordered_map>
#include "core/String.hpp"
#include "ReleaseTypes.hpp"
namespace lms::core::stringUtils
{
template<>
std::optional<ui::PrimaryReleaseType> readAs(std::string_view str)
std::optional<ui::PicardReleaseType::PrimaryType> readAs(std::string_view str)
{
static const std::unordered_map<std::string, ui::PrimaryReleaseType> entries{
{ "album", ui::PrimaryReleaseType::Album },
{ "single", ui::PrimaryReleaseType::Single },
{ "ep", ui::PrimaryReleaseType::EP },
{ "broadcast", ui::PrimaryReleaseType::Broadcast },
{ "other", ui::PrimaryReleaseType::Other },
static const std::unordered_map<std::string, ui::PicardReleaseType::PrimaryType> entries{
{ "album", ui::PicardReleaseType::PrimaryType::Album },
{ "single", ui::PicardReleaseType::PrimaryType::Single },
{ "ep", ui::PicardReleaseType::PrimaryType::EP },
{ "broadcast", ui::PicardReleaseType::PrimaryType::Broadcast },
{ "other", ui::PicardReleaseType::PrimaryType::Other },
};
const auto it{ entries.find(stringToLower(stringTrim(str))) };
@@ -45,21 +45,21 @@ namespace lms::core::stringUtils
}
template<>
std::optional<ui::SecondaryReleaseType> readAs(std::string_view str)
std::optional<ui::PicardReleaseType::SecondaryType> readAs(std::string_view str)
{
static const std::unordered_map<std::string, ui::SecondaryReleaseType> entries{
{ "compilation", ui::SecondaryReleaseType::Compilation },
{ "soundtrack", ui::SecondaryReleaseType::Soundtrack },
{ "spokenword", ui::SecondaryReleaseType::Spokenword },
{ "interview", ui::SecondaryReleaseType::Interview },
{ "audiobook", ui::SecondaryReleaseType::Audiobook },
{ "audio drama", ui::SecondaryReleaseType::AudioDrama },
{ "live", ui::SecondaryReleaseType::Live },
{ "remix", ui::SecondaryReleaseType::Remix },
{ "dj-mix", ui::SecondaryReleaseType::DJMix },
{ "mixtape/street", ui::SecondaryReleaseType::Mixtape_Street },
{ "demo", ui::SecondaryReleaseType::Demo },
{ "field recording", ui::SecondaryReleaseType::FieldRecording },
static const std::unordered_map<std::string, ui::PicardReleaseType::SecondaryType> entries{
{ "compilation", ui::PicardReleaseType::SecondaryType::Compilation },
{ "soundtrack", ui::PicardReleaseType::SecondaryType::Soundtrack },
{ "spokenword", ui::PicardReleaseType::SecondaryType::Spokenword },
{ "interview", ui::PicardReleaseType::SecondaryType::Interview },
{ "audiobook", ui::PicardReleaseType::SecondaryType::Audiobook },
{ "audio drama", ui::PicardReleaseType::SecondaryType::AudioDrama },
{ "live", ui::PicardReleaseType::SecondaryType::Live },
{ "remix", ui::PicardReleaseType::SecondaryType::Remix },
{ "dj-mix", ui::PicardReleaseType::SecondaryType::DJMix },
{ "mixtape/street", ui::PicardReleaseType::SecondaryType::Mixtape_Street },
{ "demo", ui::PicardReleaseType::SecondaryType::Demo },
{ "field recording", ui::PicardReleaseType::SecondaryType::FieldRecording },
};
const auto it{ entries.find(stringToLower(stringTrim(str))) };
@@ -72,48 +72,48 @@ namespace lms::core::stringUtils
namespace lms::ui
{
ReleaseType parseReleaseType(const std::vector<std::string>& releaseTypeNames)
std::optional<PicardReleaseType> parsePicardReleaseType(const std::vector<std::string>& releaseTypeNames)
{
ReleaseType res;
if (releaseTypeNames.empty())
return std::nullopt;
for (std::string_view releaseTypeName : releaseTypeNames)
const auto primaryType{ core::stringUtils::readAs<PicardReleaseType::PrimaryType>(releaseTypeNames[0]) };
if (!primaryType)
return std::nullopt;
PicardReleaseType res{ .primaryType = *primaryType, .secondaryTypes = {} };
for (std::size_t i{ 1 }; i < releaseTypeNames.size(); ++i)
{
if (auto primaryType{ core::stringUtils::readAs<PrimaryReleaseType>(releaseTypeName) })
{
if (!res.primaryType)
res.primaryType = primaryType;
else
res.customTypes.push_back(std::string{ releaseTypeName });
}
else if (auto secondaryType{ core::stringUtils::readAs<SecondaryReleaseType>(releaseTypeName) })
{
res.secondaryTypes.insert(*secondaryType);
}
else
res.customTypes.push_back(std::string{ releaseTypeName });
const auto secondaryType{ core::stringUtils::readAs<PicardReleaseType::SecondaryType>(releaseTypeNames[i]) };
if (!secondaryType)
return std::nullopt;
res.secondaryTypes.insert(*secondaryType);
}
return res;
}
bool operator<(std::optional<PrimaryReleaseType> typeA, std::optional<PrimaryReleaseType> typeB)
ReleaseType parseReleaseType(const std::vector<std::string>& releaseTypeNames)
{
if (!typeA && typeB)
return false;
else if (typeA && !typeB)
return true;
else
return static_cast<int>(*typeA) < static_cast<int>(*typeB);
if (const auto picardReleaseType{ parsePicardReleaseType(releaseTypeNames) })
return *picardReleaseType;
return CustomReleaseType{ .types = releaseTypeNames };
}
bool operator<(core::EnumSet<SecondaryReleaseType> typesA, core::EnumSet<SecondaryReleaseType> typesB)
bool operator<(core::EnumSet<PicardReleaseType::SecondaryType> lhs, core::EnumSet<PicardReleaseType::SecondaryType> rhs)
{
return typesA.getBitfield() < typesB.getBitfield();
return lhs.getBitfield() < rhs.getBitfield();
}
bool ReleaseType::operator<(const ReleaseType& other) const
bool PicardReleaseType::operator<(const PicardReleaseType& other) const
{
// TODO : order custom types and compare for each element (size is not to be compared first)
return std::tie(primaryType, secondaryTypes, customTypes) < std::tie(other.primaryType, other.secondaryTypes, other.customTypes);
return std::tie(primaryType, secondaryTypes) < std::tie(other.primaryType, other.secondaryTypes);
}
bool CustomReleaseType::operator<(const CustomReleaseType& other) const
{
return types < other.types;
}
} // namespace lms::ui
+37 -31
View File
@@ -19,49 +19,55 @@
#pragma once
#include <optional>
#include <string>
#include <variant>
#include <vector>
#include "core/EnumSet.hpp"
namespace lms::ui
{
// see https://musicbrainz.org/doc/Release_Group/Type
enum class PrimaryReleaseType
struct PicardReleaseType
{
Album,
Single,
EP,
Broadcast,
Other
// see https://musicbrainz.org/doc/Release_Group/Type
enum class PrimaryType : std::uint8_t
{
Album,
Single,
EP,
Broadcast,
Other
};
enum class SecondaryType : std::uint8_t
{
Compilation,
Soundtrack,
Spokenword,
Interview,
Audiobook,
AudioDrama,
Live,
Remix,
DJMix,
Mixtape_Street,
Demo,
FieldRecording,
};
PrimaryType primaryType;
core::EnumSet<SecondaryType> secondaryTypes;
bool operator<(const PicardReleaseType&) const;
};
enum class SecondaryReleaseType
struct CustomReleaseType
{
Compilation,
Soundtrack,
Spokenword,
Interview,
Audiobook,
AudioDrama,
Live,
Remix,
DJMix,
Mixtape_Street,
Demo,
FieldRecording,
};
struct ReleaseType
{
std::optional<PrimaryReleaseType> primaryType;
core::EnumSet<SecondaryReleaseType> secondaryTypes;
std::vector<std::string> customTypes;
bool operator<(const ReleaseType& other) const;
std::vector<std::string> types;
bool operator<(const CustomReleaseType&) const;
};
using ReleaseType = std::variant<PicardReleaseType, CustomReleaseType>;
ReleaseType parseReleaseType(const std::vector<std::string>& releaseTypeNames);
} // namespace lms::ui