Added some foreachSubRange

This commit is contained in:
emeric
2023-11-08 13:53:27 +01:00
parent 4bc5aee986
commit 6bb6dd6add
2 changed files with 349 additions and 294 deletions
@@ -26,208 +26,225 @@
namespace Database namespace Database
{ {
// Caution: do not change enum values if they are set! // Caution: do not change enum values if they are set!
// Request: // Request:
// size = 0 => no size limit! // size = 0 => no size limit!
// Response (via RangeResults) // Response (via RangeResults)
// size => results size // size => results size
struct Range struct Range
{ {
std::size_t offset {}; std::size_t offset{};
std::size_t size {}; std::size_t size{};
// TODO remove this // TODO remove this
operator bool() const { return size != 0; } operator bool() const { return size != 0; }
}; };
template <typename T> // Func must return true to continue iterating
struct RangeResults template <typename Func>
{ void foreachSubRange(Range range, std::size_t subRangeSize, Func&& func)
Range range; {
std::vector<T> results; assert(subRangeSize > 0);
bool moreResults{};
RangeResults getSubRange(Range subRange) Range subRange{ range.offset, std::min(range.size, subRangeSize) };
{ while (subRange.size > 0)
assert(subRange.offset >= range.offset); {
if (!func(subRange))
break;
if (!subRange.size) subRange.offset += subRange.size;
subRange.size = range.size - (subRange.offset - range.offset); subRange.size = std::min(subRangeSize, range.size - (subRange.offset - range.offset));
}
}
subRange.offset = std::min(subRange.offset, range.offset + range.size); template <typename T>
subRange.size = std::min(subRange.size, range.offset + range.size - subRange.offset); struct RangeResults
{
Range range;
std::vector<T> results;
bool moreResults{};
RangeResults subResults; RangeResults getSubRange(Range subRange)
{
assert(subRange.offset >= range.offset);
auto itBegin {std::cbegin(results) + subRange.offset - range.offset}; if (!subRange.size)
auto itEnd {itBegin + subRange.size}; subRange.size = range.size - (subRange.offset - range.offset);
subResults.results.reserve(std::distance(itBegin, itEnd));
std::copy(itBegin, itEnd, std::back_inserter(subResults.results));
subResults.range = subRange; subRange.offset = std::min(subRange.offset, range.offset + range.size);
if (subRange.offset + subRange.size == range.offset + range.size) subRange.size = std::min(subRange.size, range.offset + range.size - subRange.offset);
subResults.moreResults = moreResults;
else
subResults.moreResults = true;
return subResults; RangeResults subResults;
}
};
struct DateRange auto itBegin{ std::cbegin(results) + subRange.offset - range.offset };
{ auto itEnd{ itBegin + subRange.size };
Wt::WDate begin; subResults.results.reserve(std::distance(itBegin, itEnd));
Wt::WDate end; std::copy(itBegin, itEnd, std::back_inserter(subResults.results));
static DateRange fromYearRange(int from, int to); subResults.range = subRange;
}; if (subRange.offset + subRange.size == range.offset + range.size)
subResults.moreResults = moreResults;
else
subResults.moreResults = true;
struct DiscInfo return subResults;
{ }
std::size_t position; };
std::string name;
};
enum class ArtistSortMethod struct DateRange
{ {
None, Wt::WDate begin;
ByName, Wt::WDate end;
BySortName,
Random,
LastWritten,
StarredDateDesc,
};
enum class ReleaseSortMethod static DateRange fromYearRange(int from, int to);
{ };
None,
Name,
Date,
OriginalDate,
OriginalDateDesc,
Random,
LastWritten,
StarredDateDesc,
};
enum class TrackListSortMethod struct DiscInfo
{ {
None, std::size_t position;
Name, std::string name;
LastModifiedDesc, };
};
enum class TrackSortMethod enum class ArtistSortMethod
{ {
None, None,
Random, ByName,
LastWritten, BySortName,
StarredDateDesc, Random,
Name, LastWritten,
DateDescAndRelease, StarredDateDesc,
Release, // order by disc/track number };
TrackList, // order by asc order in tracklist
};
enum class TrackArtistLinkType enum class ReleaseSortMethod
{ {
Artist = 0, // regular track artist None,
Arranger = 1, Name,
Composer = 2, Date,
Conductor = 3, OriginalDate,
Lyricist = 4, OriginalDateDesc,
Mixer = 5, Random,
Performer = 6, LastWritten,
Producer = 7, StarredDateDesc,
ReleaseArtist = 8, };
Remixer = 9,
Writer = 10,
};
// User selectable audio file formats enum class TrackListSortMethod
enum class AudioFormat {
{ None,
MP3 = 1, Name,
OGG_OPUS = 2, LastModifiedDesc,
OGG_VORBIS = 3, };
WEBM_VORBIS = 4,
MATROSKA_OPUS = 5,
};
using Bitrate = std::uint32_t; enum class TrackSortMethod
// Do not remove values! {
void visitAllowedAudioBitrates(std::function<void(Bitrate)>); None,
bool isAudioBitrateAllowed(Bitrate bitrate); Random,
LastWritten,
StarredDateDesc,
Name,
DateDescAndRelease,
Release, // order by disc/track number
TrackList, // order by asc order in tracklist
};
enum class ScrobblingBackend enum class TrackArtistLinkType
{ {
Internal = 0, Artist = 0, // regular track artist
ListenBrainz = 1, Arranger = 1,
}; Composer = 2,
Conductor = 3,
Lyricist = 4,
Mixer = 5,
Performer = 6,
Producer = 7,
ReleaseArtist = 8,
Remixer = 9,
Writer = 10,
};
enum class FeedbackBackend // User selectable audio file formats
{ enum class AudioFormat
Internal = 0, {
ListenBrainz = 1, MP3 = 1,
}; OGG_OPUS = 2,
OGG_VORBIS = 3,
WEBM_VORBIS = 4,
MATROSKA_OPUS = 5,
};
enum class SyncState using Bitrate = std::uint32_t;
{ // Do not remove values!
PendingAdd = 0, void visitAllowedAudioBitrates(std::function<void(Bitrate)>);
Synchronized = 1, bool isAudioBitrateAllowed(Bitrate bitrate);
PendingRemove = 2,
};
enum class UserType enum class ScrobblingBackend
{ {
REGULAR = 0, Internal = 0,
ADMIN = 1, ListenBrainz = 1,
DEMO = 2, };
};
enum class UITheme enum class FeedbackBackend
{ {
Light = 0, Internal = 0,
Dark = 1, ListenBrainz = 1,
}; };
enum class SubsonicArtistListMode enum class SyncState
{ {
AllArtists = 0, PendingAdd = 0,
ReleaseArtists = 1, Synchronized = 1,
TrackArtists = 2, PendingRemove = 2,
}; };
enum class TrackListType enum class UserType
{ {
Playlist, // user controlled playlists REGULAR = 0,
Internal, // internal usage (current playqueue, history, ...) ADMIN = 1,
}; DEMO = 2,
};
// as defined in https://musicbrainz.org/doc/Release_Group/Type enum class UITheme
enum class ReleaseTypePrimary {
{ Light = 0,
Album, Dark = 1,
Single, };
EP,
Broadcast,
Other,
};
enum class ReleaseTypeSecondary enum class SubsonicArtistListMode
{ {
Compilation, AllArtists = 0,
Soundtrack, ReleaseArtists = 1,
Spokenword, TrackArtists = 2,
Interview, };
Audiobook,
AudioDrama, enum class TrackListType
Live, {
Remix, Playlist, // user controlled playlists
DJMix, Internal, // internal usage (current playqueue, history, ...)
Mixtape_Street, };
Demo,
}; // 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,
};
} }
+158 -120
View File
@@ -36,174 +36,212 @@
#include "services/database/User.hpp" #include "services/database/User.hpp"
TmpDatabase::TmpDatabase() TmpDatabase::TmpDatabase()
: _tmpFile {std::tmpnam(nullptr)} : _tmpFile{ std::tmpnam(nullptr) }
, _fileDeleter {_tmpFile} , _fileDeleter{ _tmpFile }
, _db {_tmpFile} , _db{ _tmpFile }
{ {
} }
Database::Db& Database::Db&
TmpDatabase::getDb() TmpDatabase::getDb()
{ {
return _db; return _db;
} }
DatabaseFixture::~DatabaseFixture() DatabaseFixture::~DatabaseFixture()
{ {
testDatabaseEmpty(); testDatabaseEmpty();
} }
void void
DatabaseFixture::SetUpTestCase() DatabaseFixture::SetUpTestCase()
{ {
_tmpDb = std::make_unique<TmpDatabase>(); _tmpDb = std::make_unique<TmpDatabase>();
{ {
Database::Session s {_tmpDb->getDb()}; Database::Session s{ _tmpDb->getDb() };
s.prepareTables(); s.prepareTables();
s.analyze(); s.analyze();
// remove default created entries // remove default created entries
{ {
auto transaction {s.createUniqueTransaction()}; auto transaction{ s.createUniqueTransaction() };
auto clusterTypes {Database::ClusterType::find(s, Database::Range {})}; auto clusterTypes{ Database::ClusterType::find(s, Database::Range {}) };
for (auto clusterTypeId : clusterTypes.results) for (auto clusterTypeId : clusterTypes.results)
{ {
auto clusterType {Database::ClusterType::find(s, clusterTypeId)}; auto clusterType{ Database::ClusterType::find(s, clusterTypeId) };
clusterType.remove(); clusterType.remove();
} }
} }
} }
} }
void void
DatabaseFixture::TearDownTestCase() DatabaseFixture::TearDownTestCase()
{ {
_tmpDb.reset(); _tmpDb.reset();
} }
void void
DatabaseFixture::testDatabaseEmpty() DatabaseFixture::testDatabaseEmpty()
{ {
using namespace Database; using namespace Database;
auto uniqueTransaction {session.createUniqueTransaction()}; auto uniqueTransaction{ session.createUniqueTransaction() };
EXPECT_EQ(Artist::getCount(session), 0); EXPECT_EQ(Artist::getCount(session), 0);
EXPECT_EQ(Cluster::getCount(session), 0); EXPECT_EQ(Cluster::getCount(session), 0);
EXPECT_EQ(ClusterType::getCount(session), 0); EXPECT_EQ(ClusterType::getCount(session), 0);
EXPECT_EQ(Listen::getCount(session), 0); EXPECT_EQ(Listen::getCount(session), 0);
EXPECT_EQ(Release::getCount(session), 0); EXPECT_EQ(Release::getCount(session), 0);
EXPECT_EQ(StarredArtist::getCount(session), 0); EXPECT_EQ(StarredArtist::getCount(session), 0);
EXPECT_EQ(StarredRelease::getCount(session), 0); EXPECT_EQ(StarredRelease::getCount(session), 0);
EXPECT_EQ(StarredTrack::getCount(session), 0); EXPECT_EQ(StarredTrack::getCount(session), 0);
EXPECT_EQ(Track::getCount(session), 0); EXPECT_EQ(Track::getCount(session), 0);
EXPECT_EQ(TrackBookmark::getCount(session), 0); EXPECT_EQ(TrackBookmark::getCount(session), 0);
EXPECT_EQ(TrackList::getCount(session), 0); EXPECT_EQ(TrackList::getCount(session), 0);
EXPECT_EQ(User::getCount(session), 0); EXPECT_EQ(User::getCount(session), 0);
} }
TEST_F(DatabaseFixture, Common_subRangeEmpty) TEST_F(DatabaseFixture, Common_subRangeEmpty)
{ {
using namespace Database; using namespace Database;
RangeResults<int> results; RangeResults<int> results;
results.range = Range {0, 0}; results.range = Range{ 0, 0 };
results.results = {}; results.results = {};
results.moreResults = false; results.moreResults = false;
{ {
auto subRange {results.getSubRange(Range {0, 0})}; auto subRange{ results.getSubRange(Range {0, 0}) };
EXPECT_FALSE(subRange.moreResults); EXPECT_FALSE(subRange.moreResults);
ASSERT_EQ(subRange.results.size(), 0); ASSERT_EQ(subRange.results.size(), 0);
EXPECT_EQ(subRange.range, Range {}); EXPECT_EQ(subRange.range, Range{});
} }
{ {
auto subRange {results.getSubRange(Range {0, 1})}; auto subRange{ results.getSubRange(Range {0, 1}) };
EXPECT_FALSE(subRange.moreResults); EXPECT_FALSE(subRange.moreResults);
ASSERT_EQ(subRange.results.size(), 0); ASSERT_EQ(subRange.results.size(), 0);
} }
}
TEST_F(DatabaseFixture, Common_subRangeForeach)
{
using namespace Database;
struct TestCase
{
Range range;
std::size_t subRangeSize;
std::vector<Range> expectedSubRanges;
};
TestCase testCases[]
{
{Range{0, 0}, 1, {}},
{Range{1, 0}, 1, {}},
{Range{1, 1}, 1, { Range{ 1,1 } }},
{Range{1, 3}, 1, { Range{ 1,1 }, Range {2,1}, Range{3,1} }},
{Range{0, 100}, 100, { Range{0,100} }},
{Range{0, 50}, 100, { Range{0,50} }},
{Range{100, 200}, 100, { Range{100,100}, Range{200,100} }},
{Range{100, 101}, 100, { Range{100,100}, Range{200,1}}},
{Range{1000, 10}, 100, { Range{1000,10} }},
{Range{1, 100}, 50, { Range{1,50}, Range{51, 50} }},
};
for (const TestCase& test : testCases)
{
std::vector<Range> subRanges;
foreachSubRange(test.range, test.subRangeSize, [&](Range subRange)
{
subRanges.push_back(subRange);
return true;
});
EXPECT_EQ(subRanges, test.expectedSubRanges) << ", test index = " << std::distance(std::cbegin(testCases), &test);
}
} }
TEST_F(DatabaseFixture, Common_IdType) TEST_F(DatabaseFixture, Common_IdType)
{ {
using namespace Database; using namespace Database;
{ {
const IdType id{}; const IdType id{};
EXPECT_FALSE(id.isValid()); EXPECT_FALSE(id.isValid());
} }
{ {
const IdType id{0}; const IdType id{ 0 };
EXPECT_TRUE(id.isValid()); EXPECT_TRUE(id.isValid());
} }
{ {
const IdType id1{0}; const IdType id1{ 0 };
const IdType id2{0}; const IdType id2{ 0 };
EXPECT_EQ(id1, id2); EXPECT_EQ(id1, id2);
} }
{ {
const IdType id1{0}; const IdType id1{ 0 };
const IdType id2{1}; const IdType id2{ 1 };
EXPECT_NE(id1, id2); EXPECT_NE(id1, id2);
EXPECT_LT(id1, id2); EXPECT_LT(id1, id2);
EXPECT_GT(id2, id1); EXPECT_GT(id2, id1);
} }
} }
TEST_F(DatabaseFixture, Common_subRange) TEST_F(DatabaseFixture, Common_subRange)
{ {
using namespace Database; using namespace Database;
RangeResults<int> results; RangeResults<int> results;
results.range = Range {0, 2}; results.range = Range{ 0, 2 };
results.results = {5, 6}; results.results = { 5, 6 };
results.moreResults = false; results.moreResults = false;
{ {
auto subRange {results.getSubRange(Range {0, 1})}; auto subRange{ results.getSubRange(Range {0, 1}) };
EXPECT_TRUE(subRange.moreResults); EXPECT_TRUE(subRange.moreResults);
ASSERT_EQ(subRange.results.size(), 1); ASSERT_EQ(subRange.results.size(), 1);
EXPECT_EQ(subRange.results.front(), 5); EXPECT_EQ(subRange.results.front(), 5);
} }
{ {
auto subRange {results.getSubRange(Range {1, 1})}; auto subRange{ results.getSubRange(Range {1, 1}) };
EXPECT_FALSE(subRange.moreResults); EXPECT_FALSE(subRange.moreResults);
ASSERT_EQ(subRange.results.size(), 1); ASSERT_EQ(subRange.results.size(), 1);
EXPECT_EQ(subRange.results.front(), 6); EXPECT_EQ(subRange.results.front(), 6);
} }
{ {
auto subRange {results.getSubRange(Range {0, 2})}; auto subRange{ results.getSubRange(Range {0, 2}) };
EXPECT_FALSE(subRange.moreResults); EXPECT_FALSE(subRange.moreResults);
ASSERT_EQ(subRange.results.size(), 2); ASSERT_EQ(subRange.results.size(), 2);
EXPECT_EQ(subRange.results.front(), 5); EXPECT_EQ(subRange.results.front(), 5);
EXPECT_EQ(subRange.results.back(), 6); EXPECT_EQ(subRange.results.back(), 6);
} }
{ {
auto subRange {results.getSubRange(Range {})}; auto subRange{ results.getSubRange(Range {}) };
EXPECT_FALSE(subRange.moreResults); EXPECT_FALSE(subRange.moreResults);
ASSERT_EQ(subRange.results.size(), 2); ASSERT_EQ(subRange.results.size(), 2);
EXPECT_EQ(subRange.results.front(), 5); EXPECT_EQ(subRange.results.front(), 5);
EXPECT_EQ(subRange.results.back(), 6); EXPECT_EQ(subRange.results.back(), 6);
EXPECT_EQ(subRange.range, results.range); EXPECT_EQ(subRange.range, results.range);
} }
{ {
auto subRange {results.getSubRange(Range {1, 0})}; auto subRange{ results.getSubRange(Range {1, 0}) };
EXPECT_FALSE(subRange.moreResults); EXPECT_FALSE(subRange.moreResults);
ASSERT_EQ(subRange.results.size(), 1); ASSERT_EQ(subRange.results.size(), 1);
EXPECT_EQ(subRange.results.front(), 6); EXPECT_EQ(subRange.results.front(), 6);
const Range expectedRange {1, 1}; const Range expectedRange{ 1, 1 };
EXPECT_EQ(subRange.range, expectedRange); EXPECT_EQ(subRange.range, expectedRange);
} }
{ {
auto subRange {results.getSubRange(Range {3, 2})}; auto subRange{ results.getSubRange(Range {3, 2}) };
EXPECT_FALSE(subRange.moreResults); EXPECT_FALSE(subRange.moreResults);
ASSERT_EQ(subRange.results.size(), 0); ASSERT_EQ(subRange.results.size(), 0);
const Range expectedRange {2, 0}; const Range expectedRange{ 2, 0 };
EXPECT_EQ(subRange.range, expectedRange); EXPECT_EQ(subRange.range, expectedRange);
} }
} }