Added release type in scanner + UI (detailed view only)
This commit is contained in:
@@ -197,6 +197,23 @@
|
|||||||
<message id="Lms.Explore.Release.similar-releases">Similar albums</message>
|
<message id="Lms.Explore.Release.similar-releases">Similar albums</message>
|
||||||
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
||||||
<message id="Lms.Explore.Release.disc">Disc {1}</message>
|
<message id="Lms.Explore.Release.disc">Disc {1}</message>
|
||||||
|
<message id="Lms.Explore.Release.type">Type</message>
|
||||||
|
<message id="Lms.Explore.Release.type-primary-album">Album</message>
|
||||||
|
<message id="Lms.Explore.Release.type-primary-broadcast">Broadcast</message>
|
||||||
|
<message id="Lms.Explore.Release.type-primary-ep">EP</message>
|
||||||
|
<message id="Lms.Explore.Release.type-primary-other">Other</message>
|
||||||
|
<message id="Lms.Explore.Release.type-primary-single">Single</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-audiobook">Audiobook</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-audiodrama">Audio drama</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-compilation">Compilation</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-demo">Demo</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-djmix">DJ-mix</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-interview">Interview</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-live">Live</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-mixtape-street">Mixtape/Street</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-remix">Remix</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-soundtrack">Soundtrack</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-spokenword">Spokenword</message>
|
||||||
|
|
||||||
<!--Explore:TrackLists-->
|
<!--Explore:TrackLists-->
|
||||||
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">Delete playlist?</message>
|
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">Delete playlist?</message>
|
||||||
|
|||||||
@@ -197,6 +197,23 @@
|
|||||||
<message id="Lms.Explore.Release.similar-releases">Albums similaires</message>
|
<message id="Lms.Explore.Release.similar-releases">Albums similaires</message>
|
||||||
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
||||||
<message id="Lms.Explore.Release.disc">Disque {1}</message>
|
<message id="Lms.Explore.Release.disc">Disque {1}</message>
|
||||||
|
<message id="Lms.Explore.Release.type">Type</message>
|
||||||
|
<message id="Lms.Explore.Release.type-primary-album">Album</message>
|
||||||
|
<message id="Lms.Explore.Release.type-primary-broadcast">Broadcast</message>
|
||||||
|
<message id="Lms.Explore.Release.type-primary-ep">EP</message>
|
||||||
|
<message id="Lms.Explore.Release.type-primary-other">Other</message>
|
||||||
|
<message id="Lms.Explore.Release.type-primary-single">Single</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-audiobook">Audiobook</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-audiodrama">Audio drama</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-compilation">Compilation</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-demo">Demo</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-djmix">DJ-mix</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-interview">Interview</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-live">Live</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-mixtape-street">Mixtape/Street</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-remix">Remix</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-soundtrack">Soundtrack</message>
|
||||||
|
<message id="Lms.Explore.Release.type-secondary-spokenword">Spokenword</message>
|
||||||
|
|
||||||
<!--Explore:TrackLists-->
|
<!--Explore:TrackLists-->
|
||||||
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">Supprimer la liste de lecture ?</message>
|
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">Supprimer la liste de lecture ?</message>
|
||||||
|
|||||||
@@ -198,6 +198,23 @@
|
|||||||
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
||||||
<message id="Lms.Explore.Release.disc">Disco {1}</message>
|
<message id="Lms.Explore.Release.disc">Disco {1}</message>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!--Explore:TrackLists-->
|
<!--Explore:TrackLists-->
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+17
-4
@@ -198,6 +198,23 @@
|
|||||||
<message id="Lms.Explore.Release.copyright">版权所有</message>
|
<message id="Lms.Explore.Release.copyright">版权所有</message>
|
||||||
<message id="Lms.Explore.Release.disc">唱片 {1}</message>
|
<message id="Lms.Explore.Release.disc">唱片 {1}</message>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!--Explore:TrackLists-->
|
<!--Explore:TrackLists-->
|
||||||
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">删除播放列表?</message>
|
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">删除播放列表?</message>
|
||||||
|
|
||||||
@@ -210,10 +227,6 @@
|
|||||||
<!--Playqueue-->
|
<!--Playqueue-->
|
||||||
<message id="Lms.PlayQueue.clear">清除</message>
|
<message id="Lms.PlayQueue.clear">清除</message>
|
||||||
<message id="Lms.PlayQueue.create-tracklist">创建新播放列表</message>
|
<message id="Lms.PlayQueue.create-tracklist">创建新播放列表</message>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<message id="Lms.PlayQueue.playqueue">播放队列</message>
|
<message id="Lms.PlayQueue.playqueue">播放队列</message>
|
||||||
<message id="Lms.PlayQueue.radio-mode">广播模式</message>
|
<message id="Lms.PlayQueue.radio-mode">广播模式</message>
|
||||||
<message id="Lms.PlayQueue.repeat">循环播放</message>
|
<message id="Lms.PlayQueue.repeat">循环播放</message>
|
||||||
|
|||||||
@@ -103,7 +103,19 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="container-fluid d-grid gap-2">
|
<div class="container-fluid d-grid gap-2">
|
||||||
|
${<if-has-release-type>}
|
||||||
|
<div class="row align-items-center">
|
||||||
|
<div class="col-4">
|
||||||
|
${tr:Lms.Explore.Release.type}
|
||||||
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
${release-type}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
${</if-has-release-type>}
|
||||||
|
${<if-has-artist>}
|
||||||
${artist-table class="d-grid gap-2"}
|
${artist-table class="d-grid gap-2"}
|
||||||
|
${</if-has-artist>}
|
||||||
${<if-has-codec>}
|
${<if-has-codec>}
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
|
|||||||
@@ -100,7 +100,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${</if-has-clusters>}
|
${</if-has-clusters>}
|
||||||
|
${<if-has-artist>}
|
||||||
${artist-table class="d-grid gap-2"}
|
${artist-table class="d-grid gap-2"}
|
||||||
|
${</if-has-artist>}
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col-4">
|
<div class="col-4">
|
||||||
${tr:Lms.Explore.duration}
|
${tr:Lms.Explore.duration}
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "SqlQuery.hpp"
|
#include "SqlQuery.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
#include "EnumSetTraits.hpp"
|
||||||
#include "IdTypeTraits.hpp"
|
#include "IdTypeTraits.hpp"
|
||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
#include <Wt/Dbo/StdSqlTraits.h>
|
||||||
|
|
||||||
|
#include "utils/EnumSet.hpp"
|
||||||
|
|
||||||
|
namespace Wt::Dbo
|
||||||
|
{
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct sql_value_traits<EnumSet<T>, void> : public sql_value_traits<long long>
|
||||||
|
{
|
||||||
|
using ValueType = typename EnumSet<T>::ValueType;
|
||||||
|
static_assert(sizeof(long long) > sizeof(ValueType));
|
||||||
|
|
||||||
|
static void bind(EnumSet<T> v, SqlStatement *statement, int column, int size)
|
||||||
|
{
|
||||||
|
sql_value_traits<long long>::bind(static_cast<long long>(v.getBitfield()), statement, column, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool read(EnumSet<T>& v, SqlStatement *statement, int column, int size)
|
||||||
|
{
|
||||||
|
long long val;
|
||||||
|
if (sql_value_traits<long long>::read(val, statement, column, size))
|
||||||
|
{
|
||||||
|
v.setBitfield(val);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
v.clear();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -653,6 +653,18 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
|
|||||||
ScanSettings::get(session).modify()->incScanVersion();
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
void
|
||||||
|
migrateFromV39(Session& session)
|
||||||
|
{
|
||||||
|
// add release type
|
||||||
|
session.getDboSession().execute("ALTER TABLE release ADD primary_type INTEGER");
|
||||||
|
session.getDboSession().execute("ALTER TABLE release ADD secondary_types INTEGER");
|
||||||
|
|
||||||
|
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
|
||||||
|
ScanSettings::get(session).modify()->incScanVersion();
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
doDbMigration(Session& session)
|
doDbMigration(Session& session)
|
||||||
{
|
{
|
||||||
@@ -698,6 +710,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
|
|||||||
{36, migrateFromV36},
|
{36, migrateFromV36},
|
||||||
{37, migrateFromV37},
|
{37, migrateFromV37},
|
||||||
{38, migrateFromV38},
|
{38, migrateFromV38},
|
||||||
|
{39, migrateFromV39},
|
||||||
};
|
};
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Database
|
|||||||
class Session;
|
class Session;
|
||||||
|
|
||||||
using Version = std::size_t;
|
using Version = std::size_t;
|
||||||
static constexpr Version LMS_DATABASE_VERSION {39};
|
static constexpr Version LMS_DATABASE_VERSION {40};
|
||||||
class VersionInfo
|
class VersionInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "services/database/User.hpp"
|
#include "services/database/User.hpp"
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "SqlQuery.hpp"
|
#include "SqlQuery.hpp"
|
||||||
|
#include "EnumSetTraits.hpp"
|
||||||
#include "IdTypeTraits.hpp"
|
#include "IdTypeTraits.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@
|
|||||||
#include "services/database/TrackList.hpp"
|
#include "services/database/TrackList.hpp"
|
||||||
#include "services/database/TrackFeatures.hpp"
|
#include "services/database/TrackFeatures.hpp"
|
||||||
#include "services/database/User.hpp"
|
#include "services/database/User.hpp"
|
||||||
|
#include "EnumSetTraits.hpp"
|
||||||
#include "Migration.hpp"
|
#include "Migration.hpp"
|
||||||
|
|
||||||
namespace Database
|
namespace Database
|
||||||
|
|||||||
@@ -109,11 +109,15 @@ class Release : public Object<Release, ReleaseId>
|
|||||||
std::size_t getDiscCount() const; // may not be total disc (if incomplete for example)
|
std::size_t getDiscCount() const; // may not be total disc (if incomplete for example)
|
||||||
std::chrono::milliseconds getDuration() const;
|
std::chrono::milliseconds getDuration() const;
|
||||||
Wt::WDateTime getLastWritten() const;
|
Wt::WDateTime getLastWritten() const;
|
||||||
|
std::optional<ReleaseTypePrimary> getPrimaryType() const { return _primaryType; }
|
||||||
|
EnumSet<ReleaseTypeSecondary> getSecondaryTypes() const { return _secondaryTypes; }
|
||||||
|
|
||||||
// Setters
|
// Setters
|
||||||
void setName(std::string_view name) { _name = name; }
|
void setName(std::string_view name) { _name = name; }
|
||||||
void setMBID(const std::optional<UUID>& mbid) { _MBID = mbid ? mbid->getAsString() : ""; }
|
void setMBID(const std::optional<UUID>& mbid) { _MBID = mbid ? mbid->getAsString() : ""; }
|
||||||
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; }
|
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; }
|
||||||
|
void setPrimaryType(std::optional<ReleaseTypePrimary> type) { _primaryType = type; }
|
||||||
|
void setSecondaryTypes(EnumSet<ReleaseTypeSecondary> types) { _secondaryTypes = types; }
|
||||||
|
|
||||||
// Get the artists of this release
|
// Get the artists of this release
|
||||||
std::vector<ObjectPtr<Artist>> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
std::vector<ObjectPtr<Artist>> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
||||||
@@ -128,6 +132,8 @@ class Release : public Object<Release, ReleaseId>
|
|||||||
Wt::Dbo::field(a, _name, "name");
|
Wt::Dbo::field(a, _name, "name");
|
||||||
Wt::Dbo::field(a, _MBID, "mbid");
|
Wt::Dbo::field(a, _MBID, "mbid");
|
||||||
Wt::Dbo::field(a, _totalDisc, "total_disc");
|
Wt::Dbo::field(a, _totalDisc, "total_disc");
|
||||||
|
Wt::Dbo::field(a, _primaryType, "primary_type");
|
||||||
|
Wt::Dbo::field(a, _secondaryTypes, "secondary_types");
|
||||||
|
|
||||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
||||||
}
|
}
|
||||||
@@ -139,9 +145,11 @@ class Release : public Object<Release, ReleaseId>
|
|||||||
|
|
||||||
static constexpr std::size_t _maxNameLength {128};
|
static constexpr std::size_t _maxNameLength {128};
|
||||||
|
|
||||||
std::string _name;
|
std::string _name;
|
||||||
std::string _MBID;
|
std::string _MBID;
|
||||||
std::optional<int> _totalDisc {};
|
std::optional<int> _totalDisc {};
|
||||||
|
std::optional<ReleaseTypePrimary> _primaryType;
|
||||||
|
EnumSet<ReleaseTypeSecondary> _secondaryTypes;
|
||||||
|
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks in the release
|
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks; // Tracks in the release
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -191,5 +191,30 @@ namespace Database
|
|||||||
Playlist, // user controlled playlists
|
Playlist, // user controlled playlists
|
||||||
Internal, // internal usage (current playqueue, history, ...)
|
Internal, // internal usage (current playqueue, history, ...)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// as defined in https://musicbrainz.org/doc/Release_Group/Type
|
||||||
|
enum class ReleaseTypePrimary
|
||||||
|
{
|
||||||
|
Album,
|
||||||
|
Single,
|
||||||
|
EP,
|
||||||
|
Broadcast,
|
||||||
|
Other,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ReleaseTypeSecondary
|
||||||
|
{
|
||||||
|
Compilation,
|
||||||
|
Soundtrack,
|
||||||
|
Spokenword,
|
||||||
|
Interview,
|
||||||
|
Audiobook,
|
||||||
|
AudioDrama,
|
||||||
|
Live,
|
||||||
|
Remix,
|
||||||
|
DJMix,
|
||||||
|
Mixtape_Street,
|
||||||
|
Demo,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -507,3 +507,26 @@ TEST_F(DatabaseFixture, Release_getDiscCount)
|
|||||||
EXPECT_EQ(release.get()->getDiscCount(), 2);
|
EXPECT_EQ(release.get()->getDiscCount(), 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DatabaseFixture, Release_releaseType)
|
||||||
|
{
|
||||||
|
ScopedRelease release {session, "MyRelease"};
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
EXPECT_EQ(release.get()->getPrimaryType(), std::nullopt);
|
||||||
|
EXPECT_EQ(release.get()->getSecondaryTypes(), EnumSet<ReleaseTypeSecondary> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createUniqueTransaction()};
|
||||||
|
release.get().modify()->setPrimaryType({ ReleaseTypePrimary::Album });
|
||||||
|
release.get().modify()->setSecondaryTypes({ ReleaseTypeSecondary::Compilation });
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
EXPECT_EQ(release.get()->getPrimaryType(), ReleaseTypePrimary::Album);
|
||||||
|
EXPECT_TRUE(release.get()->getSecondaryTypes().contains(ReleaseTypeSecondary::Compilation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -115,6 +115,67 @@ namespace
|
|||||||
return artists;
|
return artists;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReleaseTypePrimary convertReleaseTypePrimary(MetaData::Release::PrimaryType type)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MetaData::Release::PrimaryType::Album: return ReleaseTypePrimary::Album;
|
||||||
|
case MetaData::Release::PrimaryType::Single: return ReleaseTypePrimary::Single;
|
||||||
|
case MetaData::Release::PrimaryType::EP: return ReleaseTypePrimary::EP;
|
||||||
|
case MetaData::Release::PrimaryType::Broadcast: return ReleaseTypePrimary::Broadcast;
|
||||||
|
case MetaData::Release::PrimaryType::Other: return ReleaseTypePrimary::Other;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReleaseTypePrimary::Other;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnumSet<ReleaseTypeSecondary> convertReleaseTypesSecondary(EnumSet<MetaData::Release::SecondaryType> types)
|
||||||
|
{
|
||||||
|
EnumSet<ReleaseTypeSecondary> res;
|
||||||
|
|
||||||
|
for (MetaData::Release::SecondaryType type : types)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case MetaData::Release::SecondaryType::Compilation:
|
||||||
|
res.insert(ReleaseTypeSecondary::Compilation);
|
||||||
|
break;
|
||||||
|
case MetaData::Release::SecondaryType::Soundtrack:
|
||||||
|
res.insert(ReleaseTypeSecondary::Soundtrack);
|
||||||
|
break;
|
||||||
|
case MetaData::Release::SecondaryType::Spokenword:
|
||||||
|
res.insert(ReleaseTypeSecondary::Spokenword);
|
||||||
|
break;
|
||||||
|
case MetaData::Release::SecondaryType::Interview:
|
||||||
|
res.insert(ReleaseTypeSecondary::Interview);
|
||||||
|
break;
|
||||||
|
case MetaData::Release::SecondaryType::Audiobook:
|
||||||
|
res.insert(ReleaseTypeSecondary::Audiobook);
|
||||||
|
break;
|
||||||
|
case MetaData::Release::SecondaryType::AudioDrama:
|
||||||
|
res.insert(ReleaseTypeSecondary::AudioDrama);
|
||||||
|
break;
|
||||||
|
case MetaData::Release::SecondaryType::Live:
|
||||||
|
res.insert(ReleaseTypeSecondary::Live);
|
||||||
|
break;
|
||||||
|
case MetaData::Release::SecondaryType::Remix:
|
||||||
|
res.insert(ReleaseTypeSecondary::Remix);
|
||||||
|
break;
|
||||||
|
case MetaData::Release::SecondaryType::DJMix:
|
||||||
|
res.insert(ReleaseTypeSecondary::DJMix);
|
||||||
|
break;
|
||||||
|
case MetaData::Release::SecondaryType::Mixtape_Street:
|
||||||
|
res.insert(ReleaseTypeSecondary::Mixtape_Street);
|
||||||
|
break;
|
||||||
|
case MetaData::Release::SecondaryType::Demo:
|
||||||
|
res.insert(ReleaseTypeSecondary::Demo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
updateReleaseIfNeeded(Release::pointer release, const MetaData::Release& releaseInfo)
|
updateReleaseIfNeeded(Release::pointer release, const MetaData::Release& releaseInfo)
|
||||||
{
|
{
|
||||||
@@ -122,6 +183,15 @@ namespace
|
|||||||
release.modify()->setName(releaseInfo.name);
|
release.modify()->setName(releaseInfo.name);
|
||||||
if (release->getTotalDisc() != releaseInfo.mediumCount)
|
if (release->getTotalDisc() != releaseInfo.mediumCount)
|
||||||
release.modify()->setTotalDisc(releaseInfo.mediumCount);
|
release.modify()->setTotalDisc(releaseInfo.mediumCount);
|
||||||
|
if (releaseInfo.primaryType)
|
||||||
|
{
|
||||||
|
const ReleaseTypePrimary primaryType {convertReleaseTypePrimary(*releaseInfo.primaryType)};
|
||||||
|
if (release->getPrimaryType() != primaryType)
|
||||||
|
release.modify()->setPrimaryType(primaryType);
|
||||||
|
}
|
||||||
|
const EnumSet<ReleaseTypeSecondary> secondaryTypes {convertReleaseTypesSecondary(releaseInfo.secondaryTypes)};
|
||||||
|
if (release->getSecondaryTypes() != secondaryTypes)
|
||||||
|
release.modify()->setSecondaryTypes(secondaryTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
Release::pointer
|
Release::pointer
|
||||||
|
|||||||
@@ -31,9 +31,11 @@ class EnumSet
|
|||||||
static_assert(std::is_enum<T>::value);
|
static_assert(std::is_enum<T>::value);
|
||||||
static_assert(std::is_same<underlying_type, std::uint64_t>::value || std::is_same<underlying_type, std::uint32_t>::value);
|
static_assert(std::is_same<underlying_type, std::uint64_t>::value || std::is_same<underlying_type, std::uint32_t>::value);
|
||||||
|
|
||||||
using index_type = std::uint_fast8_t;
|
using IndexType = std::uint_fast8_t;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
using ValueType = underlying_type;
|
||||||
|
|
||||||
EnumSet() = default;
|
EnumSet() = default;
|
||||||
constexpr EnumSet(std::initializer_list<T> values)
|
constexpr EnumSet(std::initializer_list<T> values)
|
||||||
{
|
{
|
||||||
@@ -112,14 +114,14 @@ class EnumSet
|
|||||||
private:
|
private:
|
||||||
friend class EnumSet;
|
friend class EnumSet;
|
||||||
|
|
||||||
constexpr iterator(const EnumSet& _container, index_type _index)
|
constexpr iterator(const EnumSet& _container, IndexType _index)
|
||||||
: _container {_container}
|
: _container {_container}
|
||||||
, _index {_index}
|
, _index {_index}
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
const EnumSet& _container;
|
const EnumSet& _container;
|
||||||
index_type _index;
|
IndexType _index;
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr iterator begin() const
|
constexpr iterator begin() const
|
||||||
@@ -132,25 +134,45 @@ class EnumSet
|
|||||||
return iterator {*this, npos};
|
return iterator {*this, npos};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
constexpr underlying_type getBitfield() const
|
||||||
static_assert(std::numeric_limits<index_type>::max() >= sizeof(underlying_type) * 8);
|
{
|
||||||
enum : index_type { npos = sizeof(underlying_type) * 8 };
|
return _bitfield;
|
||||||
|
}
|
||||||
|
|
||||||
constexpr index_type getFirstBitSetIndex(index_type start = {}) const
|
constexpr void setBitfield(underlying_type bitfield)
|
||||||
|
{
|
||||||
|
_bitfield = bitfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator==(const EnumSet other) const
|
||||||
|
{
|
||||||
|
return _bitfield == other._bitfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator!=(const EnumSet other) const
|
||||||
|
{
|
||||||
|
return _bitfield != other._bitfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static_assert(std::numeric_limits<IndexType>::max() >= sizeof(underlying_type) * 8);
|
||||||
|
enum : IndexType { npos = sizeof(underlying_type) * 8 };
|
||||||
|
|
||||||
|
constexpr IndexType getFirstBitSetIndex(IndexType start = {}) const
|
||||||
{
|
{
|
||||||
assert(start < npos);
|
assert(start < npos);
|
||||||
|
|
||||||
// return npos if no bit found
|
// return npos if no bit found
|
||||||
index_type res {countTrailingZero(_bitfield >> start)};
|
IndexType res {countTrailingZero(_bitfield >> start)};
|
||||||
if (res == npos)
|
if (res == npos)
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
return res + start;
|
return res + start;
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr index_type countTrailingZero(underlying_type bitField)
|
static constexpr IndexType countTrailingZero(underlying_type bitField)
|
||||||
{
|
{
|
||||||
index_type res {};
|
IndexType res {};
|
||||||
|
|
||||||
while (res < (sizeof(underlying_type) * 8) && (bitField & 1) == 0)
|
while (res < (sizeof(underlying_type) * 8) && (bitField & 1) == 0)
|
||||||
{
|
{
|
||||||
@@ -166,5 +188,3 @@ class EnumSet
|
|||||||
|
|
||||||
underlying_type _bitfield{};
|
underlying_type _bitfield{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
include(GoogleTest)
|
include(GoogleTest)
|
||||||
|
|
||||||
add_executable(test-utils
|
add_executable(test-utils
|
||||||
String.cpp
|
EnumSet.cpp
|
||||||
RecursiveSharedMutex.cpp
|
RecursiveSharedMutex.cpp
|
||||||
|
String.cpp
|
||||||
Utils.cpp
|
Utils.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "utils/EnumSet.hpp"
|
||||||
|
|
||||||
|
TEST(EnumSet, ctr)
|
||||||
|
{
|
||||||
|
enum class Foo
|
||||||
|
{
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
constexpr EnumSet<Foo> test {Foo::One};
|
||||||
|
|
||||||
|
static_assert(!test.empty());
|
||||||
|
static_assert(test.contains(Foo::One));
|
||||||
|
static_assert(!test.contains(Foo::Two));
|
||||||
|
|
||||||
|
EXPECT_TRUE(!test.empty());
|
||||||
|
EXPECT_TRUE(test.contains(Foo::One));
|
||||||
|
EXPECT_FALSE(test.contains(Foo::Two));
|
||||||
|
|
||||||
|
static_assert(test.getBitfield() != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
constexpr EnumSet<Foo> test {Foo::One, Foo::Two};
|
||||||
|
constexpr auto bitfield {test.getBitfield()};
|
||||||
|
|
||||||
|
EnumSet<Foo> test2;
|
||||||
|
EXPECT_FALSE(test2.contains(Foo::One));
|
||||||
|
EXPECT_FALSE(test2.contains(Foo::Two));
|
||||||
|
|
||||||
|
test2.setBitfield(bitfield);
|
||||||
|
|
||||||
|
EXPECT_TRUE(test2.contains(Foo::One));
|
||||||
|
EXPECT_TRUE(test2.contains(Foo::Two));
|
||||||
|
EXPECT_EQ(test, test2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,6 +52,43 @@ using namespace Database;
|
|||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
|
Wt::WString
|
||||||
|
buildReleaseTypeString(ReleaseTypePrimary primaryType, EnumSet<ReleaseTypeSecondary> secondaryTypes)
|
||||||
|
{
|
||||||
|
Wt::WString res;
|
||||||
|
|
||||||
|
switch (primaryType)
|
||||||
|
{
|
||||||
|
case ReleaseTypePrimary::Album: res = Wt::WString::tr("Lms.Explore.Release.type-primary-album"); break;
|
||||||
|
case ReleaseTypePrimary::Broadcast: res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast"); break;
|
||||||
|
case ReleaseTypePrimary::EP: res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep"); break;
|
||||||
|
case ReleaseTypePrimary::Single: res = Wt::WString::tr("Lms.Explore.Release.type-primary-single"); break;
|
||||||
|
case ReleaseTypePrimary::Other: res = Wt::WString::tr("Lms.Explore.Release.type-primary-other"); break;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (ReleaseTypeSecondary secondaryType : secondaryTypes)
|
||||||
|
{
|
||||||
|
res += Wt::WString {" · "};
|
||||||
|
|
||||||
|
switch (secondaryType)
|
||||||
|
{
|
||||||
|
case ReleaseTypeSecondary::Compilation: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation"); break;
|
||||||
|
case ReleaseTypeSecondary::Spokenword: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword"); break;
|
||||||
|
case ReleaseTypeSecondary::Soundtrack: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack"); break;
|
||||||
|
case ReleaseTypeSecondary::Interview: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview"); break;
|
||||||
|
case ReleaseTypeSecondary::Audiobook: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook"); break;
|
||||||
|
case ReleaseTypeSecondary::AudioDrama: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama"); break;
|
||||||
|
case ReleaseTypeSecondary::Live: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live"); break;
|
||||||
|
case ReleaseTypeSecondary::Remix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix"); break;
|
||||||
|
case ReleaseTypeSecondary::DJMix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix"); break;
|
||||||
|
case ReleaseTypeSecondary::Mixtape_Street: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street"); break;
|
||||||
|
case ReleaseTypeSecondary::Demo: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo"); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
showReleaseInfoModal(Database::ReleaseId releaseId)
|
showReleaseInfoModal(Database::ReleaseId releaseId)
|
||||||
{
|
{
|
||||||
@@ -65,7 +102,12 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
|
|||||||
Wt::WWidget* releaseInfoPtr {releaseInfo.get()};
|
Wt::WWidget* releaseInfoPtr {releaseInfo.get()};
|
||||||
releaseInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
releaseInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
|
||||||
Wt::WContainerWidget* artistTable {releaseInfo->bindNew<Wt::WContainerWidget>("artist-table")};
|
if (auto primaryReleaseType {release->getPrimaryType()})
|
||||||
|
{
|
||||||
|
releaseInfo->setCondition("if-has-release-type", true);
|
||||||
|
releaseInfo->bindString("release-type", buildReleaseTypeString(*primaryReleaseType, release->getSecondaryTypes()));
|
||||||
|
}
|
||||||
|
|
||||||
std::map<Wt::WString, std::set<ArtistId>> artistMap;
|
std::map<Wt::WString, std::set<ArtistId>> artistMap;
|
||||||
|
|
||||||
auto addArtists = [&](TrackArtistLinkType linkType, const char* type)
|
auto addArtists = [&](TrackArtistLinkType linkType, const char* type)
|
||||||
@@ -116,13 +158,19 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
|
|||||||
artistMap.erase(itRolelessPerformers);
|
artistMap.erase(itRolelessPerformers);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [role, artistIds] : artistMap)
|
if (!artistMap.empty())
|
||||||
{
|
{
|
||||||
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
|
releaseInfo->setCondition("if-has-artist", true);
|
||||||
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))};
|
Wt::WContainerWidget* artistTable {releaseInfo->bindNew<Wt::WContainerWidget>("artist-table")};
|
||||||
artistsEntry->bindString("type", role);
|
|
||||||
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
for (const auto& [role, artistIds] : artistMap)
|
||||||
artistTable->addWidget(std::move(artistsEntry));
|
{
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
|
||||||
|
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))};
|
||||||
|
artistsEntry->bindString("type", role);
|
||||||
|
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
||||||
|
artistTable->addWidget(std::move(artistsEntry));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: save in DB and mean all this
|
// TODO: save in DB and mean all this
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ namespace UserInterface::TrackListHelpers
|
|||||||
Wt::WWidget* trackInfoPtr {trackInfo.get()};
|
Wt::WWidget* trackInfoPtr {trackInfo.get()};
|
||||||
trackInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
trackInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
|
||||||
Wt::WContainerWidget* artistTable {trackInfo->bindNew<Wt::WContainerWidget>("artist-table")};
|
|
||||||
std::map<Wt::WString, std::set<ArtistId>> artistMap;
|
std::map<Wt::WString, std::set<ArtistId>> artistMap;
|
||||||
|
|
||||||
auto addArtists = [&](TrackArtistLinkType linkType, const char* type)
|
auto addArtists = [&](TrackArtistLinkType linkType, const char* type)
|
||||||
@@ -111,13 +110,19 @@ namespace UserInterface::TrackListHelpers
|
|||||||
artistMap.erase(itRolelessPerformers);
|
artistMap.erase(itRolelessPerformers);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto& [role, artistIds] : artistMap)
|
if (!artistMap.empty())
|
||||||
{
|
{
|
||||||
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
|
trackInfo->setCondition("if-has-artist", true);
|
||||||
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))};
|
Wt::WContainerWidget* artistTable {trackInfo->bindNew<Wt::WContainerWidget>("artist-table")};
|
||||||
artistsEntry->bindString("type", role);
|
|
||||||
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
for (const auto& [role, artistIds] : artistMap)
|
||||||
artistTable->addWidget(std::move(artistsEntry));
|
{
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistContainer(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
|
||||||
|
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))};
|
||||||
|
artistsEntry->bindString("type", role);
|
||||||
|
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
||||||
|
artistTable->addWidget(std::move(artistsEntry));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto audioFile {Av::parseAudioFile(track->getPath())})
|
if (const auto audioFile {Av::parseAudioFile(track->getPath())})
|
||||||
|
|||||||
Reference in New Issue
Block a user