From 2e4947dd423b9eee459db8746ca8ff5caa3a6312 Mon Sep 17 00:00:00 2001 From: emeric Date: Wed, 6 Mar 2024 18:50:25 +0100 Subject: [PATCH 01/41] Always use the provided custom delimiters to split tags --- approot/messages.xml | 4 +-- approot/messages_fr.xml | 4 +-- approot/messages_it.xml | 4 +-- src/libs/metadata/impl/AvFormatTagReader.hpp | 1 - src/libs/metadata/impl/ITagReader.hpp | 2 -- src/libs/metadata/impl/Parser.cpp | 28 +++++++++----------- src/libs/metadata/impl/TagLibTagReader.cpp | 2 -- src/libs/metadata/impl/TagLibTagReader.hpp | 2 -- src/libs/metadata/test/Parser.cpp | 20 -------------- src/libs/metadata/test/TestTagReader.hpp | 21 --------------- 10 files changed, 18 insertions(+), 70 deletions(-) diff --git a/approot/messages.xml b/approot/messages.xml index d774dec0..77d99d7f 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -64,9 +64,9 @@ Root directory -Delimiter to be used for splitting artist tags (only if the file does not contain any multi-valued tag) +Delimiter to be used for splitting artist tags Daily -Delimiter to be used for splitting other tags (only if the file does not contain any multi-valued tag) +Delimiter to be used for splitting other tags Extra tags to scan (use ; as separator) Hourly Scan now! diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index c4af2527..3629fc1a 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -64,9 +64,9 @@ Répertoire racine -Délimiteur à utiliser pour séparer les tags d'artistes (uniquement si le fichier ne contient pas de tag à valeurs multiples) +Délimiteur à utiliser pour séparer les tags d'artistes Tous les jours -Délimiteur à utiliser pour séparer les autres tags (uniquement si le fichier ne contient pas de tag à valeurs multiples) +Délimiteur à utiliser pour séparer les autres tags Tags supplémentaires à scanner (utiliser ; comme séparateur) Toutes les heures Scanner maintenant ! diff --git a/approot/messages_it.xml b/approot/messages_it.xml index f7136b27..b16fba7e 100644 --- a/approot/messages_it.xml +++ b/approot/messages_it.xml @@ -64,9 +64,9 @@ Cartella principale -Delimitatore da utilizzare per separare i tag degli artisti (solo se il file non contiene alcun tag con valori multipli) +Delimitatore da utilizzare per separare i tag degli artisti Giornaliera -Delimitatore da utilizzare per separare gli altri tag (solo se il file non contiene alcun tag con valori multipli) +Delimitatore da utilizzare per separare gli altri tag Tag aggiuntivi da scansionare (usa ; come separatore) Ogni ora Scansiona ora! diff --git a/src/libs/metadata/impl/AvFormatTagReader.hpp b/src/libs/metadata/impl/AvFormatTagReader.hpp index d2039eb4..7172a5a6 100644 --- a/src/libs/metadata/impl/AvFormatTagReader.hpp +++ b/src/libs/metadata/impl/AvFormatTagReader.hpp @@ -36,7 +36,6 @@ namespace MetaData AvFormatTagReader(const AvFormatTagReader&) = delete; AvFormatTagReader& operator=(const AvFormatTagReader&) = delete; - bool hasMultiValuedTags() const override { return false; /* not supported */} void visitTagValues(TagType tag, TagValueVisitor visitor) const override; void visitTagValues(std::string_view tag, TagValueVisitor visitor) const override; void visitPerformerTags(PerformerVisitor visitor) const override; diff --git a/src/libs/metadata/impl/ITagReader.hpp b/src/libs/metadata/impl/ITagReader.hpp index ef0dbf14..91b8e7b6 100644 --- a/src/libs/metadata/impl/ITagReader.hpp +++ b/src/libs/metadata/impl/ITagReader.hpp @@ -145,8 +145,6 @@ namespace MetaData public: virtual ~ITagReader() = default; - virtual bool hasMultiValuedTags() const = 0; - using TagValueVisitor = std::function; virtual void visitTagValues(TagType tag, TagValueVisitor visitor) const = 0; virtual void visitTagValues(std::string_view tag, TagValueVisitor visitor) const = 0; diff --git a/src/libs/metadata/impl/Parser.cpp b/src/libs/metadata/impl/Parser.cpp index 80a10e66..f9744434 100644 --- a/src/libs/metadata/impl/Parser.cpp +++ b/src/libs/metadata/impl/Parser.cpp @@ -44,19 +44,18 @@ namespace MetaData visitor(tag); } }; - if (!tagReader.hasMultiValuedTags()) + for (std::string_view tagDelimiter : tagDelimiters) { - for (std::string_view tagDelimiter : tagDelimiters) + if (value.find(tagDelimiter) != std::string_view::npos) { - if (value.find(tagDelimiter) != std::string_view::npos) - { - for (std::string_view splitTag : StringUtils::splitString(value, tagDelimiter)) - visitTagIfNonEmpty(splitTag); - return; - } + for (std::string_view splitTag : StringUtils::splitString(value, tagDelimiter)) + visitTagIfNonEmpty(splitTag); + + return; } } + // no delimiter found, or no delimiter to be used visitTagIfNonEmpty(value); }); } @@ -81,17 +80,14 @@ namespace MetaData tagReader.visitTagValues(tagType, [&](std::string_view value) { - if (!tagReader.hasMultiValuedTags()) + for (std::string_view tagDelimiter : tagDelimiters) { - for (std::string_view tagDelimiter : tagDelimiters) + if (value.find(tagDelimiter) != std::string_view::npos) { - if (value.find(tagDelimiter) != std::string_view::npos) - { - for (std::string_view splitTag : StringUtils::splitString(value, tagDelimiter)) - addTagIfNonEmpty(splitTag); + for (std::string_view splitTag : StringUtils::splitString(value, tagDelimiter)) + addTagIfNonEmpty(splitTag); - return; - } + return; } } diff --git a/src/libs/metadata/impl/TagLibTagReader.cpp b/src/libs/metadata/impl/TagLibTagReader.cpp index a44743a4..97ebb9d2 100644 --- a/src/libs/metadata/impl/TagLibTagReader.cpp +++ b/src/libs/metadata/impl/TagLibTagReader.cpp @@ -293,8 +293,6 @@ namespace MetaData for (const auto& value : _propertyMap.unsupportedData()) LMS_LOG(METADATA, DEBUG, "Unknown value: '" << value.to8Bit(true) << "'"); } - - _hasMultiValuedTags = std::any_of(std::cbegin(_propertyMap), std::cend(_propertyMap), [](const auto& entry) { return entry.second.size() > 1; }); } void TagLibTagReader::visitTagValues(TagType tag, TagValueVisitor visitor) const diff --git a/src/libs/metadata/impl/TagLibTagReader.hpp b/src/libs/metadata/impl/TagLibTagReader.hpp index faf535a3..cc3dd304 100644 --- a/src/libs/metadata/impl/TagLibTagReader.hpp +++ b/src/libs/metadata/impl/TagLibTagReader.hpp @@ -38,7 +38,6 @@ namespace MetaData TagLibTagReader(const TagLibTagReader&) = delete; TagLibTagReader& operator=(const TagLibTagReader&) = delete; - bool hasMultiValuedTags() const override { return _hasMultiValuedTags; } void visitTagValues(TagType tag, TagValueVisitor visitor) const override; void visitTagValues(std::string_view tag, TagValueVisitor visitor) const override; void visitPerformerTags(PerformerVisitor visitor) const override; @@ -52,6 +51,5 @@ namespace MetaData TagLib::FileRef _file; TagLib::PropertyMap _propertyMap; // case-insensitive keys bool _hasEmbeddedCover{}; - bool _hasMultiValuedTags{}; }; } // namespace MetaData diff --git a/src/libs/metadata/test/Parser.cpp b/src/libs/metadata/test/Parser.cpp index e1e67ff3..919d9d59 100644 --- a/src/libs/metadata/test/Parser.cpp +++ b/src/libs/metadata/test/Parser.cpp @@ -246,24 +246,4 @@ namespace MetaData EXPECT_EQ(track->artists[1].name, "Other Artist"); EXPECT_EQ(track->artistDisplayName, "This / is ; One Artist \\ Other Artist"); } - - TEST(Parser, customDelimiters_notWithMultiValuedTags) - { - const TestTagReader testTags{ - { - { TagType::Genre, { "Genre1 ; Genre2" } }, - { TagType::Language, { "Lang1", "Lang2" } }, - } - }; - - Parser parser; - static_cast(parser).setDefaultTagDelimiters(std::vector{ " ; " }); - std::unique_ptr track{ parser.parse(testTags) }; - - ASSERT_EQ(track->genres.size(), 1); - EXPECT_EQ(track->genres[0], "Genre1 ; Genre2"); - ASSERT_EQ(track->languages.size(), 2); - EXPECT_EQ(track->languages[0], "Lang1"); - EXPECT_EQ(track->languages[1], "Lang2"); - } } diff --git a/src/libs/metadata/test/TestTagReader.hpp b/src/libs/metadata/test/TestTagReader.hpp index 23d8b8ae..68686176 100644 --- a/src/libs/metadata/test/TestTagReader.hpp +++ b/src/libs/metadata/test/TestTagReader.hpp @@ -25,18 +25,6 @@ namespace MetaData { - namespace - { - template - bool tagMapHasMultiValuedTags(const TagMapType& m) - { - return std::any_of(std::cbegin(m), std::cend(m), [](const auto& tagPair) - { - return tagPair.second.size() > 1; - }); - } - } - class TestTagReader : public ITagReader { public: @@ -53,14 +41,6 @@ namespace MetaData , _performers{ std::move(performers) } , _extraUserTags{ std::move(extraUserTags) } { - _hasMultiValuedTags = tagMapHasMultiValuedTags(_tags) - || tagMapHasMultiValuedTags(_performers) - || tagMapHasMultiValuedTags(_extraUserTags); - } - - bool hasMultiValuedTags() const override - { - return _hasMultiValuedTags; } void visitTagValues(TagType tag, TagValueVisitor visitor) const override @@ -102,6 +82,5 @@ namespace MetaData const Tags _tags; const Performers _performers; const ExtraUserTags _extraUserTags; - bool _hasMultiValuedTags; }; } \ No newline at end of file From 41b944040c190ae1927f0e91a2e0c28e4234964c Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 18:35:36 +0100 Subject: [PATCH 02/41] Added LiteralString helper class --- .../utils/include/utils/LiteralString.hpp | 95 +++++++++++++++++++ src/libs/utils/test/CMakeLists.txt | 1 + src/libs/utils/test/LiteralString.cpp | 69 ++++++++++++++ 3 files changed, 165 insertions(+) create mode 100644 src/libs/utils/include/utils/LiteralString.hpp create mode 100644 src/libs/utils/test/LiteralString.cpp diff --git a/src/libs/utils/include/utils/LiteralString.hpp b/src/libs/utils/include/utils/LiteralString.hpp new file mode 100644 index 00000000..9bcf16bd --- /dev/null +++ b/src/libs/utils/include/utils/LiteralString.hpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024 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 . + */ + +#pragma once + +#include +#include +#include + +class LiteralString +{ +public: + constexpr LiteralString() noexcept = default; + template + constexpr LiteralString(const char(&str)[N]) noexcept : _str{ str, N - 1 } { static_assert(N > 0); } + + constexpr const char* c_str() const noexcept { return _str.data(); } + constexpr std::size_t length() const noexcept { return _str.length(); } + constexpr std::string_view str() const noexcept { return _str; } + constexpr auto operator<=>(const LiteralString& other) const = default; + +private: + std::string_view _str; +}; + +namespace std +{ + template<> + struct hash + { + size_t operator()(const LiteralString& str) const + { + return hash{}(str.str()); + } + }; +} + +struct LiteralStringHash +{ + using hash_type = std::hash; + using is_transparent = void; + + [[nodiscard]] size_t operator()(const LiteralString& str) const { + return hash_type{}(str.str()); + } + + [[nodiscard]] size_t operator()(std::string_view str) const { + return hash_type{}(str); + } + + [[nodiscard]] size_t operator()(const std::string& str) const { + return hash_type{}(str); + } +}; + +struct LiteralStringEqual +{ + using is_transparent = void; + + [[nodiscard]] bool operator()(const LiteralString& lhs, const LiteralString& rhs) const { + return lhs == rhs; + } + + [[nodiscard]] bool operator()(const LiteralString& lhs, const std::string& rhs) const { + return lhs.str() == rhs; + } + + [[nodiscard]] bool operator()(const LiteralString& lhs, std::string_view rhs) const { + return lhs.str() == rhs; + } + + [[nodiscard]] bool operator()(const std::string_view& lhs, LiteralString rhs) const { + return lhs == rhs.str(); + } + + [[nodiscard]] bool operator()(const std::string& lhs, const LiteralString& rhs) const { + return lhs == rhs.str(); + } +}; diff --git a/src/libs/utils/test/CMakeLists.txt b/src/libs/utils/test/CMakeLists.txt index fbfb94e7..f6de0dbd 100644 --- a/src/libs/utils/test/CMakeLists.txt +++ b/src/libs/utils/test/CMakeLists.txt @@ -2,6 +2,7 @@ include(GoogleTest) add_executable(test-utils EnumSet.cpp + LiteralString.cpp Path.cpp RecursiveSharedMutex.cpp String.cpp diff --git a/src/libs/utils/test/LiteralString.cpp b/src/libs/utils/test/LiteralString.cpp new file mode 100644 index 00000000..b191f7af --- /dev/null +++ b/src/libs/utils/test/LiteralString.cpp @@ -0,0 +1,69 @@ +/* + * 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 . + */ + +#include +#include + +#include "utils/LiteralString.hpp" + +TEST(LiteralString, ctr) +{ + { + constexpr LiteralString foo{ "abc" }; + static_assert(foo.length() == 3); + static_assert(foo == "abc"); + static_assert(foo == LiteralString{ "abc" }); + static_assert(foo < LiteralString{ "abcd" }); + static_assert(foo > LiteralString{ "aac" }); + + EXPECT_EQ(::strlen(foo.c_str()), 3); + EXPECT_EQ(foo, LiteralString{ "abc" }); + EXPECT_GT(foo, LiteralString{ "abb" }); + EXPECT_LT(foo, LiteralString{ "abcd" }); + } + + { + constexpr LiteralString foo{ "" }; + static_assert(foo.length() == 0); + static_assert(foo == ""); + static_assert(foo == LiteralString{ "" }); + static_assert(foo < LiteralString{"a"}); + EXPECT_EQ(::strlen(foo.c_str()), 0); + } +} + +TEST(LiteralString, unordered_map) +{ + { + std::unordered_map myMap{ {"abc", 42} }; + + EXPECT_TRUE(myMap.contains("abc")); + EXPECT_TRUE(myMap.contains(LiteralString{ "abc" })); + EXPECT_FALSE(myMap.contains("abcd")); + } + + { + std::unordered_map myMap{ {"abc", 42} }; + EXPECT_TRUE(myMap.contains(std::string{ "abc" })); + EXPECT_TRUE(myMap.contains(std::string_view{ "abc" })); + EXPECT_FALSE(myMap.contains(std::string{ "abcd" })); + EXPECT_FALSE(myMap.contains(std::string_view{ "abcd" })); + } + +} From f36f176a8b73ba370bb22e3df29959903dcfb9bf Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 18:45:00 +0100 Subject: [PATCH 03/41] Added a simple profiler that uses trace event format --- src/libs/utils/CMakeLists.txt | 5 + src/libs/utils/bench/CMakeLists.txt | 9 ++ src/libs/utils/bench/ProfilerBench.cpp | 53 +++++++ src/libs/utils/impl/Profiler.cpp | 166 +++++++++++++++++++++ src/libs/utils/impl/Profiler.hpp | 68 +++++++++ src/libs/utils/include/utils/IProfiler.hpp | 113 ++++++++++++++ src/libs/utils/test/CMakeLists.txt | 1 + src/libs/utils/test/Profiler.cpp | 52 +++++++ 8 files changed, 467 insertions(+) create mode 100644 src/libs/utils/bench/CMakeLists.txt create mode 100644 src/libs/utils/bench/ProfilerBench.cpp create mode 100644 src/libs/utils/impl/Profiler.cpp create mode 100644 src/libs/utils/impl/Profiler.hpp create mode 100644 src/libs/utils/include/utils/IProfiler.hpp create mode 100644 src/libs/utils/test/Profiler.cpp diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index a1f3590d..69cacb0a 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(lmsutils SHARED impl/Logger.cpp impl/NetAddress.cpp impl/Path.cpp + impl/Profiler.cpp impl/Random.cpp impl/RecursiveSharedMutex.cpp impl/StreamLogger.cpp @@ -42,3 +43,7 @@ install(TARGETS lmsutils DESTINATION lib) if(BUILD_TESTING) add_subdirectory(test) endif() + +if (BUILD_BENCHMARKS) + add_subdirectory(bench) +endif() diff --git a/src/libs/utils/bench/CMakeLists.txt b/src/libs/utils/bench/CMakeLists.txt new file mode 100644 index 00000000..8f1361c1 --- /dev/null +++ b/src/libs/utils/bench/CMakeLists.txt @@ -0,0 +1,9 @@ + +add_executable(bench-utils + ProfilerBench.cpp + ) + +target_link_libraries(bench-utils PRIVATE + lmsutils + benchmark + ) diff --git a/src/libs/utils/bench/ProfilerBench.cpp b/src/libs/utils/bench/ProfilerBench.cpp new file mode 100644 index 00000000..d8bde857 --- /dev/null +++ b/src/libs/utils/bench/ProfilerBench.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2024 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 . + */ + +#include +#include +#include + +#include "utils/ILogger.hpp" +#include "utils/IProfiler.hpp" +#include "utils/StreamLogger.hpp" + + +// Profiler is meant to built/destroyed once +Service logger{ std::make_unique(std::cout, StreamLogger::allSeverities) }; +Service profiler{ profiling::createProfiler(::profiling::Level::Overview) }; + +static void BM_Profiler_Overview(benchmark::State& state) +{ + for (auto _ : state) + { + LMS_SCOPED_PROFILE_OVERVIEW("Cat", "Test"); + } +} + +static void BM_Profiler_Detailed(benchmark::State& state) +{ + for (auto _ : state) + { + // Should do nothing + LMS_SCOPED_PROFILE_DETAILED("Cat", "Test"); + } +} + +BENCHMARK(BM_Profiler_Overview)->Threads(1)->Threads(std::thread::hardware_concurrency()); +BENCHMARK(BM_Profiler_Detailed)->Threads(1)->Threads(std::thread::hardware_concurrency()); + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/src/libs/utils/impl/Profiler.cpp b/src/libs/utils/impl/Profiler.cpp new file mode 100644 index 00000000..b432108f --- /dev/null +++ b/src/libs/utils/impl/Profiler.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2024 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 . + */ + +#include "Profiler.hpp" + +#include +#include +#include +#include "utils/Exception.hpp" +#include "utils/ILogger.hpp" + +namespace profiling +{ + namespace + { + class CurrentThreadUnregisterer + { + public: + CurrentThreadUnregisterer(Profiler* profiler) : _profiler{ profiler } {} + ~CurrentThreadUnregisterer() + { + if (_profiler) + _profiler->onThreadPreDestroy(); + } + + private: + CurrentThreadUnregisterer(const CurrentThreadUnregisterer&) = delete; + CurrentThreadUnregisterer& operator=(const CurrentThreadUnregisterer&) = delete; + + Profiler* _profiler; + }; + } + + thread_local Profiler::Buffer* Profiler::_currentBuffer{}; + + std::unique_ptr createProfiler(Level minLevel, std::size_t bufferSizeInMbytes) + { + return std::make_unique(minLevel, bufferSizeInMbytes); + } + + Profiler::Profiler(Level minLevel, std::size_t bufferSizeinMBytes) + : _minLevel{ minLevel } + , _start{ clock::now() } + , _creatorThreadId{ std::this_thread::get_id() } + , _buffers((bufferSizeinMBytes * 1024 * 1024) / BufferSize) + { + if (bufferSizeinMBytes < MinBufferSizeInMBytes) + throw LmsException{ "Profiler must be configured with at least " + std::to_string(MinBufferSizeInMBytes) + " MBytes" }; + + for (Buffer& buffer : _buffers) + _freeBuffers.push_back(&buffer); + + LMS_LOG(UTILS, INFO, "Profiler: using " << _buffers.size() << " buffers. Buffer size = " << std::to_string(BufferSize)); + } + + bool Profiler::isLevelActive(Level level) const + { + return static_cast>(level) <= static_cast>(_minLevel); + } + + void Profiler::write(const CompleteEvent& event) + { + if (!_currentBuffer) + _currentBuffer = acquireBuffer(); + + _currentBuffer->durationEvents[_currentBuffer->currentDurationIndex] = event; + + // update the index after writing the event, in case another thread wants to dump + if (++_currentBuffer->currentDurationIndex == _currentBuffer->durationEvents.size()) + { + releaseBuffer(_currentBuffer); + _currentBuffer = nullptr; + } + } + + void Profiler::onThreadPreDestroy() + { + if (_currentBuffer) + releaseBuffer(_currentBuffer); + } + + Profiler::Buffer* Profiler::acquireBuffer() + { + // We consider the creator thread will survive the profiler (thus we don't want to release anything on thread destruction) + static thread_local CurrentThreadUnregisterer currentThreadUnregister{ _creatorThreadId == std::this_thread::get_id() ? nullptr : this }; + + std::scoped_lock lock{ _mutex }; + assert(!_freeBuffers.empty()); + + Profiler::Buffer* buffer{ _freeBuffers.front() }; + _freeBuffers.pop_front(); + + // Empty new buffer only now (we want to keep history on released buffers since we dump them) + buffer->currentDurationIndex = 0; + return buffer; + } + + void Profiler::releaseBuffer(Buffer* buffer) + { + assert(buffer); + + std::scoped_lock lock{ _mutex }; + _freeBuffers.push_back(buffer); + } + + void Profiler::dumpCurrentBuffer(std::ostream& os) + { + os << "{" << std::endl; + os << "\t\"traceEvents\": [" << std::endl; + + // we allow threads to fill in their current block while dumping + { + os << "\t\t{ "; + os << "\"name\" : \"thread_name\", "; + os << "\"pid\" : 1, "; + os << "\"tid\" : " << _creatorThreadId << ", "; + os << "\"ph\" : \"M\", "; + os << "\"args\" : { \"name\" : \"MainThread\" }"; + os << " }"; + + std::scoped_lock lock{ _mutex }; + + for (Buffer& buffer : _buffers) + { + // Looks like tracing viewer is not pleased when nested event start at the same timestamp + for (std::size_t i{}; i < buffer.currentDurationIndex; ++i) + { + using clockMicro = std::chrono::duration; + const CompleteEvent& event{ buffer.durationEvents[i] }; + + os << "," << std::endl; + os << "\t\t{ "; + os << "\"name\" : \"" << event.name.c_str() << "\", "; + os << "\"cat\" : \"" << event.category.c_str() << "\", "; + os << "\"pid\": 1, "; + os << "\"tid\" : " << event.threadId << ", "; + os << "\"ts\" : " << std::fixed << std::setprecision(3) << std::chrono::duration_cast(event.start - _start).count() << ", "; + os << "\"dur\" : " << std::fixed << std::setprecision(3) << std::chrono::duration_cast(event.duration).count() << ", "; + os << "\"ph\" : \"X\""; + os << " }"; + } + } + } + + os << std::endl; + os << "\t]," << std::endl; + os << "\t\"meta_cpu_count\" : " << std::thread::hardware_concurrency() << std::endl; + os << "}" << std::endl; + } +} \ No newline at end of file diff --git a/src/libs/utils/impl/Profiler.hpp b/src/libs/utils/impl/Profiler.hpp new file mode 100644 index 00000000..e9668068 --- /dev/null +++ b/src/libs/utils/impl/Profiler.hpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2024 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 . + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include "utils/IProfiler.hpp" + +namespace profiling +{ + class Profiler : public IProfiler + { + public: + Profiler(Level minLevel, std::size_t bufferSizeinMBytes); + + void onThreadPreDestroy(); + + private: + bool isLevelActive(Level level) const override; + void write(const CompleteEvent& event) override; + void dumpCurrentBuffer(std::ostream& os) override; + + static constexpr std::size_t BufferSize{ 32 * 1024 }; + + struct alignas(64) Buffer + { + static constexpr std::size_t CompleteEventCount{ BufferSize / sizeof(CompleteEvent) }; + + std::array durationEvents; + std::atomic currentDurationIndex{}; + }; + + Buffer* acquireBuffer(); + void releaseBuffer(Buffer* buffer); + + const Level _minLevel; + const clock::time_point _start; + const std::thread::id _creatorThreadId; + + std::vector _buffers; // allocated once during construction + + std::mutex _mutex; + std::deque _freeBuffers; + + static thread_local Buffer* _currentBuffer; + }; +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/IProfiler.hpp b/src/libs/utils/include/utils/IProfiler.hpp new file mode 100644 index 00000000..3ce806a1 --- /dev/null +++ b/src/libs/utils/include/utils/IProfiler.hpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2024 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 . + */ + +#pragma once + +#include +#include +#include +#include + +#include "LiteralString.hpp" +#include "Service.hpp" + +#define LMS_ENABLE_PROFILING 1 + +#define LMS_CONCAT_IMPL(x, y) x##y +#define LMS_CONCAT(x, y) LMS_CONCAT_IMPL(x, y) + +#if LMS_ENABLE_PROFILING +#define LMS_SCOPED_PROFILE(CATEGORY, LEVEL, NAME) ::profiling::ScopedEvent LMS_CONCAT(scopedEvent_, __LINE__){ CATEGORY, LEVEL, NAME } + +#else +#define LMS_SCOPED_PROFILE(CATEGORY, LEVEL, NAME) (void)0 +#endif + +#define LMS_SCOPED_PROFILE_OVERVIEW(CATEGORY, NAME) LMS_SCOPED_PROFILE(CATEGORY, ::profiling::Level::Overview, NAME) +#define LMS_SCOPED_PROFILE_DETAILED(CATEGORY, NAME) LMS_SCOPED_PROFILE(CATEGORY, ::profiling::Level::Detailed, NAME) + +namespace profiling +{ + using clock = std::chrono::steady_clock; + + enum class Level + { + Overview, + Detailed, + }; + + class IProfiler + { + public: + struct CompleteEvent + { + clock::time_point start; + clock::duration duration; + std::thread::id threadId; + LiteralString name; + LiteralString category; + }; + + virtual ~IProfiler() = default; + + virtual bool isLevelActive(Level level) const = 0; + virtual void write(const CompleteEvent& entry) = 0; + virtual void dumpCurrentBuffer(std::ostream& os) = 0; + }; + + static constexpr std::size_t MinBufferSizeInMBytes = 16; + std::unique_ptr createProfiler(Level minLevel = Level::Overview, std::size_t bufferSizeInMbytes = MinBufferSizeInMBytes); + + class ScopedEvent + { + public: + ScopedEvent(LiteralString category, Level level, LiteralString name, IProfiler* profiler = Service::get()) + { + if (profiler && profiler->isLevelActive(level)) + { + _profiler = profiler; + + _event.start = clock::now(); + _event.threadId = std::this_thread::get_id(); + _event.name = name; + _event.category = category; + } + else + { + _profiler = nullptr; + } + } + + ~ScopedEvent() + { + if (_profiler) + { + _event.duration = clock::now() - _event.start; + _profiler->write(_event); + } + } + + private: + ScopedEvent(const ScopedEvent&) = delete; + ScopedEvent& operator=(const ScopedEvent&) = delete; + + IProfiler* _profiler; + IProfiler::CompleteEvent _event; + }; +} \ No newline at end of file diff --git a/src/libs/utils/test/CMakeLists.txt b/src/libs/utils/test/CMakeLists.txt index f6de0dbd..b8762ad8 100644 --- a/src/libs/utils/test/CMakeLists.txt +++ b/src/libs/utils/test/CMakeLists.txt @@ -4,6 +4,7 @@ add_executable(test-utils EnumSet.cpp LiteralString.cpp Path.cpp + Profiler.cpp RecursiveSharedMutex.cpp String.cpp Utils.cpp diff --git a/src/libs/utils/test/Profiler.cpp b/src/libs/utils/test/Profiler.cpp new file mode 100644 index 00000000..45e02eed --- /dev/null +++ b/src/libs/utils/test/Profiler.cpp @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024 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 . + */ + +#include +#include +#include + +#include "utils/IProfiler.hpp" + +namespace profiling::tests +{ + // not much can be tested with this implementation + TEST(Profiler, MultipleThreads) + { + auto profiler{ createProfiler(Level::Overview) }; + + std::vector threads; + for (std::size_t i{}; i < 16; ++i) + { + threads.emplace_back([&] + { + ScopedEvent loggedEvent{ "MyCategory", Level::Overview, "MyEventLogged", profiler.get() }; + ScopedEvent notLoggedEvent{ "MyCategory", Level::Detailed, "MyEventNotLogged", profiler.get() }; + }); + } + + for (std::thread& t : threads) + t.join(); + + std::ostringstream oss; + profiler->dumpCurrentBuffer(oss); + + EXPECT_NE(oss.str().find("MyEventLogged"), std::string::npos); + EXPECT_EQ(oss.str().find("MyEventNotLogged"), std::string::npos); + } +} \ No newline at end of file From aca6c00414be20132cadc3129e5aa6842658e1f8 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 18:55:54 +0100 Subject: [PATCH 04/41] Instanciated the profiler + added a way to dump the export the profilin data in the UI --- approot/admin-profilercontroller.xml | 15 +++++ approot/main.xml | 1 + approot/messages.xml | 5 ++ approot/messages_fr.xml | 5 ++ approot/messages_it.xml | 5 ++ approot/messages_zh.xml | 6 ++ conf/lms.conf | 7 +++ src/lms/CMakeLists.txt | 1 + src/lms/main.cpp | 19 +++++++ src/lms/ui/LmsApplication.cpp | 18 +++++- src/lms/ui/admin/ProfilerController.cpp | 75 +++++++++++++++++++++++++ src/lms/ui/admin/ProfilerController.hpp | 31 ++++++++++ 12 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 approot/admin-profilercontroller.xml create mode 100644 src/lms/ui/admin/ProfilerController.cpp create mode 100644 src/lms/ui/admin/ProfilerController.hpp diff --git a/approot/admin-profilercontroller.xml b/approot/admin-profilercontroller.xml new file mode 100644 index 00000000..0467e410 --- /dev/null +++ b/approot/admin-profilercontroller.xml @@ -0,0 +1,15 @@ + + + + +
+ ${tr:Lms.Admin.ProfilerController.profiler} +
+
+ ${export-btn class="btn btn-primary"} +
+
+
+
+ +
diff --git a/approot/main.xml b/approot/main.xml index 3facacf1..8d63c533 100644 --- a/approot/main.xml +++ b/approot/main.xml @@ -49,6 +49,7 @@
  • ${scan-settings class="dropdown-item"}
  • ${scanner class="dropdown-item"}
  • ${users class="dropdown-item"}
  • +
  • ${profiler class="dropdown-item"}
  • ${} diff --git a/approot/messages.xml b/approot/messages.xml index 77d99d7f..51441308 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -45,6 +45,7 @@ Libraries + Profiler Scan settings Scanner Users @@ -63,6 +64,10 @@ Path must be an existing directory Root directory + +Export profiling data +Profiler + Delimiter to be used for splitting artist tags Daily diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index 3629fc1a..1ec396db 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -45,6 +45,7 @@ Bibliothèques + Profileur Paramètres du scan Scanner Utilisateurs @@ -63,6 +64,10 @@ Le chemin doit référencer un répertoire existant Répertoire racine + +Exporter les données de profilage +Profileur + Délimiteur à utiliser pour séparer les tags d'artistes Tous les jours diff --git a/approot/messages_it.xml b/approot/messages_it.xml index b16fba7e..a8f164b2 100644 --- a/approot/messages_it.xml +++ b/approot/messages_it.xml @@ -45,6 +45,7 @@ Librerie + Profilatore Impostazioni di scansione Scanner Utenti @@ -63,6 +64,10 @@ Il percorso deve essere una directory esistente Cartella principale + +Esporta dati di profilazione +Profilatore + Delimitatore da utilizzare per separare i tag degli artisti Giornaliera diff --git a/approot/messages_zh.xml b/approot/messages_zh.xml index 2a06a2a1..00e2d059 100644 --- a/approot/messages_zh.xml +++ b/approot/messages_zh.xml @@ -49,6 +49,7 @@ + @@ -63,6 +64,10 @@ + + + + 每日 @@ -83,6 +88,7 @@ 更新开始时间 每周 + 无法获得音轨时间 无法解析文件 diff --git a/conf/lms.conf b/conf/lms.conf index e65b63bd..5823be3a 100644 --- a/conf/lms.conf +++ b/conf/lms.conf @@ -93,6 +93,13 @@ artist-image-file-names = ("artist"); # Playqueue max entry count playqueue-max-entry-count = 1000; +# Internal profiling, enable only if necessary as it brings some runtime overhead! +# Possible values are "disabled", "overview" or "detailed". +# If enabled, profiling information has to be dumped in the profiler view located in the admin menu +profiling-level = "disabled"; +# The profiling buffer size, in MBytes (min is 16) +profiling-buffer-size = 16; + # Set to true if you want to hide duplicate tracks scanner-skip-duplicate-mbid = false; diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index e0b813e4..2154e399 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(lms ui/admin/InitWizardView.cpp ui/admin/MediaLibrariesView.cpp ui/admin/MediaLibraryModal.cpp + ui/admin/ProfilerController.cpp ui/admin/ScannerController.cpp ui/admin/ScanSettingsView.cpp ui/admin/UserView.cpp diff --git a/src/lms/main.cpp b/src/lms/main.cpp index 468c2c00..c03c8a5a 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -43,6 +43,7 @@ #include "utils/IChildProcessManager.hpp" #include "utils/IConfig.hpp" #include "utils/IOContextRunner.hpp" +#include "utils/IProfiler.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" #include "utils/WtLogger.hpp" @@ -75,6 +76,20 @@ namespace throw LmsException{ "Invalid config value for 'log-min-severity'" }; } + std::optional getProfilingLevel() + { + std::string_view profilingLevel{ Service::get()->getString("profiling-level", "disabled") }; + + if (profilingLevel == "disabled") + return std::nullopt; + else if (profilingLevel == "overview") + return profiling::Level::Overview; + else if (profilingLevel == "detailed") + return profiling::Level::Detailed; + + throw LmsException{ "Invalid config value for 'profiling-level'" }; + } + std::vector generateWtConfig(std::string execPath, Severity minSeverity) { std::vector args; @@ -233,6 +248,9 @@ int main(int argc, char* argv[]) Service config{ createConfig(configFilePath) }; const Severity minLogSeverity{getLogMinSeverity()}; Service logger{ std::make_unique(minLogSeverity) }; + std::optional> profiler; + if (const auto level{ getProfilingLevel() }) + profiler.emplace(profiling::createProfiler(level.value(), config->getULong("profiling-buffer-size", profiling::MinBufferSizeInMBytes))); // use system locale. libarchive relies on this to write filenames if (char* locale{ ::setlocale(LC_ALL, "") }) @@ -243,6 +261,7 @@ int main(int argc, char* argv[]) // Make sure the working directory exists std::filesystem::create_directories(config->getPath("working-dir")); std::filesystem::create_directories(config->getPath("working-dir") / "cache"); + std::filesystem::create_directories(config->getPath("working-dir") / "profiling"); // Construct WT configuration and get the argc/argv back const std::vector wtServerArgs{ generateWtConfig(argv[0], minLogSeverity) }; diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index 9c5be7a4..735341d5 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -38,15 +38,17 @@ #include "database/User.hpp" #include "services/scrobbling/IScrobblingService.hpp" #include "utils/ILogger.hpp" +#include "utils/IProfiler.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" #include "admin/InitWizardView.hpp" #include "admin/MediaLibrariesView.hpp" +#include "admin/ProfilerController.hpp" +#include "admin/ScannerController.hpp" #include "admin/ScanSettingsView.hpp" #include "admin/UserView.hpp" #include "admin/UsersView.hpp" -#include "admin/ScannerController.hpp" #include "common/Template.hpp" #include "explore/Explore.hpp" #include "explore/Filters.hpp" @@ -77,6 +79,7 @@ namespace UserInterface res->use(appRoot + "admin-initwizard"); res->use(appRoot + "admin-medialibraries"); res->use(appRoot + "admin-medialibrary"); + res->use(appRoot + "admin-profilercontroller"); res->use(appRoot + "admin-scannercontroller"); res->use(appRoot + "admin-scansettings"); res->use(appRoot + "admin-user"); @@ -119,6 +122,7 @@ namespace UserInterface IdxAdminScanner, IdxAdminUsers, IdxAdminUser, + IdxAdminProfiler, }; void handlePathChange(Wt::WStackedWidget& stack, bool isAdmin) @@ -146,6 +150,7 @@ namespace UserInterface { "/admin/scanner", IdxAdminScanner, true, Wt::WString::tr("Lms.Admin.ScannerController.scanner") }, { "/admin/users", IdxAdminUsers, true, Wt::WString::tr("Lms.Admin.Users.users") }, { "/admin/user", IdxAdminUser, true, std::nullopt }, + { "/admin/profiler", IdxAdminProfiler, true, Wt::WString::tr("Lms.Admin.ProfilerController.profiler") }, }; LMS_LOG(UI, DEBUG, "Internal path changed to '" << wApp->internalPath() << "'"); @@ -264,6 +269,8 @@ namespace UserInterface void LmsApplication::init() { + LMS_SCOPED_PROFILE_OVERVIEW("UI", "ApplicationInit"); + setTheme(std::make_shared()); useStyleSheet("resources/font-awesome/css/font-awesome.min.css"); @@ -381,6 +388,8 @@ namespace UserInterface void LmsApplication::createHome() { + LMS_SCOPED_PROFILE_OVERVIEW("UI", "ApplicationCreateHome"); + _coverResource = std::make_shared(); declareJavaScriptFunction("onLoadCover", "function(id) { id.className += \" Lms-cover-loaded\"}"); @@ -436,6 +445,11 @@ namespace UserInterface navbar->bindNew("scan-settings", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/scan-settings" }, Wt::WString::tr("Lms.Admin.menu-scan-settings")); navbar->bindNew("scanner", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/scanner" }, Wt::WString::tr("Lms.Admin.menu-scanner")); navbar->bindNew("users", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/users" }, Wt::WString::tr("Lms.Admin.menu-users")); + // Hide the entry if the profiler is not enabled + if (Service<::profiling::IProfiler>::get()) + navbar->bindNew("profiler", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/profiler" }, Wt::WString::tr("Lms.Admin.menu-profiler")); + else + navbar->bindEmpty("profiler"); } // Contents @@ -467,6 +481,7 @@ namespace UserInterface mainStack->addNew(); mainStack->addNew(); mainStack->addNew(); + mainStack->addNew(); } explore->getPlayQueueController().setMaxTrackCountToEnqueue(_playQueue->getCapacity()); @@ -544,6 +559,7 @@ namespace UserInterface { try { + LMS_SCOPED_PROFILE_OVERVIEW("UI", "ProcessEvent"); WApplication::notify(event); } catch (LmsApplicationException& e) diff --git a/src/lms/ui/admin/ProfilerController.cpp b/src/lms/ui/admin/ProfilerController.cpp new file mode 100644 index 00000000..4de5d1a4 --- /dev/null +++ b/src/lms/ui/admin/ProfilerController.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2024 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 . + */ + +#include "ProfilerController.hpp" + +#include +#include +#include +#include + +#include "utils/IProfiler.hpp" +#include "utils/String.hpp" + +namespace UserInterface +{ + namespace + { + class ReportResource : public Wt::WResource + { + public: + ReportResource(::profiling::IProfiler& profiler) + : _profiler{ profiler } + { + } + + ~ReportResource() + { + beingDeleted(); + } + + void handleRequest(const Wt::Http::Request&, Wt::Http::Response& response) + { + suggestFileName(StringUtils::toISO8601String(Wt::WDateTime::currentDateTime()) + "-profiling.json"); + response.setMimeType("application/json"); + _profiler.dumpCurrentBuffer(response.out()); + } + + private: + profiling::IProfiler& _profiler; + }; + } + + ProfilerController::ProfilerController() + : Wt::WTemplate{ Wt::WString::tr("Lms.Admin.ProfilerController.template") } + { + addFunction("tr", &Wt::WTemplate::Functions::tr); + + Wt::WPushButton* dumpBtn{ bindNew("export-btn", Wt::WString::tr("Lms.Admin.ProfilerController.export-current-buffer")) }; + + if (auto profiler{ Service::get() }) + { + Wt::WLink link{ std::make_shared(*profiler) }; + link.setTarget(Wt::LinkTarget::NewWindow); + dumpBtn->setLink(link); + } + else + dumpBtn->setEnabled(false); + } +} // namespace UserInterface diff --git a/src/lms/ui/admin/ProfilerController.hpp b/src/lms/ui/admin/ProfilerController.hpp new file mode 100644 index 00000000..22a92b8c --- /dev/null +++ b/src/lms/ui/admin/ProfilerController.hpp @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 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 . + */ + +#pragma once + +#include + +namespace UserInterface +{ + class ProfilerController : public Wt::WTemplate + { + public: + ProfilerController(); + }; +} // namespace UserInterface From 3155be07328c5a66d6d4623e561472c90e24492b Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 18:57:45 +0100 Subject: [PATCH 05/41] Added some profiling events in DB --- src/libs/database/impl/Session.cpp | 3 +++ src/libs/database/impl/Utils.hpp | 6 ++++++ src/libs/database/include/database/Session.hpp | 6 +++++- 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 56db76f0..0eba00f5 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -23,6 +23,7 @@ #include "utils/Exception.hpp" #include "utils/ILogger.hpp" +#include "utils/IProfiler.hpp" #include "database/Artist.hpp" #include "database/AuthToken.hpp" @@ -189,6 +190,7 @@ namespace Database void Session::analyze() { + LMS_SCOPED_PROFILE_DETAILED("Database", "Analyze"); LMS_LOG(DB, INFO, "Analyzing database..."); { auto transaction{ createWriteTransaction() }; @@ -199,6 +201,7 @@ namespace Database void Session::optimize() { + LMS_SCOPED_PROFILE_DETAILED("Database", "Optimize"); LMS_LOG(DB, INFO, "Optimizing database..."); { auto transaction{ createWriteTransaction() }; diff --git a/src/libs/database/impl/Utils.hpp b/src/libs/database/impl/Utils.hpp index 68e82cda..25041b91 100644 --- a/src/libs/database/impl/Utils.hpp +++ b/src/libs/database/impl/Utils.hpp @@ -27,6 +27,7 @@ #include #include "database/Types.hpp" +#include "utils/IProfiler.hpp" namespace Database::Utils { @@ -47,6 +48,8 @@ namespace Database::Utils template RangeResults execQuery(Query& query, std::optional range) { + LMS_SCOPED_PROFILE_DETAILED("Database", "ExecQueryRange"); + RangeResults res; if (range) @@ -76,7 +79,10 @@ namespace Database::Utils applyRange(query, range); for (const auto& res : query.resultList()) + { + LMS_SCOPED_PROFILE_DETAILED("Database", "ExecQueryResult"); func(res); + } } Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime); diff --git a/src/libs/database/include/database/Session.hpp b/src/libs/database/include/database/Session.hpp index 9b96ea0a..601e32d1 100644 --- a/src/libs/database/include/database/Session.hpp +++ b/src/libs/database/include/database/Session.hpp @@ -22,6 +22,7 @@ #include #include +#include "utils/IProfiler.hpp" #include "utils/RecursiveSharedMutex.hpp" #include "database/Object.hpp" #include "database/TransactionChecker.hpp" @@ -32,6 +33,7 @@ namespace Database { public: ~WriteTransaction(); + private: friend class Session; WriteTransaction(RecursiveSharedMutex& mutex, Wt::Dbo::Session& session); @@ -40,6 +42,7 @@ namespace Database WriteTransaction& operator=(const WriteTransaction&) = delete; std::unique_lock _lock; + profiling::ScopedEvent _profilingEvent{ "Database", profiling::Level::Detailed, "WriteTransaction" }; // before actual transaction Wt::Dbo::Transaction _transaction; }; @@ -47,14 +50,15 @@ namespace Database { public: ~ReadTransaction(); + private: friend class Session; ReadTransaction(Wt::Dbo::Session& session); - ReadTransaction(const ReadTransaction&) = delete; ReadTransaction& operator=(const ReadTransaction&) = delete; + profiling::ScopedEvent _profilingEvent{ "Database", profiling::Level::Detailed, "ReadTransaction" }; // before actual transaction Wt::Dbo::Transaction _transaction; }; From db1d95e3db09f1b51ea21af55cb73cb55cb63e4d Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 18:58:24 +0100 Subject: [PATCH 06/41] Added some profiling events for cover and audio resources --- src/lms/ui/resource/AudioFileResource.cpp | 3 +++ src/lms/ui/resource/CoverResource.cpp | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/lms/ui/resource/AudioFileResource.cpp b/src/lms/ui/resource/AudioFileResource.cpp index 5003811a..58b84adc 100644 --- a/src/lms/ui/resource/AudioFileResource.cpp +++ b/src/lms/ui/resource/AudioFileResource.cpp @@ -27,6 +27,7 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "utils/ILogger.hpp" +#include "utils/IProfiler.hpp" #include "utils/String.hpp" #include "LmsApplication.hpp" @@ -83,6 +84,8 @@ namespace UserInterface void AudioFileResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) { + LMS_SCOPED_PROFILE_OVERVIEW("UI", "HandleAudioFileRequest"); + std::shared_ptr fileResourceHandler; if (!request.continuation()) diff --git a/src/lms/ui/resource/CoverResource.cpp b/src/lms/ui/resource/CoverResource.cpp index 6738c7d7..21cfaf80 100644 --- a/src/lms/ui/resource/CoverResource.cpp +++ b/src/lms/ui/resource/CoverResource.cpp @@ -26,6 +26,7 @@ #include "database/Track.hpp" #include "utils/Exception.hpp" #include "utils/ILogger.hpp" +#include "utils/IProfiler.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" @@ -61,6 +62,8 @@ namespace UserInterface void CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) { + LMS_SCOPED_PROFILE_OVERVIEW("UI", "HandleCoverRequest"); + // Retrieve parameters const std::string* trackIdStr = request.getParameter("trackid"); const std::string* releaseIdStr = request.getParameter("releaseid"); From bdd678f168c3d204ba6e6feaaa0e8c7fd4c0af3e Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 18:58:52 +0100 Subject: [PATCH 07/41] Added some profiling events for scanner --- src/libs/metadata/impl/TagLibTagReader.cpp | 14 ++++- src/libs/metadata/impl/TagLibTagReader.hpp | 2 +- .../scanner/impl/ScanStepScanFiles.cpp | 63 ++++++++++++------- .../scanner/impl/ScanStepScanFiles.hpp | 3 +- 4 files changed, 55 insertions(+), 27 deletions(-) diff --git a/src/libs/metadata/impl/TagLibTagReader.cpp b/src/libs/metadata/impl/TagLibTagReader.cpp index 97ebb9d2..6096e575 100644 --- a/src/libs/metadata/impl/TagLibTagReader.cpp +++ b/src/libs/metadata/impl/TagLibTagReader.cpp @@ -35,6 +35,7 @@ #include "metadata/Exception.hpp" #include "utils/ILogger.hpp" +#include "utils/IProfiler.hpp" #include "utils/String.hpp" namespace MetaData @@ -169,12 +170,19 @@ namespace MetaData dst[tag] = std::move(values); } } + + TagLib::FileRef parseFile(const std::filesystem::path& p, ParserReadStyle parserReadStyle) + { + LMS_SCOPED_PROFILE_DETAILED("MetaData", "TagLibParseFile"); + + return TagLib::FileRef{ p.string().c_str() + , true // read audio properties + , readStyleToTagLibReadStyle(parserReadStyle) }; + } } TagLibTagReader::TagLibTagReader(const std::filesystem::path& p, ParserReadStyle parserReadStyle, bool debug) - : _file{ p.string().c_str() - , true // read audio properties - , readStyleToTagLibReadStyle(parserReadStyle) } + : _file{ parseFile(p, parserReadStyle) } { if (_file.isNull()) { diff --git a/src/libs/metadata/impl/TagLibTagReader.hpp b/src/libs/metadata/impl/TagLibTagReader.hpp index cc3dd304..a6db24e1 100644 --- a/src/libs/metadata/impl/TagLibTagReader.hpp +++ b/src/libs/metadata/impl/TagLibTagReader.hpp @@ -48,7 +48,7 @@ namespace MetaData std::size_t getBitsPerSample() const override; std::size_t getSampleRate() const override; - TagLib::FileRef _file; + const TagLib::FileRef _file; TagLib::PropertyMap _propertyMap; // case-insensitive keys bool _hasEmbeddedCover{}; }; diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp index 622ed495..2e2c7eb3 100644 --- a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp @@ -34,6 +34,7 @@ #include "utils/IConfig.hpp" #include "utils/ILogger.hpp" #include "utils/Path.hpp" +#include "utils/IProfiler.hpp" namespace Scanner { @@ -188,7 +189,7 @@ namespace Scanner { std::vector clusters; - auto getOrCreateClusters {[&](std::string tag, std::span values) + auto getOrCreateClusters{ [&](std::string tag, std::span values) { auto clusterType = ClusterType::find(session, tag); if (!clusterType) @@ -202,7 +203,7 @@ namespace Scanner clusters.push_back(cluster); } - }}; + } }; // TODO: migrate these fields in dedicated tables in DB getOrCreateClusters("GENRE", track.genres); @@ -236,14 +237,15 @@ namespace Scanner if (threadCount == 0) threadCount = std::max(std::thread::hardware_concurrency() / 2, 1); - + return threadCount; } } // namespace - ScanStepScanFiles::MetadataScanQueue::MetadataScanQueue(MetaData::IParser& parser, std::size_t threadCount) + ScanStepScanFiles::MetadataScanQueue::MetadataScanQueue(MetaData::IParser& parser, std::size_t threadCount, bool& abort) : _metadataParser{ parser } , _scanContextRunner{ _scanContext, threadCount } + , _abort{ abort } {} void ScanStepScanFiles::MetadataScanQueue::pushScanRequest(const std::filesystem::path& path) @@ -255,23 +257,34 @@ namespace Scanner _scanContext.post([=, this] { + LMS_SCOPED_PROFILE_OVERVIEW("Scanner", "AudioFileParseJob"); + std::unique_ptr track; - try - { - track = _metadataParser.parse(path); - } - catch(const MetaData::Exception& e) - { - LMS_LOG(DBUPDATER, INFO, "Failed to parse '" << path.string() << "'"); - } - + if (_abort) { std::scoped_lock lock{ _mutex }; - - _scanResults.emplace_back(MetaDataScanResult{ std::move(path), std::move(track) }); _ongoingScanCount -= 1; } + else + { + try + { + track = _metadataParser.parse(path); + } + catch (const MetaData::Exception& e) + { + LMS_LOG(DBUPDATER, INFO, "Failed to parse '" << path.string() << "'"); + } + + { + std::scoped_lock lock{ _mutex }; + + _scanResults.emplace_back(MetaDataScanResult{ std::move(path), std::move(track) }); + _ongoingScanCount -= 1; + } + } + _condVar.notify_all(); }); } @@ -286,10 +299,10 @@ namespace Scanner { results.clear(); results.reserve(maxCount); - + { std::scoped_lock lock{ _mutex }; - + while (results.size() < maxCount && !_scanResults.empty()) { results.push_back(std::move(_scanResults.front())); @@ -302,22 +315,24 @@ namespace Scanner void ScanStepScanFiles::MetadataScanQueue::wait(std::size_t maxScanRequestCount) { + LMS_SCOPED_PROFILE_OVERVIEW("Scanner", "WaitParseResults"); + std::unique_lock lock{ _mutex }; - _condVar.wait(lock, [=, this] {return _ongoingScanCount <= maxScanRequestCount;}); + _condVar.wait(lock, [=, this] { return _ongoingScanCount <= maxScanRequestCount; }); } ScanStepScanFiles::ScanStepScanFiles(InitParams& initParams) : ScanStepBase{ initParams } , _metadataParser{ MetaData::createParser(MetaData::ParserBackend::TagLib, getParserReadStyle()) } // For now, always use TagLib - , _metadataScanQueue{ *_metadataParser, getScanMetaDataThreadCount() } + , _metadataScanQueue{ *_metadataParser, getScanMetaDataThreadCount(), _abortScan } { LMS_LOG(DBUPDATER, INFO, "Using " << _metadataScanQueue.getThreadCount() << " thread(s) for scanning file metadata"); } void ScanStepScanFiles::process(ScanContext& context) { - const std::size_t scanQueueMaxScanRequestCount{ 20 * _metadataScanQueue.getThreadCount() }; - const std::size_t processMetaDataBatchSize{ 10 }; + const std::size_t scanQueueMaxScanRequestCount{ 100 * _metadataScanQueue.getThreadCount() }; + const std::size_t processMetaDataBatchSize{ 5 }; { std::vector tagsToParse{ _extraTagsToParse }; @@ -334,6 +349,8 @@ namespace Scanner { PathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) { + LMS_SCOPED_PROFILE_DETAILED("Scanner", "OnExploreFile"); + if (_abortScan) return false; @@ -367,7 +384,7 @@ namespace Scanner while (_metadataScanQueue.popResults(scanResults, processMetaDataBatchSize) > 0) processMetaDataScanResults(context, scanResults, mediaLibrary); } - } + } bool ScanStepScanFiles::checkFileNeedScan(ScanContext& context, const std::filesystem::path& file, const ScannerSettings::MediaLibraryInfo& libraryInfo) { @@ -427,6 +444,8 @@ namespace Scanner void ScanStepScanFiles::processMetaDataScanResults(ScanContext& context, std::span scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo) { + LMS_SCOPED_PROFILE_OVERVIEW("Scanner", "ProcessScanResults"); + Database::Session& dbSession{ _db.getTLSSession() }; auto transaction{ dbSession.createWriteTransaction() }; diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.hpp b/src/libs/services/scanner/impl/ScanStepScanFiles.hpp index 0669c418..23fbe661 100644 --- a/src/libs/services/scanner/impl/ScanStepScanFiles.hpp +++ b/src/libs/services/scanner/impl/ScanStepScanFiles.hpp @@ -58,7 +58,7 @@ namespace Scanner class MetadataScanQueue { public: - MetadataScanQueue(MetaData::IParser& parser, std::size_t threadCount); + MetadataScanQueue(MetaData::IParser& parser, std::size_t threadCount, bool& abort); std::size_t getThreadCount() const { return _scanContextRunner.getThreadCount(); } @@ -78,6 +78,7 @@ namespace Scanner std::size_t _ongoingScanCount{}; std::deque _scanResults; std::condition_variable _condVar; + bool& _abort; }; MetadataScanQueue _metadataScanQueue; From f072f171d8b7df26e28306952b9fffdd5e37900c Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 19:02:02 +0100 Subject: [PATCH 08/41] Added some profiling events for the subsonic API --- src/libs/subsonic/impl/SubsonicResource.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index 92797b30..d99ed32b 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -28,8 +28,10 @@ #include "database/Session.hpp" #include "database/User.hpp" #include "utils/EnumSet.hpp" +#include "utils/LiteralString.hpp" #include "utils/IConfig.hpp" #include "utils/ILogger.hpp" +#include "utils/IProfiler.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" #include "utils/Utils.hpp" @@ -159,7 +161,7 @@ namespace API::Subsonic CheckImplementedFunc checkFunc{}; }; - static const std::unordered_map requestEntryPoints + static const std::unordered_map requestEntryPoints { // System {"/ping", {handlePingRequest}}, @@ -310,17 +312,26 @@ namespace API::Subsonic auto itEntryPoint{ requestEntryPoints.find(requestPath) }; if (itEntryPoint != requestEntryPoints.end()) { + LMS_SCOPED_PROFILE_OVERVIEW("Subsonic", itEntryPoint->first); + if (itEntryPoint->second.checkFunc) itEntryPoint->second.checkFunc(); checkUserTypeIsAllowed(requestContext, itEntryPoint->second.allowedUserTypes); - const Response resp{ (itEntryPoint->second.func)(requestContext) }; + const Response resp{ [&] { + LMS_SCOPED_PROFILE_DETAILED("Subsonic", "HandleRequest"); + return itEntryPoint->second.func(requestContext); + }()}; + + { + LMS_SCOPED_PROFILE_DETAILED("Subsonic", "WriteResponse"); + + resp.write(response.out(), format); + response.setMimeType(std::string{ ResponseFormatToMimeType(format) }); + } - resp.write(response.out(), format); - response.setMimeType(std::string{ ResponseFormatToMimeType(format) }); LMS_LOG(API_SUBSONIC, DEBUG, "Request " << requestId << " '" << requestPath << "' handled!"); - return; } From d6fd95ab443232415b54e95bb7d71f2adb1ec440 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 19:14:13 +0100 Subject: [PATCH 09/41] Added some profiling events for subsonic API (media retrieval) --- src/libs/subsonic/impl/SubsonicResource.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index d99ed32b..b9333208 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -268,7 +268,7 @@ namespace API::Subsonic }; using MediaRetrievalHandlerFunc = std::function; - static std::unordered_map mediaRetrievalHandlers + static std::unordered_map mediaRetrievalHandlers { // Media retrieval {"/download", handleDownload}, @@ -277,7 +277,6 @@ namespace API::Subsonic }; } - SubsonicResource::SubsonicResource(Db& db) : _serverProtocolVersionsByClient{ readConfigProtocolVersions() } , _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() } @@ -338,6 +337,8 @@ namespace API::Subsonic auto itStreamHandler{ mediaRetrievalHandlers.find(requestPath) }; if (itStreamHandler != mediaRetrievalHandlers.end()) { + LMS_SCOPED_PROFILE_OVERVIEW("Subsonic", itStreamHandler->first); + itStreamHandler->second(requestContext, request, response); LMS_LOG(API_SUBSONIC, DEBUG, "Request " << requestId << " '" << requestPath << "' handled!"); return; From 15b4a4601ea30c70ed19be315a68f5bd6964a801 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 19:22:06 +0100 Subject: [PATCH 10/41] Removed unnecessary write access to the DB --- src/libs/services/auth/impl/AuthServiceBase.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/libs/services/auth/impl/AuthServiceBase.cpp b/src/libs/services/auth/impl/AuthServiceBase.cpp index eed258c5..704a80ff 100644 --- a/src/libs/services/auth/impl/AuthServiceBase.cpp +++ b/src/libs/services/auth/impl/AuthServiceBase.cpp @@ -36,11 +36,18 @@ namespace Auth UserId AuthServiceBase::getOrCreateUser(std::string_view loginName) { Session& session{ getDbSession() }; - auto transaction{ session.createWriteTransaction() }; - User::pointer user{ User::find(session, loginName) }; + User::pointer user; + + { + auto transaction{ session.createReadTransaction() }; + user = User::find(session, loginName); + } + if (!user) { + auto transaction{ session.createWriteTransaction() }; + const UserType type{ User::getCount(session) == 0 ? UserType::ADMIN : UserType::REGULAR }; LMS_LOG(AUTH, DEBUG, "Creating user '" << loginName << "', admin = " << (type == UserType::ADMIN)); From 88580a89030ba3c85bb49f38f2d6dbff176d8ec1 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 20:23:58 +0100 Subject: [PATCH 11/41] Fixed race --- src/libs/services/auth/impl/AuthServiceBase.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libs/services/auth/impl/AuthServiceBase.cpp b/src/libs/services/auth/impl/AuthServiceBase.cpp index 704a80ff..7119d4ac 100644 --- a/src/libs/services/auth/impl/AuthServiceBase.cpp +++ b/src/libs/services/auth/impl/AuthServiceBase.cpp @@ -38,7 +38,7 @@ namespace Auth Session& session{ getDbSession() }; User::pointer user; - + { auto transaction{ session.createReadTransaction() }; user = User::find(session, loginName); @@ -48,12 +48,16 @@ namespace Auth { auto transaction{ session.createWriteTransaction() }; - const UserType type{ User::getCount(session) == 0 ? UserType::ADMIN : UserType::REGULAR }; + user = User::find(session, loginName); + if (!user) + { + const UserType type{ User::getCount(session) == 0 ? UserType::ADMIN : UserType::REGULAR }; - LMS_LOG(AUTH, DEBUG, "Creating user '" << loginName << "', admin = " << (type == UserType::ADMIN)); + LMS_LOG(AUTH, DEBUG, "Creating user '" << loginName << "', admin = " << (type == UserType::ADMIN)); - user = session.create(loginName); - user.modify()->setType(type); + user = session.create(loginName); + user.modify()->setType(type); + } } return user->getId(); From 65d95060b0c097768a1c010d8951153295042bb6 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 22:22:15 +0100 Subject: [PATCH 12/41] Added a way to set thread name in profiling captures --- src/libs/utils/impl/Profiler.cpp | 44 +++++++++++++++++----- src/libs/utils/impl/Profiler.hpp | 5 +++ src/libs/utils/include/utils/IProfiler.hpp | 2 + 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/libs/utils/impl/Profiler.cpp b/src/libs/utils/impl/Profiler.cpp index b432108f..2849290e 100644 --- a/src/libs/utils/impl/Profiler.cpp +++ b/src/libs/utils/impl/Profiler.cpp @@ -63,6 +63,8 @@ namespace profiling if (bufferSizeinMBytes < MinBufferSizeInMBytes) throw LmsException{ "Profiler must be configured with at least " + std::to_string(MinBufferSizeInMBytes) + " MBytes" }; + setThreadName(_creatorThreadId, "MainThread"); + for (Buffer& buffer : _buffers) _freeBuffers.push_back(&buffer); @@ -124,16 +126,30 @@ namespace profiling os << "{" << std::endl; os << "\t\"traceEvents\": [" << std::endl; + bool first{ true }; + + { + std::scoped_lock lock{ _threadNameMutex }; + + for (const auto& [threadId, threadName] : _threadNames) + { + if (first) + first = false; + else + os << ", " << std::endl; + + os << "\t\t{ "; + os << "\"name\" : \"thread_name\", "; + os << "\"pid\" : 1, "; + os << "\"tid\" : " << threadId << ", "; + os << "\"ph\" : \"M\", "; + os << "\"args\" : { \"name\" : \"" + threadName + "\" }"; + os << " }"; + } + } + // we allow threads to fill in their current block while dumping { - os << "\t\t{ "; - os << "\"name\" : \"thread_name\", "; - os << "\"pid\" : 1, "; - os << "\"tid\" : " << _creatorThreadId << ", "; - os << "\"ph\" : \"M\", "; - os << "\"args\" : { \"name\" : \"MainThread\" }"; - os << " }"; - std::scoped_lock lock{ _mutex }; for (Buffer& buffer : _buffers) @@ -144,7 +160,11 @@ namespace profiling using clockMicro = std::chrono::duration; const CompleteEvent& event{ buffer.durationEvents[i] }; - os << "," << std::endl; + if (first) + first = false; + else + os << ", " << std::endl;; + os << "\t\t{ "; os << "\"name\" : \"" << event.name.c_str() << "\", "; os << "\"cat\" : \"" << event.category.c_str() << "\", "; @@ -163,4 +183,10 @@ namespace profiling os << "\t\"meta_cpu_count\" : " << std::thread::hardware_concurrency() << std::endl; os << "}" << std::endl; } + + void Profiler::setThreadName(std::thread::id id, std::string_view threadName) + { + std::scoped_lock lock{ _threadNameMutex }; + _threadNames.emplace(id, threadName); + } } \ No newline at end of file diff --git a/src/libs/utils/impl/Profiler.hpp b/src/libs/utils/impl/Profiler.hpp index e9668068..b8b4fdc2 100644 --- a/src/libs/utils/impl/Profiler.hpp +++ b/src/libs/utils/impl/Profiler.hpp @@ -24,6 +24,7 @@ #include #include #include +#include #include "utils/IProfiler.hpp" @@ -40,6 +41,7 @@ namespace profiling bool isLevelActive(Level level) const override; void write(const CompleteEvent& event) override; void dumpCurrentBuffer(std::ostream& os) override; + void setThreadName(std::thread::id id, std::string_view threadName) override; static constexpr std::size_t BufferSize{ 32 * 1024 }; @@ -60,6 +62,9 @@ namespace profiling std::vector _buffers; // allocated once during construction + std::mutex _threadNameMutex; + std::unordered_map _threadNames; + std::mutex _mutex; std::deque _freeBuffers; diff --git a/src/libs/utils/include/utils/IProfiler.hpp b/src/libs/utils/include/utils/IProfiler.hpp index 3ce806a1..35cd3d12 100644 --- a/src/libs/utils/include/utils/IProfiler.hpp +++ b/src/libs/utils/include/utils/IProfiler.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "LiteralString.hpp" @@ -69,6 +70,7 @@ namespace profiling virtual bool isLevelActive(Level level) const = 0; virtual void write(const CompleteEvent& entry) = 0; virtual void dumpCurrentBuffer(std::ostream& os) = 0; + virtual void setThreadName(std::thread::id id, std::string_view threadName) = 0; }; static constexpr std::size_t MinBufferSizeInMBytes = 16; From 9c8844d866c99f74b2fd8f987a0ae27f80267eb6 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 22:23:02 +0100 Subject: [PATCH 13/41] Added some thread names --- .../scanner/impl/ScanStepScanFiles.cpp | 2 +- src/libs/utils/impl/IOContextRunner.cpp | 22 +++++++++++++++---- .../utils/include/utils/IOContextRunner.hpp | 2 +- src/lms/main.cpp | 2 +- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp index 2e2c7eb3..e52caa4f 100644 --- a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp @@ -244,7 +244,7 @@ namespace Scanner ScanStepScanFiles::MetadataScanQueue::MetadataScanQueue(MetaData::IParser& parser, std::size_t threadCount, bool& abort) : _metadataParser{ parser } - , _scanContextRunner{ _scanContext, threadCount } + , _scanContextRunner{ _scanContext, threadCount, "ScannerMetadata" } , _abort{ abort } {} diff --git a/src/libs/utils/impl/IOContextRunner.cpp b/src/libs/utils/impl/IOContextRunner.cpp index 77a88b9d..3814eb1a 100644 --- a/src/libs/utils/impl/IOContextRunner.cpp +++ b/src/libs/utils/impl/IOContextRunner.cpp @@ -22,16 +22,31 @@ #include #include "utils/ILogger.hpp" +#include "utils/IProfiler.hpp" -IOContextRunner::IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount) +IOContextRunner::IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name) : _ioService{ ioService } , _work{ ioService } { LMS_LOG(UTILS, INFO, "Starting IO context with " << threadCount << " threads..."); + for (std::size_t i{}; i < threadCount; ++i) { - _threads.emplace_back([&] + std::string threadName{ name }; + if (!threadName.empty()) + { + threadName += "Thread_"; + threadName += std::to_string(i); + } + + _threads.emplace_back([this, threadName] { + if (!threadName.empty()) + { + if (auto * profiler{ Service::get() }) + profiler->setThreadName(std::this_thread::get_id(), threadName); + } + try { _ioService.run(); @@ -45,8 +60,7 @@ IOContextRunner::IOContextRunner(boost::asio::io_service& ioService, std::size_t } } -void -IOContextRunner::stop() +void IOContextRunner::stop() { LMS_LOG(UTILS, DEBUG, "Stopping IO context..."); _work.reset(); diff --git a/src/libs/utils/include/utils/IOContextRunner.hpp b/src/libs/utils/include/utils/IOContextRunner.hpp index e538826f..1a1857a2 100644 --- a/src/libs/utils/include/utils/IOContextRunner.hpp +++ b/src/libs/utils/include/utils/IOContextRunner.hpp @@ -26,7 +26,7 @@ class IOContextRunner { public: - IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount); + IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name); ~IOContextRunner(); void stop(); diff --git a/src/lms/main.cpp b/src/lms/main.cpp index c03c8a5a..ef20d249 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -277,7 +277,7 @@ int main(int argc, char* argv[]) Wt::WServer server{ argv[0] }; server.setServerConfiguration(wtServerArgs.size(), const_cast(&wtArgv[0])); - IOContextRunner ioContextRunner{ ioContext, getThreadCount() }; + IOContextRunner ioContextRunner{ ioContext, getThreadCount(), "Misc" }; // Connection pool size must be twice the number of threads: we have at least 2 io pools with getThreadCount() each and they all may access the database Database::Db database{ config->getPath("working-dir") / "lms.db", getThreadCount() * 2 }; From fa9844810dc37a928770c9a37754b31acffd65ff Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 22:23:41 +0100 Subject: [PATCH 14/41] Added some detailed profiling info for the http send queue --- src/libs/utils/impl/http/SendQueue.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/utils/impl/http/SendQueue.cpp b/src/libs/utils/impl/http/SendQueue.cpp index c4e77df0..b5880d2e 100644 --- a/src/libs/utils/impl/http/SendQueue.cpp +++ b/src/libs/utils/impl/http/SendQueue.cpp @@ -24,6 +24,7 @@ #include "utils/Exception.hpp" #include "utils/ILogger.hpp" +#include "utils/IProfiler.hpp" #include "utils/String.hpp" #define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[Http SendQueue] - " << message) @@ -117,6 +118,8 @@ namespace Http bool SendQueue::sendRequest(const ClientRequest& request) { + LMS_SCOPED_PROFILE_DETAILED("SendQueue", "SendRequest"); + std::string url {_baseUrl + request.getParameters().relativeUrl}; LOG(DEBUG, "Sending request to url '" << url << "'"); @@ -141,6 +144,8 @@ namespace Http void SendQueue::onClientDone(Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) { + LMS_SCOPED_PROFILE_DETAILED("SendQueue", "OnClientDone"); + if (ec == boost::asio::error::operation_aborted) { LOG(DEBUG, "Client aborted"); From 47ef5f7cc775047a976dfb49162e9220805dfe36 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 9 Mar 2024 22:39:07 +0100 Subject: [PATCH 15/41] Added some detailed profiling info into subsonic responses handlers --- src/libs/subsonic/impl/responses/Album.cpp | 3 +++ src/libs/subsonic/impl/responses/Artist.cpp | 3 +++ src/libs/subsonic/impl/responses/Song.cpp | 3 +++ 3 files changed, 9 insertions(+) diff --git a/src/libs/subsonic/impl/responses/Album.cpp b/src/libs/subsonic/impl/responses/Album.cpp index b263acc4..1a1f1b10 100644 --- a/src/libs/subsonic/impl/responses/Album.cpp +++ b/src/libs/subsonic/impl/responses/Album.cpp @@ -25,6 +25,7 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" +#include "utils/IProfiler.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" @@ -40,6 +41,8 @@ namespace API::Subsonic Response::Node createAlbumNode(RequestContext& context, const Release::pointer& release, const User::pointer& user, bool id3) { + LMS_SCOPED_PROFILE_DETAILED("Subsonic", "CreateAlbum"); + Response::Node albumNode; if (id3) { diff --git a/src/libs/subsonic/impl/responses/Artist.cpp b/src/libs/subsonic/impl/responses/Artist.cpp index 0dddd170..bee9631c 100644 --- a/src/libs/subsonic/impl/responses/Artist.cpp +++ b/src/libs/subsonic/impl/responses/Artist.cpp @@ -24,6 +24,7 @@ #include "database/TrackArtistLink.hpp" #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" +#include "utils/IProfiler.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" @@ -75,6 +76,8 @@ namespace API::Subsonic Response::Node createArtistNode(RequestContext& context, const Artist::pointer& artist, const User::pointer& user, bool id3) { + LMS_SCOPED_PROFILE_DETAILED("Subsonic", "CreateArtist"); + Response::Node artistNode{ createArtistNode(artist) }; artistNode.setAttribute("id", idToString(artist->getId())); diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index 51a959bc..e06d4820 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -30,6 +30,7 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" +#include "utils/IProfiler.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" #include "responses/Artist.hpp" @@ -96,6 +97,8 @@ namespace API::Subsonic Response::Node createSongNode(RequestContext& context, const Track::pointer& track, const User::pointer& user) { + LMS_SCOPED_PROFILE_DETAILED("Subsonic", "CreateSong"); + Response::Node trackResponse; trackResponse.setAttribute("id", idToString(track->getId())); From c6ae3fe3fa3ddd5df92b3ba58717614d0387ba38 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 10 Mar 2024 12:21:01 +0100 Subject: [PATCH 16/41] Renamed profiling to tracing --- ...ofilercontroller.xml => admin-tracing.xml} | 4 +- approot/main.xml | 2 +- approot/messages.xml | 10 ++-- approot/messages_fr.xml | 10 ++-- approot/messages_it.xml | 10 ++-- approot/messages_zh.xml | 8 ++-- conf/lms.conf | 10 ++-- src/libs/database/impl/Session.cpp | 6 +-- src/libs/database/impl/Utils.hpp | 6 +-- .../database/include/database/Session.hpp | 6 +-- src/libs/metadata/impl/TagLibTagReader.cpp | 4 +- .../scanner/impl/ScanStepScanFiles.cpp | 10 ++-- src/libs/subsonic/impl/SubsonicResource.cpp | 10 ++-- src/libs/subsonic/impl/responses/Album.cpp | 4 +- src/libs/subsonic/impl/responses/Artist.cpp | 4 +- src/libs/subsonic/impl/responses/Song.cpp | 4 +- src/libs/utils/CMakeLists.txt | 2 +- src/libs/utils/bench/CMakeLists.txt | 2 +- ...ProfilerBench.cpp => TraceLoggerBench.cpp} | 18 ++++---- src/libs/utils/impl/IOContextRunner.cpp | 6 +-- .../impl/{Profiler.cpp => TraceLogger.cpp} | 46 ++++++++++--------- .../impl/{Profiler.hpp => TraceLogger.hpp} | 8 ++-- src/libs/utils/impl/http/SendQueue.cpp | 6 +-- .../utils/{IProfiler.hpp => ITraceLogger.hpp} | 44 +++++++++--------- src/libs/utils/test/CMakeLists.txt | 2 +- .../test/{Profiler.cpp => TraceLogger.cpp} | 14 +++--- src/lms/CMakeLists.txt | 2 +- src/lms/main.cpp | 25 +++++----- src/lms/ui/LmsApplication.cpp | 26 +++++------ ...ProfilerController.cpp => TracingView.cpp} | 24 +++++----- ...ProfilerController.hpp => TracingView.hpp} | 4 +- src/lms/ui/resource/AudioFileResource.cpp | 4 +- src/lms/ui/resource/CoverResource.cpp | 4 +- 33 files changed, 173 insertions(+), 172 deletions(-) rename approot/{admin-profilercontroller.xml => admin-tracing.xml} (67%) rename src/libs/utils/bench/{ProfilerBench.cpp => TraceLoggerBench.cpp} (64%) rename src/libs/utils/impl/{Profiler.cpp => TraceLogger.cpp} (76%) rename src/libs/utils/impl/{Profiler.hpp => TraceLogger.hpp} (92%) rename src/libs/utils/include/utils/{IProfiler.hpp => ITraceLogger.hpp} (61%) rename src/libs/utils/test/{Profiler.cpp => TraceLogger.cpp} (74%) rename src/lms/ui/admin/{ProfilerController.cpp => TracingView.cpp} (71%) rename src/lms/ui/admin/{ProfilerController.hpp => TracingView.hpp} (90%) diff --git a/approot/admin-profilercontroller.xml b/approot/admin-tracing.xml similarity index 67% rename from approot/admin-profilercontroller.xml rename to approot/admin-tracing.xml index 0467e410..eeb1ad43 100644 --- a/approot/admin-profilercontroller.xml +++ b/approot/admin-tracing.xml @@ -1,9 +1,9 @@ - +
    - ${tr:Lms.Admin.ProfilerController.profiler} + ${tr:Lms.Admin.Tracing.tracing}
    ${export-btn class="btn btn-primary"} diff --git a/approot/main.xml b/approot/main.xml index 8d63c533..aad114b9 100644 --- a/approot/main.xml +++ b/approot/main.xml @@ -49,7 +49,7 @@
  • ${scan-settings class="dropdown-item"}
  • ${scanner class="dropdown-item"}
  • ${users class="dropdown-item"}
  • -
  • ${profiler class="dropdown-item"}
  • +
  • ${tracing class="dropdown-item"}
  • ${} diff --git a/approot/messages.xml b/approot/messages.xml index 51441308..e78a91e9 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -45,9 +45,9 @@ Libraries - Profiler Scan settings Scanner + Tracing Users @@ -64,10 +64,6 @@ Path must be an existing directory Root directory - -Export profiling data -Profiler - Delimiter to be used for splitting artist tags Daily @@ -118,6 +114,10 @@ Scanning files: {1}/{2} files ({3}%)... Step status + +Export traces +Tracing + New user Admin diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index 1ec396db..b0627408 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -45,9 +45,9 @@ Bibliothèques - Profileur Paramètres du scan Scanner + Traces Utilisateurs @@ -64,10 +64,6 @@ Le chemin doit référencer un répertoire existant Répertoire racine - -Exporter les données de profilage -Profileur - Délimiteur à utiliser pour séparer les tags d'artistes Tous les jours @@ -118,6 +114,10 @@ Scan des fichiers : {1}/{2} fichiers ({3}%)... Statut de l'étape + +Exporter les traces +Traces + Ajouter Admin diff --git a/approot/messages_it.xml b/approot/messages_it.xml index a8f164b2..aa675ec5 100644 --- a/approot/messages_it.xml +++ b/approot/messages_it.xml @@ -45,9 +45,9 @@ Librerie - Profilatore Impostazioni di scansione Scanner + Tracing Utenti @@ -64,10 +64,6 @@ Il percorso deve essere una directory esistente Cartella principale - -Esporta dati di profilazione -Profilatore - Delimitatore da utilizzare per separare i tag degli artisti Giornaliera @@ -118,6 +114,10 @@ Scansione files: {1}/{2} files ({3}%)... Stato passo + +Esporta tracce +Tracing + Nuovo utente Amministratore diff --git a/approot/messages_zh.xml b/approot/messages_zh.xml index 00e2d059..3a89f689 100644 --- a/approot/messages_zh.xml +++ b/approot/messages_zh.xml @@ -64,10 +64,6 @@ - - - - 每日 @@ -118,6 +114,10 @@ 扫描文件中: {1}/{2} 个文件 ({3}%)... 当前步骤状态 + + + + 新建用户 管理员 diff --git a/conf/lms.conf b/conf/lms.conf index 5823be3a..58d89bbb 100644 --- a/conf/lms.conf +++ b/conf/lms.conf @@ -93,12 +93,12 @@ artist-image-file-names = ("artist"); # Playqueue max entry count playqueue-max-entry-count = 1000; -# Internal profiling, enable only if necessary as it brings some runtime overhead! +# Internal tracing for profiling purposes. Enable only if necessary, as it incurs some runtime overhead! # Possible values are "disabled", "overview" or "detailed". -# If enabled, profiling information has to be dumped in the profiler view located in the admin menu -profiling-level = "disabled"; -# The profiling buffer size, in MBytes (min is 16) -profiling-buffer-size = 16; +# If enabled, data has to be dumped in the tracing view located in the admin menu +tracing-level = "disabled"; +# The tracing buffer size, in MBytes (min is 16) +tracing-buffer-size = 16; # Set to true if you want to hide duplicate tracks scanner-skip-duplicate-mbid = false; diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 0eba00f5..9d977a27 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -23,7 +23,7 @@ #include "utils/Exception.hpp" #include "utils/ILogger.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "database/Artist.hpp" #include "database/AuthToken.hpp" @@ -190,7 +190,7 @@ namespace Database void Session::analyze() { - LMS_SCOPED_PROFILE_DETAILED("Database", "Analyze"); + LMS_SCOPED_TRACE_DETAILED("Database", "Analyze"); LMS_LOG(DB, INFO, "Analyzing database..."); { auto transaction{ createWriteTransaction() }; @@ -201,7 +201,7 @@ namespace Database void Session::optimize() { - LMS_SCOPED_PROFILE_DETAILED("Database", "Optimize"); + LMS_SCOPED_TRACE_DETAILED("Database", "Optimize"); LMS_LOG(DB, INFO, "Optimizing database..."); { auto transaction{ createWriteTransaction() }; diff --git a/src/libs/database/impl/Utils.hpp b/src/libs/database/impl/Utils.hpp index 25041b91..29be679c 100644 --- a/src/libs/database/impl/Utils.hpp +++ b/src/libs/database/impl/Utils.hpp @@ -27,7 +27,7 @@ #include #include "database/Types.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" namespace Database::Utils { @@ -48,7 +48,7 @@ namespace Database::Utils template RangeResults execQuery(Query& query, std::optional range) { - LMS_SCOPED_PROFILE_DETAILED("Database", "ExecQueryRange"); + LMS_SCOPED_TRACE_DETAILED("Database", "ExecQueryRange"); RangeResults res; @@ -80,7 +80,7 @@ namespace Database::Utils for (const auto& res : query.resultList()) { - LMS_SCOPED_PROFILE_DETAILED("Database", "ExecQueryResult"); + LMS_SCOPED_TRACE_DETAILED("Database", "ExecQueryResult"); func(res); } } diff --git a/src/libs/database/include/database/Session.hpp b/src/libs/database/include/database/Session.hpp index 601e32d1..07edf49a 100644 --- a/src/libs/database/include/database/Session.hpp +++ b/src/libs/database/include/database/Session.hpp @@ -22,7 +22,7 @@ #include #include -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/RecursiveSharedMutex.hpp" #include "database/Object.hpp" #include "database/TransactionChecker.hpp" @@ -42,7 +42,7 @@ namespace Database WriteTransaction& operator=(const WriteTransaction&) = delete; std::unique_lock _lock; - profiling::ScopedEvent _profilingEvent{ "Database", profiling::Level::Detailed, "WriteTransaction" }; // before actual transaction + tracing::ScopedTrace _trace{ "Database", tracing::Level::Detailed, "WriteTransaction" }; // before actual transaction Wt::Dbo::Transaction _transaction; }; @@ -58,7 +58,7 @@ namespace Database ReadTransaction(const ReadTransaction&) = delete; ReadTransaction& operator=(const ReadTransaction&) = delete; - profiling::ScopedEvent _profilingEvent{ "Database", profiling::Level::Detailed, "ReadTransaction" }; // before actual transaction + tracing::ScopedTrace _trace{ "Database", tracing::Level::Detailed, "ReadTransaction" }; // before actual transaction Wt::Dbo::Transaction _transaction; }; diff --git a/src/libs/metadata/impl/TagLibTagReader.cpp b/src/libs/metadata/impl/TagLibTagReader.cpp index 6096e575..f03e3321 100644 --- a/src/libs/metadata/impl/TagLibTagReader.cpp +++ b/src/libs/metadata/impl/TagLibTagReader.cpp @@ -35,7 +35,7 @@ #include "metadata/Exception.hpp" #include "utils/ILogger.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/String.hpp" namespace MetaData @@ -173,7 +173,7 @@ namespace MetaData TagLib::FileRef parseFile(const std::filesystem::path& p, ParserReadStyle parserReadStyle) { - LMS_SCOPED_PROFILE_DETAILED("MetaData", "TagLibParseFile"); + LMS_SCOPED_TRACE_DETAILED("MetaData", "TagLibParseFile"); return TagLib::FileRef{ p.string().c_str() , true // read audio properties diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp index e52caa4f..54cc7b06 100644 --- a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp @@ -34,7 +34,7 @@ #include "utils/IConfig.hpp" #include "utils/ILogger.hpp" #include "utils/Path.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" namespace Scanner { @@ -257,7 +257,7 @@ namespace Scanner _scanContext.post([=, this] { - LMS_SCOPED_PROFILE_OVERVIEW("Scanner", "AudioFileParseJob"); + LMS_SCOPED_TRACE_OVERVIEW("Scanner", "AudioFileParseJob"); std::unique_ptr track; @@ -315,7 +315,7 @@ namespace Scanner void ScanStepScanFiles::MetadataScanQueue::wait(std::size_t maxScanRequestCount) { - LMS_SCOPED_PROFILE_OVERVIEW("Scanner", "WaitParseResults"); + LMS_SCOPED_TRACE_OVERVIEW("Scanner", "WaitParseResults"); std::unique_lock lock{ _mutex }; _condVar.wait(lock, [=, this] { return _ongoingScanCount <= maxScanRequestCount; }); @@ -349,7 +349,7 @@ namespace Scanner { PathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) { - LMS_SCOPED_PROFILE_DETAILED("Scanner", "OnExploreFile"); + LMS_SCOPED_TRACE_DETAILED("Scanner", "OnExploreFile"); if (_abortScan) return false; @@ -444,7 +444,7 @@ namespace Scanner void ScanStepScanFiles::processMetaDataScanResults(ScanContext& context, std::span scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo) { - LMS_SCOPED_PROFILE_OVERVIEW("Scanner", "ProcessScanResults"); + LMS_SCOPED_TRACE_OVERVIEW("Scanner", "ProcessScanResults"); Database::Session& dbSession{ _db.getTLSSession() }; auto transaction{ dbSession.createWriteTransaction() }; diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index b9333208..0f2e23b2 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -31,7 +31,7 @@ #include "utils/LiteralString.hpp" #include "utils/IConfig.hpp" #include "utils/ILogger.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" #include "utils/Utils.hpp" @@ -311,7 +311,7 @@ namespace API::Subsonic auto itEntryPoint{ requestEntryPoints.find(requestPath) }; if (itEntryPoint != requestEntryPoints.end()) { - LMS_SCOPED_PROFILE_OVERVIEW("Subsonic", itEntryPoint->first); + LMS_SCOPED_TRACE_OVERVIEW("Subsonic", itEntryPoint->first); if (itEntryPoint->second.checkFunc) itEntryPoint->second.checkFunc(); @@ -319,12 +319,12 @@ namespace API::Subsonic checkUserTypeIsAllowed(requestContext, itEntryPoint->second.allowedUserTypes); const Response resp{ [&] { - LMS_SCOPED_PROFILE_DETAILED("Subsonic", "HandleRequest"); + LMS_SCOPED_TRACE_DETAILED("Subsonic", "HandleRequest"); return itEntryPoint->second.func(requestContext); }()}; { - LMS_SCOPED_PROFILE_DETAILED("Subsonic", "WriteResponse"); + LMS_SCOPED_TRACE_DETAILED("Subsonic", "WriteResponse"); resp.write(response.out(), format); response.setMimeType(std::string{ ResponseFormatToMimeType(format) }); @@ -337,7 +337,7 @@ namespace API::Subsonic auto itStreamHandler{ mediaRetrievalHandlers.find(requestPath) }; if (itStreamHandler != mediaRetrievalHandlers.end()) { - LMS_SCOPED_PROFILE_OVERVIEW("Subsonic", itStreamHandler->first); + LMS_SCOPED_TRACE_OVERVIEW("Subsonic", itStreamHandler->first); itStreamHandler->second(requestContext, request, response); LMS_LOG(API_SUBSONIC, DEBUG, "Request " << requestId << " '" << requestPath << "' handled!"); diff --git a/src/libs/subsonic/impl/responses/Album.cpp b/src/libs/subsonic/impl/responses/Album.cpp index 1a1f1b10..fc97fb47 100644 --- a/src/libs/subsonic/impl/responses/Album.cpp +++ b/src/libs/subsonic/impl/responses/Album.cpp @@ -25,7 +25,7 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" @@ -41,7 +41,7 @@ namespace API::Subsonic Response::Node createAlbumNode(RequestContext& context, const Release::pointer& release, const User::pointer& user, bool id3) { - LMS_SCOPED_PROFILE_DETAILED("Subsonic", "CreateAlbum"); + LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateAlbum"); Response::Node albumNode; diff --git a/src/libs/subsonic/impl/responses/Artist.cpp b/src/libs/subsonic/impl/responses/Artist.cpp index bee9631c..68cbfea3 100644 --- a/src/libs/subsonic/impl/responses/Artist.cpp +++ b/src/libs/subsonic/impl/responses/Artist.cpp @@ -24,7 +24,7 @@ #include "database/TrackArtistLink.hpp" #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" @@ -76,7 +76,7 @@ namespace API::Subsonic Response::Node createArtistNode(RequestContext& context, const Artist::pointer& artist, const User::pointer& user, bool id3) { - LMS_SCOPED_PROFILE_DETAILED("Subsonic", "CreateArtist"); + LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateArtist"); Response::Node artistNode{ createArtistNode(artist) }; diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index e06d4820..807d9130 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -30,7 +30,7 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" #include "responses/Artist.hpp" @@ -97,7 +97,7 @@ namespace API::Subsonic Response::Node createSongNode(RequestContext& context, const Track::pointer& track, const User::pointer& user) { - LMS_SCOPED_PROFILE_DETAILED("Subsonic", "CreateSong"); + LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateSong"); Response::Node trackResponse; diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 69cacb0a..7d00aaa1 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -10,11 +10,11 @@ add_library(lmsutils SHARED impl/Logger.cpp impl/NetAddress.cpp impl/Path.cpp - impl/Profiler.cpp impl/Random.cpp impl/RecursiveSharedMutex.cpp impl/StreamLogger.cpp impl/String.cpp + impl/TraceLogger.cpp impl/UUID.cpp impl/WtLogger.cpp ) diff --git a/src/libs/utils/bench/CMakeLists.txt b/src/libs/utils/bench/CMakeLists.txt index 8f1361c1..f8326be4 100644 --- a/src/libs/utils/bench/CMakeLists.txt +++ b/src/libs/utils/bench/CMakeLists.txt @@ -1,6 +1,6 @@ add_executable(bench-utils - ProfilerBench.cpp + TraceLoggerBench.cpp ) target_link_libraries(bench-utils PRIVATE diff --git a/src/libs/utils/bench/ProfilerBench.cpp b/src/libs/utils/bench/TraceLoggerBench.cpp similarity index 64% rename from src/libs/utils/bench/ProfilerBench.cpp rename to src/libs/utils/bench/TraceLoggerBench.cpp index d8bde857..68cf20f3 100644 --- a/src/libs/utils/bench/ProfilerBench.cpp +++ b/src/libs/utils/bench/TraceLoggerBench.cpp @@ -22,32 +22,32 @@ #include #include "utils/ILogger.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/StreamLogger.hpp" -// Profiler is meant to built/destroyed once +// The trace logger is meant to built/destroyed once Service logger{ std::make_unique(std::cout, StreamLogger::allSeverities) }; -Service profiler{ profiling::createProfiler(::profiling::Level::Overview) }; +Service traceLogger{ tracing::createTraceLogger(::tracing::Level::Overview) }; -static void BM_Profiler_Overview(benchmark::State& state) +static void BM_TraceLogger_Overview(benchmark::State& state) { for (auto _ : state) { - LMS_SCOPED_PROFILE_OVERVIEW("Cat", "Test"); + LMS_SCOPED_TRACE_OVERVIEW("Cat", "Test"); } } -static void BM_Profiler_Detailed(benchmark::State& state) +static void BM_TraceLogger_Detailed(benchmark::State& state) { for (auto _ : state) { // Should do nothing - LMS_SCOPED_PROFILE_DETAILED("Cat", "Test"); + LMS_SCOPED_TRACE_DETAILED("Cat", "Test"); } } -BENCHMARK(BM_Profiler_Overview)->Threads(1)->Threads(std::thread::hardware_concurrency()); -BENCHMARK(BM_Profiler_Detailed)->Threads(1)->Threads(std::thread::hardware_concurrency()); +BENCHMARK(BM_TraceLogger_Overview)->Threads(1)->Threads(std::thread::hardware_concurrency()); +BENCHMARK(BM_TraceLogger_Detailed)->Threads(1)->Threads(std::thread::hardware_concurrency()); BENCHMARK_MAIN(); \ No newline at end of file diff --git a/src/libs/utils/impl/IOContextRunner.cpp b/src/libs/utils/impl/IOContextRunner.cpp index 3814eb1a..15199bee 100644 --- a/src/libs/utils/impl/IOContextRunner.cpp +++ b/src/libs/utils/impl/IOContextRunner.cpp @@ -22,7 +22,7 @@ #include #include "utils/ILogger.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" IOContextRunner::IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name) : _ioService{ ioService } @@ -43,8 +43,8 @@ IOContextRunner::IOContextRunner(boost::asio::io_service& ioService, std::size_t { if (!threadName.empty()) { - if (auto * profiler{ Service::get() }) - profiler->setThreadName(std::this_thread::get_id(), threadName); + if (auto * traceLogger{ Service::get() }) + traceLogger->setThreadName(std::this_thread::get_id(), threadName); } try diff --git a/src/libs/utils/impl/Profiler.cpp b/src/libs/utils/impl/TraceLogger.cpp similarity index 76% rename from src/libs/utils/impl/Profiler.cpp rename to src/libs/utils/impl/TraceLogger.cpp index 2849290e..4cde78c5 100644 --- a/src/libs/utils/impl/Profiler.cpp +++ b/src/libs/utils/impl/TraceLogger.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "Profiler.hpp" +#include "TraceLogger.hpp" #include #include @@ -25,58 +25,58 @@ #include "utils/Exception.hpp" #include "utils/ILogger.hpp" -namespace profiling +namespace tracing { namespace { class CurrentThreadUnregisterer { public: - CurrentThreadUnregisterer(Profiler* profiler) : _profiler{ profiler } {} + CurrentThreadUnregisterer(TraceLogger* logger) : _logger{ logger } {} ~CurrentThreadUnregisterer() { - if (_profiler) - _profiler->onThreadPreDestroy(); + if (_logger) + _logger->onThreadPreDestroy(); } private: CurrentThreadUnregisterer(const CurrentThreadUnregisterer&) = delete; CurrentThreadUnregisterer& operator=(const CurrentThreadUnregisterer&) = delete; - Profiler* _profiler; + TraceLogger* _logger; }; } - thread_local Profiler::Buffer* Profiler::_currentBuffer{}; + thread_local TraceLogger::Buffer* TraceLogger::_currentBuffer{}; - std::unique_ptr createProfiler(Level minLevel, std::size_t bufferSizeInMbytes) + std::unique_ptr createTraceLogger(Level minLevel, std::size_t bufferSizeInMbytes) { - return std::make_unique(minLevel, bufferSizeInMbytes); + return std::make_unique(minLevel, bufferSizeInMbytes); } - Profiler::Profiler(Level minLevel, std::size_t bufferSizeinMBytes) + TraceLogger::TraceLogger(Level minLevel, std::size_t bufferSizeinMBytes) : _minLevel{ minLevel } , _start{ clock::now() } , _creatorThreadId{ std::this_thread::get_id() } , _buffers((bufferSizeinMBytes * 1024 * 1024) / BufferSize) { if (bufferSizeinMBytes < MinBufferSizeInMBytes) - throw LmsException{ "Profiler must be configured with at least " + std::to_string(MinBufferSizeInMBytes) + " MBytes" }; + throw LmsException{ "TraceLogger must be configured with at least " + std::to_string(MinBufferSizeInMBytes) + " MBytes" }; setThreadName(_creatorThreadId, "MainThread"); for (Buffer& buffer : _buffers) _freeBuffers.push_back(&buffer); - LMS_LOG(UTILS, INFO, "Profiler: using " << _buffers.size() << " buffers. Buffer size = " << std::to_string(BufferSize)); + LMS_LOG(UTILS, INFO, "TraceLogger: using " << _buffers.size() << " buffers. Buffer size = " << std::to_string(BufferSize)); } - bool Profiler::isLevelActive(Level level) const + bool TraceLogger::isLevelActive(Level level) const { return static_cast>(level) <= static_cast>(_minLevel); } - void Profiler::write(const CompleteEvent& event) + void TraceLogger::write(const CompleteEvent& event) { if (!_currentBuffer) _currentBuffer = acquireBuffer(); @@ -91,21 +91,21 @@ namespace profiling } } - void Profiler::onThreadPreDestroy() + void TraceLogger::onThreadPreDestroy() { if (_currentBuffer) releaseBuffer(_currentBuffer); } - Profiler::Buffer* Profiler::acquireBuffer() + TraceLogger::Buffer* TraceLogger::acquireBuffer() { - // We consider the creator thread will survive the profiler (thus we don't want to release anything on thread destruction) + // We consider the creator thread will survive the trace logger (thus we don't want to release anything on thread destruction) static thread_local CurrentThreadUnregisterer currentThreadUnregister{ _creatorThreadId == std::this_thread::get_id() ? nullptr : this }; std::scoped_lock lock{ _mutex }; assert(!_freeBuffers.empty()); - Profiler::Buffer* buffer{ _freeBuffers.front() }; + TraceLogger::Buffer* buffer{ _freeBuffers.front() }; _freeBuffers.pop_front(); // Empty new buffer only now (we want to keep history on released buffers since we dump them) @@ -113,7 +113,7 @@ namespace profiling return buffer; } - void Profiler::releaseBuffer(Buffer* buffer) + void TraceLogger::releaseBuffer(Buffer* buffer) { assert(buffer); @@ -121,7 +121,7 @@ namespace profiling _freeBuffers.push_back(buffer); } - void Profiler::dumpCurrentBuffer(std::ostream& os) + void TraceLogger::dumpCurrentBuffer(std::ostream& os) { os << "{" << std::endl; os << "\t\"traceEvents\": [" << std::endl; @@ -154,10 +154,12 @@ namespace profiling for (Buffer& buffer : _buffers) { - // Looks like tracing viewer is not pleased when nested event start at the same timestamp for (std::size_t i{}; i < buffer.currentDurationIndex; ++i) { + // Looks like tracing viewer is not pleased when nested event start at the same timestamp + // Hence the double representation as the microsecond unit is not precise enough using clockMicro = std::chrono::duration; + const CompleteEvent& event{ buffer.durationEvents[i] }; if (first) @@ -184,7 +186,7 @@ namespace profiling os << "}" << std::endl; } - void Profiler::setThreadName(std::thread::id id, std::string_view threadName) + void TraceLogger::setThreadName(std::thread::id id, std::string_view threadName) { std::scoped_lock lock{ _threadNameMutex }; _threadNames.emplace(id, threadName); diff --git a/src/libs/utils/impl/Profiler.hpp b/src/libs/utils/impl/TraceLogger.hpp similarity index 92% rename from src/libs/utils/impl/Profiler.hpp rename to src/libs/utils/impl/TraceLogger.hpp index b8b4fdc2..c786f282 100644 --- a/src/libs/utils/impl/Profiler.hpp +++ b/src/libs/utils/impl/TraceLogger.hpp @@ -26,14 +26,14 @@ #include #include -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" -namespace profiling +namespace tracing { - class Profiler : public IProfiler + class TraceLogger : public ITraceLogger { public: - Profiler(Level minLevel, std::size_t bufferSizeinMBytes); + TraceLogger(Level minLevel, std::size_t bufferSizeinMBytes); void onThreadPreDestroy(); diff --git a/src/libs/utils/impl/http/SendQueue.cpp b/src/libs/utils/impl/http/SendQueue.cpp index b5880d2e..4c5ccee2 100644 --- a/src/libs/utils/impl/http/SendQueue.cpp +++ b/src/libs/utils/impl/http/SendQueue.cpp @@ -24,7 +24,7 @@ #include "utils/Exception.hpp" #include "utils/ILogger.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/String.hpp" #define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[Http SendQueue] - " << message) @@ -118,7 +118,7 @@ namespace Http bool SendQueue::sendRequest(const ClientRequest& request) { - LMS_SCOPED_PROFILE_DETAILED("SendQueue", "SendRequest"); + LMS_SCOPED_TRACE_DETAILED("SendQueue", "SendRequest"); std::string url {_baseUrl + request.getParameters().relativeUrl}; LOG(DEBUG, "Sending request to url '" << url << "'"); @@ -144,7 +144,7 @@ namespace Http void SendQueue::onClientDone(Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) { - LMS_SCOPED_PROFILE_DETAILED("SendQueue", "OnClientDone"); + LMS_SCOPED_TRACE_DETAILED("SendQueue", "OnClientDone"); if (ec == boost::asio::error::operation_aborted) { diff --git a/src/libs/utils/include/utils/IProfiler.hpp b/src/libs/utils/include/utils/ITraceLogger.hpp similarity index 61% rename from src/libs/utils/include/utils/IProfiler.hpp rename to src/libs/utils/include/utils/ITraceLogger.hpp index 35cd3d12..ea3a1f4a 100644 --- a/src/libs/utils/include/utils/IProfiler.hpp +++ b/src/libs/utils/include/utils/ITraceLogger.hpp @@ -28,22 +28,22 @@ #include "LiteralString.hpp" #include "Service.hpp" -#define LMS_ENABLE_PROFILING 1 +#define LMS_SUPPORT_TRACING 1 #define LMS_CONCAT_IMPL(x, y) x##y #define LMS_CONCAT(x, y) LMS_CONCAT_IMPL(x, y) -#if LMS_ENABLE_PROFILING -#define LMS_SCOPED_PROFILE(CATEGORY, LEVEL, NAME) ::profiling::ScopedEvent LMS_CONCAT(scopedEvent_, __LINE__){ CATEGORY, LEVEL, NAME } +#if LMS_SUPPORT_TRACING +#define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) ::tracing::ScopedTrace LMS_CONCAT(ScopedTrace_, __LINE__){ CATEGORY, LEVEL, NAME } #else -#define LMS_SCOPED_PROFILE(CATEGORY, LEVEL, NAME) (void)0 +#define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) (void)0 #endif -#define LMS_SCOPED_PROFILE_OVERVIEW(CATEGORY, NAME) LMS_SCOPED_PROFILE(CATEGORY, ::profiling::Level::Overview, NAME) -#define LMS_SCOPED_PROFILE_DETAILED(CATEGORY, NAME) LMS_SCOPED_PROFILE(CATEGORY, ::profiling::Level::Detailed, NAME) +#define LMS_SCOPED_TRACE_OVERVIEW(CATEGORY, NAME) LMS_SCOPED_TRACE(CATEGORY, ::tracing::Level::Overview, NAME) +#define LMS_SCOPED_TRACE_DETAILED(CATEGORY, NAME) LMS_SCOPED_TRACE(CATEGORY, ::tracing::Level::Detailed, NAME) -namespace profiling +namespace tracing { using clock = std::chrono::steady_clock; @@ -53,7 +53,7 @@ namespace profiling Detailed, }; - class IProfiler + class ITraceLogger { public: struct CompleteEvent @@ -65,7 +65,7 @@ namespace profiling LiteralString category; }; - virtual ~IProfiler() = default; + virtual ~ITraceLogger() = default; virtual bool isLevelActive(Level level) const = 0; virtual void write(const CompleteEvent& entry) = 0; @@ -74,16 +74,16 @@ namespace profiling }; static constexpr std::size_t MinBufferSizeInMBytes = 16; - std::unique_ptr createProfiler(Level minLevel = Level::Overview, std::size_t bufferSizeInMbytes = MinBufferSizeInMBytes); + std::unique_ptr createTraceLogger(Level minLevel = Level::Overview, std::size_t bufferSizeInMbytes = MinBufferSizeInMBytes); - class ScopedEvent + class ScopedTrace { public: - ScopedEvent(LiteralString category, Level level, LiteralString name, IProfiler* profiler = Service::get()) + ScopedTrace(LiteralString category, Level level, LiteralString name, ITraceLogger* traceLogger = Service::get()) { - if (profiler && profiler->isLevelActive(level)) + if (traceLogger && traceLogger->isLevelActive(level)) { - _profiler = profiler; + _traceLogger = traceLogger; _event.start = clock::now(); _event.threadId = std::this_thread::get_id(); @@ -92,24 +92,24 @@ namespace profiling } else { - _profiler = nullptr; + _traceLogger = nullptr; } } - ~ScopedEvent() + ~ScopedTrace() { - if (_profiler) + if (_traceLogger) { _event.duration = clock::now() - _event.start; - _profiler->write(_event); + _traceLogger->write(_event); } } private: - ScopedEvent(const ScopedEvent&) = delete; - ScopedEvent& operator=(const ScopedEvent&) = delete; + ScopedTrace(const ScopedTrace&) = delete; + ScopedTrace& operator=(const ScopedTrace&) = delete; - IProfiler* _profiler; - IProfiler::CompleteEvent _event; + ITraceLogger* _traceLogger; + ITraceLogger::CompleteEvent _event; }; } \ No newline at end of file diff --git a/src/libs/utils/test/CMakeLists.txt b/src/libs/utils/test/CMakeLists.txt index b8762ad8..b6d2012e 100644 --- a/src/libs/utils/test/CMakeLists.txt +++ b/src/libs/utils/test/CMakeLists.txt @@ -4,9 +4,9 @@ add_executable(test-utils EnumSet.cpp LiteralString.cpp Path.cpp - Profiler.cpp RecursiveSharedMutex.cpp String.cpp + TraceLogger.cpp Utils.cpp ) diff --git a/src/libs/utils/test/Profiler.cpp b/src/libs/utils/test/TraceLogger.cpp similarity index 74% rename from src/libs/utils/test/Profiler.cpp rename to src/libs/utils/test/TraceLogger.cpp index 45e02eed..f6aca813 100644 --- a/src/libs/utils/test/Profiler.cpp +++ b/src/libs/utils/test/TraceLogger.cpp @@ -21,22 +21,22 @@ #include #include -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" -namespace profiling::tests +namespace tracing::tests { // not much can be tested with this implementation - TEST(Profiler, MultipleThreads) + TEST(TraceLogger, MultipleThreads) { - auto profiler{ createProfiler(Level::Overview) }; + auto traceLogger{ createTraceLogger(Level::Overview) }; std::vector threads; for (std::size_t i{}; i < 16; ++i) { threads.emplace_back([&] { - ScopedEvent loggedEvent{ "MyCategory", Level::Overview, "MyEventLogged", profiler.get() }; - ScopedEvent notLoggedEvent{ "MyCategory", Level::Detailed, "MyEventNotLogged", profiler.get() }; + ScopedTrace loggedEvent{ "MyCategory", Level::Overview, "MyEventLogged", traceLogger.get() }; + ScopedTrace notLoggedEvent{ "MyCategory", Level::Detailed, "MyEventNotLogged", traceLogger.get() }; }); } @@ -44,7 +44,7 @@ namespace profiling::tests t.join(); std::ostringstream oss; - profiler->dumpCurrentBuffer(oss); + traceLogger->dumpCurrentBuffer(oss); EXPECT_NE(oss.str().find("MyEventLogged"), std::string::npos); EXPECT_EQ(oss.str().find("MyEventNotLogged"), std::string::npos); diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index 2154e399..6b926f17 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -14,7 +14,7 @@ add_executable(lms ui/admin/InitWizardView.cpp ui/admin/MediaLibrariesView.cpp ui/admin/MediaLibraryModal.cpp - ui/admin/ProfilerController.cpp + ui/admin/TracingView.cpp ui/admin/ScannerController.cpp ui/admin/ScanSettingsView.cpp ui/admin/UserView.cpp diff --git a/src/lms/main.cpp b/src/lms/main.cpp index ef20d249..f1bc70cc 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -43,7 +43,7 @@ #include "utils/IChildProcessManager.hpp" #include "utils/IConfig.hpp" #include "utils/IOContextRunner.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" #include "utils/WtLogger.hpp" @@ -76,18 +76,18 @@ namespace throw LmsException{ "Invalid config value for 'log-min-severity'" }; } - std::optional getProfilingLevel() + std::optional getTracingLevel() { - std::string_view profilingLevel{ Service::get()->getString("profiling-level", "disabled") }; + std::string_view tracingLevel{ Service::get()->getString("tracing-level", "disabled") }; - if (profilingLevel == "disabled") + if (tracingLevel == "disabled") return std::nullopt; - else if (profilingLevel == "overview") - return profiling::Level::Overview; - else if (profilingLevel == "detailed") - return profiling::Level::Detailed; + else if (tracingLevel == "overview") + return tracing::Level::Overview; + else if (tracingLevel == "detailed") + return tracing::Level::Detailed; - throw LmsException{ "Invalid config value for 'profiling-level'" }; + throw LmsException{ "Invalid config value for 'tracing-level'" }; } std::vector generateWtConfig(std::string execPath, Severity minSeverity) @@ -248,9 +248,9 @@ int main(int argc, char* argv[]) Service config{ createConfig(configFilePath) }; const Severity minLogSeverity{getLogMinSeverity()}; Service logger{ std::make_unique(minLogSeverity) }; - std::optional> profiler; - if (const auto level{ getProfilingLevel() }) - profiler.emplace(profiling::createProfiler(level.value(), config->getULong("profiling-buffer-size", profiling::MinBufferSizeInMBytes))); + std::optional> traceLogger; + if (const auto level{ getTracingLevel() }) + traceLogger.emplace(tracing::createTraceLogger(level.value(), config->getULong("tracing-buffer-size", tracing::MinBufferSizeInMBytes))); // use system locale. libarchive relies on this to write filenames if (char* locale{ ::setlocale(LC_ALL, "") }) @@ -261,7 +261,6 @@ int main(int argc, char* argv[]) // Make sure the working directory exists std::filesystem::create_directories(config->getPath("working-dir")); std::filesystem::create_directories(config->getPath("working-dir") / "cache"); - std::filesystem::create_directories(config->getPath("working-dir") / "profiling"); // Construct WT configuration and get the argc/argv back const std::vector wtServerArgs{ generateWtConfig(argv[0], minLogSeverity) }; diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index 735341d5..e9704765 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -38,13 +38,13 @@ #include "database/User.hpp" #include "services/scrobbling/IScrobblingService.hpp" #include "utils/ILogger.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" #include "admin/InitWizardView.hpp" #include "admin/MediaLibrariesView.hpp" -#include "admin/ProfilerController.hpp" +#include "admin/TracingView.hpp" #include "admin/ScannerController.hpp" #include "admin/ScanSettingsView.hpp" #include "admin/UserView.hpp" @@ -79,9 +79,9 @@ namespace UserInterface res->use(appRoot + "admin-initwizard"); res->use(appRoot + "admin-medialibraries"); res->use(appRoot + "admin-medialibrary"); - res->use(appRoot + "admin-profilercontroller"); res->use(appRoot + "admin-scannercontroller"); res->use(appRoot + "admin-scansettings"); + res->use(appRoot + "admin-tracing"); res->use(appRoot + "admin-user"); res->use(appRoot + "admin-users"); res->use(appRoot + "artist"); @@ -122,7 +122,7 @@ namespace UserInterface IdxAdminScanner, IdxAdminUsers, IdxAdminUser, - IdxAdminProfiler, + IdxAdminTracing, }; void handlePathChange(Wt::WStackedWidget& stack, bool isAdmin) @@ -150,7 +150,7 @@ namespace UserInterface { "/admin/scanner", IdxAdminScanner, true, Wt::WString::tr("Lms.Admin.ScannerController.scanner") }, { "/admin/users", IdxAdminUsers, true, Wt::WString::tr("Lms.Admin.Users.users") }, { "/admin/user", IdxAdminUser, true, std::nullopt }, - { "/admin/profiler", IdxAdminProfiler, true, Wt::WString::tr("Lms.Admin.ProfilerController.profiler") }, + { "/admin/tracing", IdxAdminTracing, true, Wt::WString::tr("Lms.Admin.Tracing.tracing") }, }; LMS_LOG(UI, DEBUG, "Internal path changed to '" << wApp->internalPath() << "'"); @@ -269,7 +269,7 @@ namespace UserInterface void LmsApplication::init() { - LMS_SCOPED_PROFILE_OVERVIEW("UI", "ApplicationInit"); + LMS_SCOPED_TRACE_OVERVIEW("UI", "ApplicationInit"); setTheme(std::make_shared()); @@ -388,7 +388,7 @@ namespace UserInterface void LmsApplication::createHome() { - LMS_SCOPED_PROFILE_OVERVIEW("UI", "ApplicationCreateHome"); + LMS_SCOPED_TRACE_OVERVIEW("UI", "ApplicationCreateHome"); _coverResource = std::make_shared(); @@ -445,11 +445,11 @@ namespace UserInterface navbar->bindNew("scan-settings", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/scan-settings" }, Wt::WString::tr("Lms.Admin.menu-scan-settings")); navbar->bindNew("scanner", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/scanner" }, Wt::WString::tr("Lms.Admin.menu-scanner")); navbar->bindNew("users", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/users" }, Wt::WString::tr("Lms.Admin.menu-users")); - // Hide the entry if the profiler is not enabled - if (Service<::profiling::IProfiler>::get()) - navbar->bindNew("profiler", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/profiler" }, Wt::WString::tr("Lms.Admin.menu-profiler")); + // Hide the entry if the trace logger is not enabled + if (Service<::tracing::ITraceLogger>::get()) + navbar->bindNew("tracing", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/tracing" }, Wt::WString::tr("Lms.Admin.menu-tracing")); else - navbar->bindEmpty("profiler"); + navbar->bindEmpty("tracing"); } // Contents @@ -481,7 +481,7 @@ namespace UserInterface mainStack->addNew(); mainStack->addNew(); mainStack->addNew(); - mainStack->addNew(); + mainStack->addNew(); } explore->getPlayQueueController().setMaxTrackCountToEnqueue(_playQueue->getCapacity()); @@ -559,7 +559,7 @@ namespace UserInterface { try { - LMS_SCOPED_PROFILE_OVERVIEW("UI", "ProcessEvent"); + LMS_SCOPED_TRACE_OVERVIEW("UI", "ProcessEvent"); WApplication::notify(event); } catch (LmsApplicationException& e) diff --git a/src/lms/ui/admin/ProfilerController.cpp b/src/lms/ui/admin/TracingView.cpp similarity index 71% rename from src/lms/ui/admin/ProfilerController.cpp rename to src/lms/ui/admin/TracingView.cpp index 4de5d1a4..2e156b32 100644 --- a/src/lms/ui/admin/ProfilerController.cpp +++ b/src/lms/ui/admin/TracingView.cpp @@ -17,14 +17,14 @@ * along with LMS. If not, see . */ -#include "ProfilerController.hpp" +#include "TracingView.hpp" #include #include #include #include -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/String.hpp" namespace UserInterface @@ -34,8 +34,8 @@ namespace UserInterface class ReportResource : public Wt::WResource { public: - ReportResource(::profiling::IProfiler& profiler) - : _profiler{ profiler } + ReportResource(::tracing::ITraceLogger& traceLogger) + : _traceLogger{ traceLogger } { } @@ -46,26 +46,26 @@ namespace UserInterface void handleRequest(const Wt::Http::Request&, Wt::Http::Response& response) { - suggestFileName(StringUtils::toISO8601String(Wt::WDateTime::currentDateTime()) + "-profiling.json"); + suggestFileName(StringUtils::toISO8601String(Wt::WDateTime::currentDateTime()) + "-traces.json"); response.setMimeType("application/json"); - _profiler.dumpCurrentBuffer(response.out()); + _traceLogger.dumpCurrentBuffer(response.out()); } private: - profiling::IProfiler& _profiler; + tracing::ITraceLogger& _traceLogger; }; } - ProfilerController::ProfilerController() - : Wt::WTemplate{ Wt::WString::tr("Lms.Admin.ProfilerController.template") } + TracingView::TracingView() + : Wt::WTemplate{ Wt::WString::tr("Lms.Admin.Tracing.template") } { addFunction("tr", &Wt::WTemplate::Functions::tr); - Wt::WPushButton* dumpBtn{ bindNew("export-btn", Wt::WString::tr("Lms.Admin.ProfilerController.export-current-buffer")) }; + Wt::WPushButton* dumpBtn{ bindNew("export-btn", Wt::WString::tr("Lms.Admin.Tracing.export-current-buffer")) }; - if (auto profiler{ Service::get() }) + if (auto traceLogger{ Service::get() }) { - Wt::WLink link{ std::make_shared(*profiler) }; + Wt::WLink link{ std::make_shared(*traceLogger) }; link.setTarget(Wt::LinkTarget::NewWindow); dumpBtn->setLink(link); } diff --git a/src/lms/ui/admin/ProfilerController.hpp b/src/lms/ui/admin/TracingView.hpp similarity index 90% rename from src/lms/ui/admin/ProfilerController.hpp rename to src/lms/ui/admin/TracingView.hpp index 22a92b8c..acf82584 100644 --- a/src/lms/ui/admin/ProfilerController.hpp +++ b/src/lms/ui/admin/TracingView.hpp @@ -23,9 +23,9 @@ namespace UserInterface { - class ProfilerController : public Wt::WTemplate + class TracingView : public Wt::WTemplate { public: - ProfilerController(); + TracingView(); }; } // namespace UserInterface diff --git a/src/lms/ui/resource/AudioFileResource.cpp b/src/lms/ui/resource/AudioFileResource.cpp index 58b84adc..5b10ff56 100644 --- a/src/lms/ui/resource/AudioFileResource.cpp +++ b/src/lms/ui/resource/AudioFileResource.cpp @@ -27,7 +27,7 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "utils/ILogger.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/String.hpp" #include "LmsApplication.hpp" @@ -84,7 +84,7 @@ namespace UserInterface void AudioFileResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) { - LMS_SCOPED_PROFILE_OVERVIEW("UI", "HandleAudioFileRequest"); + LMS_SCOPED_TRACE_OVERVIEW("UI", "HandleAudioFileRequest"); std::shared_ptr fileResourceHandler; diff --git a/src/lms/ui/resource/CoverResource.cpp b/src/lms/ui/resource/CoverResource.cpp index 21cfaf80..080b4431 100644 --- a/src/lms/ui/resource/CoverResource.cpp +++ b/src/lms/ui/resource/CoverResource.cpp @@ -26,7 +26,7 @@ #include "database/Track.hpp" #include "utils/Exception.hpp" #include "utils/ILogger.hpp" -#include "utils/IProfiler.hpp" +#include "utils/ITraceLogger.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" @@ -62,7 +62,7 @@ namespace UserInterface void CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) { - LMS_SCOPED_PROFILE_OVERVIEW("UI", "HandleCoverRequest"); + LMS_SCOPED_TRACE_OVERVIEW("UI", "HandleCoverRequest"); // Retrieve parameters const std::string* trackIdStr = request.getParameter("trackid"); From 377e50c525bcef47fbd29364f26fefab7f4dc2f8 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 10 Mar 2024 15:27:45 +0100 Subject: [PATCH 17/41] Ignore vscode stuff for now --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 761aaa87..b8617e34 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ CMakeCache.txt CMakeFiles/ build/ +.vscode/ \ No newline at end of file From 487960b4131aa04124065d6051d53524fd2e1b37 Mon Sep 17 00:00:00 2001 From: emeric Date: Sun, 10 Mar 2024 15:28:16 +0100 Subject: [PATCH 18/41] Switched to LiteralString for keys in subsonic responses --- src/libs/subsonic/impl/SubsonicResponse.cpp | 22 ++++++++++----------- src/libs/subsonic/impl/SubsonicResponse.hpp | 14 ++----------- 2 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/libs/subsonic/impl/SubsonicResponse.cpp b/src/libs/subsonic/impl/SubsonicResponse.cpp index c5d971e3..70153e2c 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.cpp +++ b/src/libs/subsonic/impl/SubsonicResponse.cpp @@ -193,13 +193,13 @@ namespace API::Subsonic for (const auto& [key, value] : node._attributes) { if (std::holds_alternative(value)) - res.put("." + std::string{ key.get() }, std::get(value)); + res.put("." + std::string{ key.str() }, std::get(value)); else if (std::holds_alternative(value)) - res.put("." + std::string{ key.get() }, std::get(value)); + res.put("." + std::string{ key.str() }, std::get(value)); else if (std::holds_alternative(value)) - res.put("." + std::string{ key.get() }, std::get(value)); + res.put("." + std::string{ key.str() }, std::get(value)); else if (std::holds_alternative(value)) - res.put("." + std::string{ key.get() }, std::get(value)); + res.put("." + std::string{ key.str() }, std::get(value)); } auto valueToPropertyTree = [](const Node::ValueType& value) @@ -221,19 +221,19 @@ namespace API::Subsonic { for (const auto& [key, childNode] : node._children) { - res.add_child(std::string{ key.get() }, nodeToPropertyTree(childNode)); + res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode)); } for (const auto& [key, childArrayNodes] : node._childrenArrays) { for (const Node& childNode : childArrayNodes) - res.add_child(std::string{ key.get() }, nodeToPropertyTree(childNode)); + res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode)); } for (const auto& [key, childArrayValues] : node._childrenValues) { for (const Response::Node::ValueType& value : childArrayValues) - res.add_child(std::string{ key.get() }, valueToPropertyTree(value)); + res.add_child(std::string{ key.str() }, valueToPropertyTree(value)); } } @@ -255,7 +255,7 @@ namespace API::Subsonic if (!first) os << ','; - serializeEscapedString(os, key.get()); + serializeEscapedString(os, key.str()); os << ':'; serializeValue(os, value); @@ -279,7 +279,7 @@ namespace API::Subsonic if (!first) os << ','; - serializeEscapedString(os, key.get()); + serializeEscapedString(os, key.str()); os << ':'; serializeNode(os, childNode); @@ -291,7 +291,7 @@ namespace API::Subsonic if (!first) os << ','; - serializeEscapedString(os, key.get()); + serializeEscapedString(os, key.str()); os << ":["; bool firstChild{ true }; @@ -313,7 +313,7 @@ namespace API::Subsonic if (!first) os << ','; - serializeEscapedString(os, key.get()); + serializeEscapedString(os, key.str()); os << ":["; bool firstChild{ true }; diff --git a/src/libs/subsonic/impl/SubsonicResponse.hpp b/src/libs/subsonic/impl/SubsonicResponse.hpp index 60690dae..5ff50686 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.hpp +++ b/src/libs/subsonic/impl/SubsonicResponse.hpp @@ -25,6 +25,7 @@ #include #include +#include "utils/LiteralString.hpp" #include "RequestContext.hpp" namespace API::Subsonic @@ -205,18 +206,7 @@ namespace API::Subsonic class Node { public: - class Key - { - public: - template - constexpr Key(const char (&str)[N]) : _str{ str } {} - constexpr std::string_view get() const { return _str; } - - bool constexpr operator<(const Key& other) const { return _str < other._str; } - - private: - const std::string_view _str; - }; + using Key = LiteralString; void setAttribute(Key key, std::string_view value); From 3261cc6550cf794d76d4368f32921ef057f309e3 Mon Sep 17 00:00:00 2001 From: David Roman Date: Mon, 11 Mar 2024 19:25:30 +0100 Subject: [PATCH 19/41] make tests optional --- CMakeLists.txt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 369ef896..0da122b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,11 +10,16 @@ if (UNIX) set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--no-undefined") endif () -include(CTest) +option(ENABLE_TESTS "Enable tests" ON) + +if(ENABLE_TESTS) + include(CTest) + find_package(GTest REQUIRED) +endif() + find_package(PkgConfig REQUIRED) find_package(Threads REQUIRED) find_package(Filesystem REQUIRED) -find_package(GTest REQUIRED) find_package(Boost REQUIRED COMPONENTS system program_options) find_package(Wt REQUIRED COMPONENTS Wt Dbo DboSqlite3 HTTP) pkg_check_modules(Taglib REQUIRED IMPORTED_TARGET taglib) From 0e78820402f9d68127368d11e643d50f54054f5d Mon Sep 17 00:00:00 2001 From: David Roman Date: Mon, 11 Mar 2024 19:27:08 +0100 Subject: [PATCH 20/41] use cmake lib dir instead of hardcoded name --- src/libs/av/CMakeLists.txt | 2 +- src/libs/database/CMakeLists.txt | 2 +- src/libs/image/CMakeLists.txt | 2 +- src/libs/metadata/CMakeLists.txt | 2 +- src/libs/services/auth/CMakeLists.txt | 2 +- src/libs/services/cover/CMakeLists.txt | 2 +- src/libs/services/feedback/CMakeLists.txt | 2 +- src/libs/services/recommendation/CMakeLists.txt | 2 +- src/libs/services/scanner/CMakeLists.txt | 2 +- src/libs/services/scrobbling/CMakeLists.txt | 2 +- src/libs/som/CMakeLists.txt | 2 +- src/libs/subsonic/CMakeLists.txt | 2 +- src/libs/utils/CMakeLists.txt | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/libs/av/CMakeLists.txt b/src/libs/av/CMakeLists.txt index fee1201e..e616260d 100644 --- a/src/libs/av/CMakeLists.txt +++ b/src/libs/av/CMakeLists.txt @@ -26,5 +26,5 @@ target_link_libraries(lmsav PRIVATE PkgConfig::LIBAV ) -install(TARGETS lmsav DESTINATION lib) +install(TARGETS lmsav DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/libs/database/CMakeLists.txt b/src/libs/database/CMakeLists.txt index 7188f977..0b1eb84e 100644 --- a/src/libs/database/CMakeLists.txt +++ b/src/libs/database/CMakeLists.txt @@ -42,7 +42,7 @@ target_link_libraries(lmsdatabase PUBLIC Wt::Dbo ) -install(TARGETS lmsdatabase DESTINATION lib) +install(TARGETS lmsdatabase DESTINATION ${CMAKE_INSTALL_LIBDIR}) if(BUILD_TESTING) add_subdirectory(test) diff --git a/src/libs/image/CMakeLists.txt b/src/libs/image/CMakeLists.txt index cb855563..eb48565e 100644 --- a/src/libs/image/CMakeLists.txt +++ b/src/libs/image/CMakeLists.txt @@ -34,5 +34,5 @@ else () message(FATAL_ERROR "Invalid IMAGE_LIBRARY provided") endif() -install(TARGETS lmsimage DESTINATION lib) +install(TARGETS lmsimage DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/libs/metadata/CMakeLists.txt b/src/libs/metadata/CMakeLists.txt index 8a11a7cf..8b3cb54c 100644 --- a/src/libs/metadata/CMakeLists.txt +++ b/src/libs/metadata/CMakeLists.txt @@ -28,5 +28,5 @@ target_link_libraries(lmsmetadata PUBLIC std::filesystem ) -install(TARGETS lmsmetadata DESTINATION lib) +install(TARGETS lmsmetadata DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/libs/services/auth/CMakeLists.txt b/src/libs/services/auth/CMakeLists.txt index 0a2eb6a5..edb9c4c2 100644 --- a/src/libs/services/auth/CMakeLists.txt +++ b/src/libs/services/auth/CMakeLists.txt @@ -35,5 +35,5 @@ if (USE_PAM) target_link_libraries(lmsauth PRIVATE ${PAM_LIBRARIES}) endif (USE_PAM) -install(TARGETS lmsauth DESTINATION lib) +install(TARGETS lmsauth DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/libs/services/cover/CMakeLists.txt b/src/libs/services/cover/CMakeLists.txt index 41169c5d..6dc17262 100644 --- a/src/libs/services/cover/CMakeLists.txt +++ b/src/libs/services/cover/CMakeLists.txt @@ -24,5 +24,5 @@ target_link_libraries(lmsservice-cover PUBLIC std::filesystem ) -install(TARGETS lmsservice-cover DESTINATION lib) +install(TARGETS lmsservice-cover DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/libs/services/feedback/CMakeLists.txt b/src/libs/services/feedback/CMakeLists.txt index 02e63624..b7634e77 100644 --- a/src/libs/services/feedback/CMakeLists.txt +++ b/src/libs/services/feedback/CMakeLists.txt @@ -26,4 +26,4 @@ target_link_libraries(lmsfeedback PUBLIC lmsdatabase ) -install(TARGETS lmsfeedback DESTINATION lib) +install(TARGETS lmsfeedback DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/libs/services/recommendation/CMakeLists.txt b/src/libs/services/recommendation/CMakeLists.txt index 8244679b..603a66b0 100644 --- a/src/libs/services/recommendation/CMakeLists.txt +++ b/src/libs/services/recommendation/CMakeLists.txt @@ -26,5 +26,5 @@ target_link_libraries(lmsrecommendation PRIVATE std::filesystem ) -install(TARGETS lmsrecommendation DESTINATION lib) +install(TARGETS lmsrecommendation DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/libs/services/scanner/CMakeLists.txt b/src/libs/services/scanner/CMakeLists.txt index 8a0508e9..78d49e73 100644 --- a/src/libs/services/scanner/CMakeLists.txt +++ b/src/libs/services/scanner/CMakeLists.txt @@ -29,5 +29,5 @@ target_link_libraries(lmsscanner PUBLIC Wt::Wt ) -install(TARGETS lmsscanner DESTINATION lib) +install(TARGETS lmsscanner DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/libs/services/scrobbling/CMakeLists.txt b/src/libs/services/scrobbling/CMakeLists.txt index 2f78000a..db065828 100644 --- a/src/libs/services/scrobbling/CMakeLists.txt +++ b/src/libs/services/scrobbling/CMakeLists.txt @@ -26,7 +26,7 @@ target_link_libraries(lmsscrobbling PUBLIC lmsdatabase ) -install(TARGETS lmsscrobbling DESTINATION lib) +install(TARGETS lmsscrobbling DESTINATION ${CMAKE_INSTALL_LIBDIR}) if(BUILD_TESTING) add_subdirectory(test) diff --git a/src/libs/som/CMakeLists.txt b/src/libs/som/CMakeLists.txt index 87a2d53f..eeb971d6 100644 --- a/src/libs/som/CMakeLists.txt +++ b/src/libs/som/CMakeLists.txt @@ -17,7 +17,7 @@ target_link_libraries(lmssom PUBLIC set_property(TARGET lmssom PROPERTY POSITION_INDEPENDENT_CODE ON) -install(TARGETS lmssom DESTINATION lib) +install(TARGETS lmssom DESTINATION ${CMAKE_INSTALL_LIBDIR}) if(BUILD_TESTING) add_subdirectory(test) diff --git a/src/libs/subsonic/CMakeLists.txt b/src/libs/subsonic/CMakeLists.txt index 07394e94..41afbbb8 100644 --- a/src/libs/subsonic/CMakeLists.txt +++ b/src/libs/subsonic/CMakeLists.txt @@ -56,5 +56,5 @@ target_link_libraries(lmssubsonic PUBLIC Wt::Wt ) -install(TARGETS lmssubsonic DESTINATION lib) +install(TARGETS lmssubsonic DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/utils/CMakeLists.txt index 7d00aaa1..35368f15 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/utils/CMakeLists.txt @@ -38,7 +38,7 @@ target_link_libraries(lmsutils PUBLIC Wt::Wt ) -install(TARGETS lmsutils DESTINATION lib) +install(TARGETS lmsutils DESTINATION ${CMAKE_INSTALL_LIBDIR}) if(BUILD_TESTING) add_subdirectory(test) From bc03408b995e6a7316b4d6fa939be2a37d804529 Mon Sep 17 00:00:00 2001 From: David Roman Date: Mon, 11 Mar 2024 20:49:18 +0100 Subject: [PATCH 21/41] Fix assertion Closes: #431 --- src/libs/database/impl/Utils.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/database/impl/Utils.hpp b/src/libs/database/impl/Utils.hpp index 29be679c..83c7e3df 100644 --- a/src/libs/database/impl/Utils.hpp +++ b/src/libs/database/impl/Utils.hpp @@ -62,11 +62,11 @@ namespace Database::Utils // TODO may optim by not actually requesting the last one res.moreResults = true; res.results.pop_back(); + res.range.offset = range->offset; } else res.moreResults = false; - res.range.offset = range->offset; res.range.size = res.results.size(); return res; From 4b7c4295ec74178eda32206faaf3f3dce75e0db4 Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 12 Mar 2024 08:32:08 +0100 Subject: [PATCH 22/41] Refactored namespaces --- src/libs/CMakeLists.txt | 2 +- src/libs/av/CMakeLists.txt | 2 +- src/libs/av/impl/AudioFile.cpp | 16 +- src/libs/av/impl/AudioFile.hpp | 4 +- .../av/impl/RawResourceHandlerCreator.cpp | 6 +- src/libs/av/impl/Transcoder.cpp | 22 +- src/libs/av/impl/Transcoder.hpp | 12 +- .../av/impl/TranscodingResourceHandler.cpp | 4 +- .../av/impl/TranscodingResourceHandler.hpp | 4 +- src/libs/av/include/av/IAudioFile.hpp | 4 +- .../include/av/RawResourceHandlerCreator.hpp | 4 +- .../av/include/av/TranscodingParameters.hpp | 4 +- .../av/TranscodingResourceHandlerCreator.hpp | 4 +- src/libs/av/include/av/Types.hpp | 6 +- src/libs/{utils => core}/CMakeLists.txt | 12 +- src/libs/core/bench/CMakeLists.txt | 9 + src/libs/core/bench/TraceLoggerBench.cpp | 55 + src/libs/core/impl/ArchiveZipper.cpp | 298 ++ src/libs/core/impl/ArchiveZipper.hpp | 80 + src/libs/core/impl/ChildProcess.cpp | 209 ++ .../{utils => core}/impl/ChildProcess.hpp | 42 +- .../impl/ChildProcessManager.cpp | 34 +- .../impl/ChildProcessManager.hpp | 31 +- src/libs/core/impl/Config.cpp | 130 + src/libs/core/impl/Config.hpp | 51 + src/libs/core/impl/FileResourceHandler.cpp | 137 + .../impl/FileResourceHandler.hpp | 30 +- src/libs/core/impl/IOContextRunner.cpp | 85 + src/libs/core/impl/Logger.cpp | 81 + src/libs/{utils => core}/impl/NetAddress.cpp | 2 +- src/libs/{utils => core}/impl/Path.cpp | 22 +- src/libs/{utils => core}/impl/Random.cpp | 26 +- src/libs/core/impl/RecursiveSharedMutex.cpp | 134 + .../{utils => core}/impl/StreamLogger.cpp | 24 +- src/libs/{utils => core}/impl/String.cpp | 8 +- src/libs/{utils => core}/impl/TraceLogger.cpp | 6 +- src/libs/{utils => core}/impl/TraceLogger.hpp | 4 +- src/libs/core/impl/UUID.cpp | 91 + src/libs/core/impl/WtLogger.cpp | 69 + src/libs/{utils => core}/impl/http/Client.cpp | 34 +- src/libs/{utils => core}/impl/http/Client.hpp | 29 +- src/libs/core/impl/http/ClientRequest.hpp | 75 + src/libs/core/impl/http/SendQueue.cpp | 240 ++ src/libs/core/impl/http/SendQueue.hpp | 78 + .../include/core}/Crc32Calculator.hpp | 36 +- src/libs/core/include/core/EnumSet.hpp | 193 ++ .../utils => core/include/core}/Exception.hpp | 13 +- .../core}/FileResourceHandlerCreator.hpp | 8 +- .../include/core}/IChildProcess.hpp | 46 +- .../include/core}/IChildProcessManager.hpp | 17 +- src/libs/core/include/core/IConfig.hpp | 43 + src/libs/core/include/core/ILogger.hpp | 103 + .../include/core}/IOContextRunner.hpp | 29 +- .../include/core}/IResourceHandler.hpp | 11 +- .../include/core}/ITraceLogger.hpp | 8 +- .../utils => core/include/core}/IZipper.hpp | 44 +- src/libs/core/include/core/LiteralString.hpp | 101 + .../include/core}/NetAddress.hpp | 0 .../utils => core/include/core}/Path.hpp | 4 +- src/libs/core/include/core/Random.hpp | 60 + .../include/core}/RecursiveSharedMutex.hpp | 36 +- src/libs/core/include/core/Service.hpp | 73 + src/libs/core/include/core/StreamLogger.hpp | 42 + .../utils => core/include/core}/String.hpp | 5 +- .../utils => core/include/core}/Tuple.hpp | 10 +- .../utils => core/include/core}/UUID.hpp | 28 +- .../utils => core/include/core}/Utils.hpp | 6 +- .../utils => core/include/core}/WtLogger.hpp | 23 +- .../core}/ZipperResourceHandlerCreator.hpp | 10 +- .../core}/http/ClientRequestParameters.hpp | 51 +- .../include/core}/http/IClient.hpp | 15 +- src/libs/{utils => core}/test/CMakeLists.txt | 8 +- src/libs/core/test/EnumSet.cpp | 63 + src/libs/core/test/LiteralString.cpp | 72 + src/libs/core/test/Path.cpp | 108 + src/libs/core/test/RecursiveSharedMutex.cpp | 105 + src/libs/core/test/String.cpp | 297 ++ src/libs/{utils => core}/test/TraceLogger.cpp | 6 +- src/libs/{utils => core}/test/Utils.cpp | 0 src/libs/database/CMakeLists.txt | 2 +- src/libs/database/impl/Artist.cpp | 32 +- src/libs/database/impl/AuthToken.cpp | 2 +- src/libs/database/impl/Cluster.cpp | 18 +- src/libs/database/impl/Db.cpp | 12 +- src/libs/database/impl/EnumSetTraits.hpp | 10 +- src/libs/database/impl/IdTypeTraits.hpp | 4 +- src/libs/database/impl/Listen.cpp | 18 +- src/libs/database/impl/MediaLibrary.cpp | 4 +- src/libs/database/impl/Migration.cpp | 14 +- src/libs/database/impl/Migration.hpp | 2 +- src/libs/database/impl/Release.cpp | 24 +- src/libs/database/impl/ScanSettings.cpp | 20 +- src/libs/database/impl/Session.cpp | 13 +- src/libs/database/impl/SqlQuery.cpp | 265 +- src/libs/database/impl/SqlQuery.hpp | 153 +- src/libs/database/impl/StarredArtist.cpp | 4 +- src/libs/database/impl/StarredRelease.cpp | 4 +- src/libs/database/impl/StarredTrack.cpp | 6 +- src/libs/database/impl/Track.cpp | 30 +- src/libs/database/impl/TrackArtistLink.cpp | 12 +- src/libs/database/impl/TrackBookmark.cpp | 8 +- src/libs/database/impl/TrackFeatures.cpp | 8 +- src/libs/database/impl/TrackList.cpp | 23 +- src/libs/database/impl/TransactionChecker.cpp | 2 +- src/libs/database/impl/Types.cpp | 2 +- src/libs/database/impl/User.cpp | 8 +- src/libs/database/impl/Utils.cpp | 26 +- src/libs/database/impl/Utils.hpp | 6 +- src/libs/database/include/database/Artist.hpp | 20 +- .../database/include/database/AuthToken.hpp | 2 +- .../database/include/database/Cluster.hpp | 6 +- src/libs/database/include/database/Db.hpp | 14 +- src/libs/database/include/database/IdType.hpp | 12 +- src/libs/database/include/database/Listen.hpp | 5 +- .../include/database/MediaLibrary.hpp | 6 +- src/libs/database/include/database/Object.hpp | 6 +- .../database/include/database/Release.hpp | 28 +- .../database/include/database/ReleaseId.hpp | 1 - .../include/database/ReleaseTypeId.hpp | 1 - .../include/database/ScanSettings.hpp | 4 +- .../database/include/database/Session.hpp | 16 +- .../include/database/StarredArtist.hpp | 4 +- .../include/database/StarredArtistId.hpp | 1 - .../include/database/StarredRelease.hpp | 4 +- .../include/database/StarredReleaseId.hpp | 1 - .../include/database/StarredTrack.hpp | 6 +- .../include/database/StarredTrackId.hpp | 1 - src/libs/database/include/database/Track.hpp | 36 +- .../include/database/TrackArtistLink.hpp | 8 +- .../include/database/TrackBookmark.hpp | 82 +- .../include/database/TrackFeatures.hpp | 69 +- .../database/include/database/TrackId.hpp | 1 - .../database/include/database/TrackList.hpp | 6 +- .../database/include/database/TrackListId.hpp | 1 - .../include/database/TransactionChecker.hpp | 2 +- src/libs/database/include/database/Types.hpp | 2 +- src/libs/database/include/database/User.hpp | 10 +- src/libs/database/include/database/UserId.hpp | 1 - src/libs/database/test/Artist.cpp | 849 ++--- src/libs/database/test/Cluster.cpp | 1537 ++++----- src/libs/database/test/Common.cpp | 362 +- src/libs/database/test/Common.hpp | 200 +- src/libs/database/test/DatabaseTest.cpp | 133 +- src/libs/database/test/Listen.cpp | 3050 ++++++++--------- src/libs/database/test/Release.cpp | 1524 ++++---- src/libs/database/test/StarredArtist.cpp | 221 +- src/libs/database/test/StarredRelease.cpp | 199 +- src/libs/database/test/StarredTrack.cpp | 199 +- src/libs/database/test/Track.cpp | 386 +-- src/libs/database/test/TrackBookmark.cpp | 67 +- src/libs/database/test/TrackFeatures.cpp | 41 +- src/libs/database/test/TrackList.cpp | 343 +- src/libs/image/CMakeLists.txt | 2 +- .../image/impl/graphicsmagick/JPEGImage.cpp | 6 +- .../image/impl/graphicsmagick/JPEGImage.hpp | 2 +- .../image/impl/graphicsmagick/RawImage.cpp | 18 +- .../image/impl/graphicsmagick/RawImage.hpp | 2 +- src/libs/image/impl/stb/JPEGImage.cpp | 4 +- src/libs/image/impl/stb/JPEGImage.hpp | 2 +- src/libs/image/impl/stb/RawImage.cpp | 12 +- src/libs/image/impl/stb/RawImage.hpp | 2 +- src/libs/image/include/image/Exception.hpp | 9 +- .../image/include/image/IEncodedImage.hpp | 5 +- src/libs/image/include/image/IRawImage.hpp | 2 +- src/libs/metadata/CMakeLists.txt | 2 +- src/libs/metadata/impl/AvFormatTagReader.cpp | 15 +- src/libs/metadata/impl/AvFormatTagReader.hpp | 8 +- src/libs/metadata/impl/ITagReader.hpp | 4 +- src/libs/metadata/impl/Parser.cpp | 60 +- src/libs/metadata/impl/Parser.hpp | 4 +- src/libs/metadata/impl/TagLibTagReader.cpp | 16 +- src/libs/metadata/impl/TagLibTagReader.hpp | 4 +- src/libs/metadata/impl/Utils.cpp | 12 +- src/libs/metadata/impl/Utils.hpp | 2 +- .../metadata/include/metadata/Exception.hpp | 6 +- .../metadata/include/metadata/IParser.hpp | 20 +- src/libs/metadata/test/Parser.cpp | 20 +- src/libs/metadata/test/TestTagReader.hpp | 2 +- src/libs/metadata/test/Utils.cpp | 239 +- src/libs/services/auth/CMakeLists.txt | 2 +- .../services/auth/impl/AuthServiceBase.cpp | 6 +- .../services/auth/impl/AuthServiceBase.hpp | 14 +- .../services/auth/impl/AuthTokenService.cpp | 32 +- .../services/auth/impl/AuthTokenService.hpp | 10 +- src/libs/services/auth/impl/EnvService.cpp | 4 +- .../services/auth/impl/LoginThrottler.cpp | 151 +- .../services/auth/impl/LoginThrottler.hpp | 6 +- .../auth/impl/PasswordServiceBase.cpp | 15 +- .../auth/impl/PasswordServiceBase.hpp | 10 +- .../http-headers/HttpHeadersEnvService.cpp | 18 +- .../http-headers/HttpHeadersEnvService.hpp | 6 +- .../impl/internal/InternalPasswordService.cpp | 32 +- .../impl/internal/InternalPasswordService.hpp | 8 +- .../auth/impl/pam/PAMPasswordService.cpp | 8 +- .../auth/impl/pam/PAMPasswordService.hpp | 4 +- .../services/auth/IAuthTokenService.hpp | 12 +- .../include/services/auth/IEnvService.hpp | 10 +- .../services/auth/IPasswordService.hpp | 10 +- .../auth/include/services/auth/Types.hpp | 14 +- src/libs/services/cover/CMakeLists.txt | 2 +- src/libs/services/cover/impl/CoverService.cpp | 78 +- src/libs/services/cover/impl/CoverService.hpp | 52 +- .../include/services/cover/ICoverService.hpp | 16 +- src/libs/services/feedback/CMakeLists.txt | 2 +- .../feedback/impl/FeedbackService.cpp | 12 +- .../feedback/impl/FeedbackService.hpp | 44 +- .../feedback/impl/FeedbackService.impl.hpp | 4 +- .../feedback/impl/IFeedbackBackend.hpp | 14 +- .../impl/internal/InternalBackend.cpp | 34 +- .../impl/internal/InternalBackend.hpp | 20 +- .../feedback/impl/listenbrainz/Exception.hpp | 6 +- .../impl/listenbrainz/FeedbackTypes.cpp | 4 +- .../impl/listenbrainz/FeedbackTypes.hpp | 8 +- .../impl/listenbrainz/FeedbacksParser.cpp | 6 +- .../impl/listenbrainz/FeedbacksParser.hpp | 4 +- .../listenbrainz/FeedbacksSynchronizer.cpp | 82 +- .../listenbrainz/FeedbacksSynchronizer.hpp | 36 +- .../impl/listenbrainz/ListenBrainzBackend.cpp | 44 +- .../impl/listenbrainz/ListenBrainzBackend.hpp | 25 +- .../feedback/impl/listenbrainz/Utils.cpp | 6 +- .../feedback/impl/listenbrainz/Utils.hpp | 10 +- .../include/services/feedback/Exception.hpp | 6 +- .../services/feedback/IFeedbackService.hpp | 60 +- .../impl/ClustersEngineCreator.hpp | 6 +- .../impl/FeaturesEngineCreator.hpp | 6 +- .../services/recommendation/impl/IEngine.hpp | 16 +- .../impl/PlaylistGeneratorService.cpp | 14 +- .../impl/PlaylistGeneratorService.hpp | 12 +- .../impl/RecommendationService.cpp | 24 +- .../impl/RecommendationService.hpp | 16 +- .../impl/clusters/ClustersEngine.cpp | 8 +- .../impl/clusters/ClustersEngine.hpp | 16 +- .../impl/features/FeaturesDefs.cpp | 8 +- .../impl/features/FeaturesDefs.hpp | 4 +- .../impl/features/FeaturesEngine.cpp | 62 +- .../impl/features/FeaturesEngine.hpp | 282 +- .../impl/features/FeaturesEngineCache.cpp | 50 +- .../impl/features/FeaturesEngineCache.hpp | 37 +- .../ConsecutiveArtists.cpp | 14 +- .../ConsecutiveArtists.hpp | 12 +- .../ConsecutiveReleases.cpp | 18 +- .../ConsecutiveReleases.hpp | 14 +- .../playlist-constraints/DuplicateTracks.cpp | 6 +- .../playlist-constraints/DuplicateTracks.hpp | 6 +- .../impl/playlist-constraints/IConstraint.hpp | 4 +- .../IPlaylistGeneratorService.hpp | 8 +- .../recommendation/IRecommendationService.hpp | 16 +- .../include/services/recommendation/Types.hpp | 10 +- src/libs/services/scanner/CMakeLists.txt | 2 +- src/libs/services/scanner/impl/IScanStep.hpp | 2 +- .../services/scanner/impl/ScanStepBase.hpp | 8 +- .../impl/ScanStepCheckDuplicatedDbFiles.cpp | 6 +- .../impl/ScanStepCheckDuplicatedDbFiles.hpp | 2 +- .../impl/ScanStepComputeClusterStats.cpp | 8 +- .../impl/ScanStepComputeClusterStats.hpp | 2 +- .../scanner/impl/ScanStepDiscoverFiles.cpp | 10 +- .../scanner/impl/ScanStepDiscoverFiles.hpp | 2 +- .../impl/ScanStepRemoveOrphanDbFiles.cpp | 22 +- .../impl/ScanStepRemoveOrphanDbFiles.hpp | 2 +- .../scanner/impl/ScanStepScanFiles.cpp | 76 +- .../scanner/impl/ScanStepScanFiles.hpp | 16 +- .../services/scanner/impl/ScannerService.cpp | 19 +- .../services/scanner/impl/ScannerService.hpp | 10 +- .../services/scanner/impl/ScannerSettings.hpp | 6 +- .../services/scanner/impl/ScannerStats.cpp | 4 +- .../services/scanner/IScannerService.hpp | 6 +- .../services/scanner/ScannerEvents.hpp | 2 +- .../include/services/scanner/ScannerStats.hpp | 6 +- src/libs/services/scrobbling/CMakeLists.txt | 2 +- .../scrobbling/impl/IScrobblingBackend.hpp | 4 +- .../scrobbling/impl/ScrobblingService.cpp | 56 +- .../scrobbling/impl/ScrobblingService.hpp | 18 +- .../impl/internal/InternalBackend.cpp | 16 +- .../impl/internal/InternalBackend.hpp | 8 +- .../impl/listenbrainz/Exception.hpp | 6 +- .../impl/listenbrainz/ListenBrainzBackend.cpp | 18 +- .../impl/listenbrainz/ListenBrainzBackend.hpp | 12 +- .../impl/listenbrainz/ListenTypes.cpp | 4 +- .../impl/listenbrainz/ListenTypes.hpp | 12 +- .../impl/listenbrainz/ListensParser.cpp | 12 +- .../impl/listenbrainz/ListensParser.hpp | 4 +- .../impl/listenbrainz/ListensSynchronizer.cpp | 106 +- .../impl/listenbrainz/ListensSynchronizer.hpp | 36 +- .../scrobbling/impl/listenbrainz/Utils.cpp | 6 +- .../scrobbling/impl/listenbrainz/Utils.hpp | 10 +- .../include/services/scrobbling/Exception.hpp | 6 +- .../scrobbling/IScrobblingService.hpp | 48 +- .../include/services/scrobbling/Listen.hpp | 6 +- .../services/scrobbling/test/CMakeLists.txt | 2 +- .../services/scrobbling/test/Listenbrainz.cpp | 200 +- .../services/scrobbling/test/Scrobbling.cpp | 9 +- src/libs/som/CMakeLists.txt | 2 +- src/libs/som/bench/SomBench.cpp | 49 +- src/libs/som/impl/DataNormalizer.cpp | 4 +- src/libs/som/impl/Network.cpp | 12 +- src/libs/som/include/som/DataNormalizer.hpp | 4 +- src/libs/som/include/som/InputVector.hpp | 6 +- src/libs/som/include/som/Matrix.hpp | 12 +- src/libs/som/include/som/Network.hpp | 4 +- src/libs/som/test/SomTest.cpp | 185 +- src/libs/subsonic/CMakeLists.txt | 2 +- src/libs/subsonic/impl/ClientInfo.hpp | 2 +- src/libs/subsonic/impl/ParameterParsing.cpp | 4 +- src/libs/subsonic/impl/ParameterParsing.hpp | 6 +- src/libs/subsonic/impl/ProtocolVersion.cpp | 14 +- src/libs/subsonic/impl/ProtocolVersion.hpp | 8 +- src/libs/subsonic/impl/RequestContext.hpp | 8 +- src/libs/subsonic/impl/SubsonicId.cpp | 62 +- src/libs/subsonic/impl/SubsonicId.hpp | 30 +- src/libs/subsonic/impl/SubsonicResource.cpp | 78 +- src/libs/subsonic/impl/SubsonicResource.hpp | 10 +- src/libs/subsonic/impl/SubsonicResponse.cpp | 8 +- src/libs/subsonic/impl/SubsonicResponse.hpp | 6 +- src/libs/subsonic/impl/Utils.cpp | 10 +- src/libs/subsonic/impl/Utils.hpp | 2 +- .../impl/entrypoints/AlbumSongLists.cpp | 22 +- .../impl/entrypoints/AlbumSongLists.hpp | 2 +- .../subsonic/impl/entrypoints/Bookmarks.cpp | 4 +- .../subsonic/impl/entrypoints/Bookmarks.hpp | 2 +- .../subsonic/impl/entrypoints/Browsing.cpp | 32 +- .../subsonic/impl/entrypoints/Browsing.hpp | 2 +- .../impl/entrypoints/MediaAnnotation.cpp | 24 +- .../impl/entrypoints/MediaAnnotation.hpp | 2 +- .../impl/entrypoints/MediaLibraryScanning.cpp | 10 +- .../impl/entrypoints/MediaLibraryScanning.hpp | 2 +- .../impl/entrypoints/MediaRetrieval.cpp | 96 +- .../impl/entrypoints/MediaRetrieval.hpp | 2 +- .../subsonic/impl/entrypoints/Playlists.cpp | 4 +- .../subsonic/impl/entrypoints/Playlists.hpp | 2 +- .../subsonic/impl/entrypoints/Searching.cpp | 8 +- .../subsonic/impl/entrypoints/Searching.hpp | 2 +- src/libs/subsonic/impl/entrypoints/System.cpp | 2 +- src/libs/subsonic/impl/entrypoints/System.hpp | 2 +- .../impl/entrypoints/UserManagement.cpp | 36 +- .../impl/entrypoints/UserManagement.hpp | 2 +- src/libs/subsonic/impl/responses/Album.cpp | 28 +- src/libs/subsonic/impl/responses/Album.hpp | 6 +- src/libs/subsonic/impl/responses/Artist.cpp | 22 +- src/libs/subsonic/impl/responses/Artist.hpp | 14 +- src/libs/subsonic/impl/responses/Bookmark.cpp | 4 +- src/libs/subsonic/impl/responses/Bookmark.hpp | 6 +- .../subsonic/impl/responses/Contributor.cpp | 6 +- .../subsonic/impl/responses/Contributor.hpp | 6 +- .../subsonic/impl/responses/DiscTitle.cpp | 4 +- .../subsonic/impl/responses/DiscTitle.hpp | 4 +- src/libs/subsonic/impl/responses/Genre.cpp | 4 +- src/libs/subsonic/impl/responses/Genre.hpp | 6 +- src/libs/subsonic/impl/responses/ItemDate.cpp | 2 +- src/libs/subsonic/impl/responses/ItemDate.hpp | 2 +- .../subsonic/impl/responses/ItemGenre.cpp | 2 +- .../subsonic/impl/responses/ItemGenre.hpp | 4 +- src/libs/subsonic/impl/responses/Playlist.cpp | 4 +- src/libs/subsonic/impl/responses/Playlist.hpp | 6 +- .../subsonic/impl/responses/ReplayGain.cpp | 4 +- .../subsonic/impl/responses/ReplayGain.hpp | 6 +- src/libs/subsonic/impl/responses/Song.cpp | 36 +- src/libs/subsonic/impl/responses/Song.hpp | 6 +- src/libs/subsonic/impl/responses/User.cpp | 4 +- src/libs/subsonic/impl/responses/User.hpp | 6 +- .../include/subsonic/SubsonicResource.hpp | 6 +- src/libs/utils/bench/CMakeLists.txt | 9 - src/libs/utils/bench/TraceLoggerBench.cpp | 53 - src/libs/utils/impl/ArchiveZipper.cpp | 309 -- src/libs/utils/impl/ArchiveZipper.hpp | 81 - src/libs/utils/impl/ChildProcess.cpp | 207 -- src/libs/utils/impl/Config.cpp | 134 - src/libs/utils/impl/Config.hpp | 49 - src/libs/utils/impl/FileResourceHandler.cpp | 136 - src/libs/utils/impl/IOContextRunner.cpp | 82 - src/libs/utils/impl/Logger.cpp | 79 - src/libs/utils/impl/RecursiveSharedMutex.cpp | 137 - src/libs/utils/impl/UUID.cpp | 89 - src/libs/utils/impl/WtLogger.cpp | 67 - src/libs/utils/impl/http/ClientRequest.hpp | 75 - src/libs/utils/impl/http/SendQueue.cpp | 249 -- src/libs/utils/impl/http/SendQueue.hpp | 79 - src/libs/utils/include/utils/EnumSet.hpp | 190 - src/libs/utils/include/utils/IConfig.hpp | 42 - src/libs/utils/include/utils/ILogger.hpp | 100 - .../utils/include/utils/LiteralString.hpp | 95 - src/libs/utils/include/utils/Random.hpp | 66 - src/libs/utils/include/utils/Service.hpp | 71 - src/libs/utils/include/utils/StreamLogger.hpp | 40 - src/libs/utils/test/EnumSet.cpp | 60 - src/libs/utils/test/LiteralString.cpp | 69 - src/libs/utils/test/Path.cpp | 111 - src/libs/utils/test/RecursiveSharedMutex.cpp | 106 - src/libs/utils/test/String.cpp | 295 -- src/lms/CMakeLists.txt | 2 +- src/lms/main.cpp | 386 +-- src/lms/ui/Auth.cpp | 349 +- src/lms/ui/Auth.hpp | 11 +- src/lms/ui/LmsApplication.cpp | 74 +- src/lms/ui/LmsApplication.hpp | 30 +- src/lms/ui/LmsApplicationException.hpp | 6 +- src/lms/ui/LmsApplicationManager.cpp | 2 +- src/lms/ui/LmsApplicationManager.hpp | 4 +- src/lms/ui/LmsTheme.cpp | 4 +- src/lms/ui/LmsTheme.hpp | 2 +- src/lms/ui/MediaPlayer.cpp | 40 +- src/lms/ui/MediaPlayer.hpp | 20 +- src/lms/ui/ModalManager.cpp | 4 +- src/lms/ui/ModalManager.hpp | 2 +- src/lms/ui/Notification.hpp | 2 +- src/lms/ui/NotificationContainer.cpp | 4 +- src/lms/ui/NotificationContainer.hpp | 4 +- src/lms/ui/PlayQueue.cpp | 152 +- src/lms/ui/PlayQueue.hpp | 32 +- src/lms/ui/SettingsView.cpp | 26 +- src/lms/ui/SettingsView.hpp | 4 +- src/lms/ui/Utils.cpp | 45 +- src/lms/ui/Utils.hpp | 30 +- src/lms/ui/admin/InitWizardView.cpp | 22 +- src/lms/ui/admin/InitWizardView.hpp | 4 +- src/lms/ui/admin/MediaLibrariesView.cpp | 28 +- src/lms/ui/admin/MediaLibrariesView.hpp | 6 +- src/lms/ui/admin/MediaLibraryModal.cpp | 20 +- src/lms/ui/admin/MediaLibraryModal.hpp | 8 +- src/lms/ui/admin/ScanSettingsView.cpp | 20 +- src/lms/ui/admin/ScanSettingsView.hpp | 4 +- src/lms/ui/admin/ScannerController.cpp | 56 +- src/lms/ui/admin/ScannerController.hpp | 4 +- src/lms/ui/admin/TracingView.cpp | 16 +- src/lms/ui/admin/TracingView.hpp | 4 +- src/lms/ui/admin/UserView.cpp | 28 +- src/lms/ui/admin/UserView.hpp | 4 +- src/lms/ui/admin/UsersView.cpp | 12 +- src/lms/ui/admin/UsersView.hpp | 4 +- src/lms/ui/common/DoubleValidator.cpp | 4 +- src/lms/ui/common/DoubleValidator.hpp | 4 +- .../ui/common/InfiniteScrollingContainer.cpp | 2 +- .../ui/common/InfiniteScrollingContainer.hpp | 2 +- src/lms/ui/common/LoadingIndicator.cpp | 2 +- src/lms/ui/common/LoadingIndicator.hpp | 2 +- src/lms/ui/common/LoginNameValidator.cpp | 34 +- src/lms/ui/common/LoginNameValidator.hpp | 4 +- src/lms/ui/common/MandatoryValidator.cpp | 4 +- src/lms/ui/common/MandatoryValidator.hpp | 4 +- src/lms/ui/common/PasswordValidator.cpp | 134 +- src/lms/ui/common/PasswordValidator.hpp | 6 +- src/lms/ui/common/Template.cpp | 8 +- src/lms/ui/common/Template.hpp | 4 +- src/lms/ui/common/UUIDValidator.cpp | 4 +- src/lms/ui/common/UUIDValidator.hpp | 4 +- src/lms/ui/common/UppercaseValidator.cpp | 4 +- src/lms/ui/common/UppercaseValidator.hpp | 4 +- src/lms/ui/common/ValueStringModel.hpp | 4 +- src/lms/ui/explore/ArtistCollector.cpp | 20 +- src/lms/ui/explore/ArtistCollector.hpp | 14 +- src/lms/ui/explore/ArtistListHelpers.cpp | 12 +- src/lms/ui/explore/ArtistListHelpers.hpp | 8 +- src/lms/ui/explore/ArtistView.cpp | 50 +- src/lms/ui/explore/ArtistView.hpp | 16 +- src/lms/ui/explore/ArtistsView.cpp | 168 +- src/lms/ui/explore/ArtistsView.hpp | 6 +- src/lms/ui/explore/DatabaseCollectorBase.cpp | 10 +- src/lms/ui/explore/DatabaseCollectorBase.hpp | 4 +- src/lms/ui/explore/Explore.cpp | 4 +- src/lms/ui/explore/Explore.hpp | 4 +- src/lms/ui/explore/Filters.cpp | 8 +- src/lms/ui/explore/Filters.hpp | 10 +- src/lms/ui/explore/PlayQueueController.cpp | 76 +- src/lms/ui/explore/PlayQueueController.hpp | 14 +- src/lms/ui/explore/ReleaseCollector.cpp | 18 +- src/lms/ui/explore/ReleaseCollector.hpp | 10 +- src/lms/ui/explore/ReleaseHelpers.cpp | 24 +- src/lms/ui/explore/ReleaseHelpers.hpp | 14 +- src/lms/ui/explore/ReleaseTypes.cpp | 56 +- src/lms/ui/explore/ReleaseTypes.hpp | 8 +- src/lms/ui/explore/ReleaseView.cpp | 82 +- src/lms/ui/explore/ReleaseView.hpp | 16 +- src/lms/ui/explore/ReleasesView.cpp | 11 +- src/lms/ui/explore/ReleasesView.hpp | 6 +- src/lms/ui/explore/SearchView.cpp | 19 +- src/lms/ui/explore/SearchView.hpp | 6 +- src/lms/ui/explore/TrackCollector.cpp | 16 +- src/lms/ui/explore/TrackCollector.hpp | 10 +- src/lms/ui/explore/TrackListHelpers.cpp | 50 +- src/lms/ui/explore/TrackListHelpers.hpp | 12 +- src/lms/ui/explore/TrackListView.cpp | 30 +- src/lms/ui/explore/TrackListView.hpp | 8 +- src/lms/ui/explore/TrackListsView.cpp | 178 +- src/lms/ui/explore/TrackListsView.hpp | 12 +- src/lms/ui/explore/TracksView.cpp | 12 +- src/lms/ui/explore/TracksView.hpp | 6 +- src/lms/ui/resource/AudioFileResource.cpp | 20 +- src/lms/ui/resource/AudioFileResource.hpp | 6 +- .../ui/resource/AudioTranscodingResource.cpp | 62 +- .../ui/resource/AudioTranscodingResource.hpp | 8 +- src/lms/ui/resource/CoverResource.cpp | 36 +- src/lms/ui/resource/CoverResource.hpp | 8 +- src/lms/ui/resource/DownloadResource.cpp | 78 +- src/lms/ui/resource/DownloadResource.hpp | 34 +- src/tools/cover/LmsCover.cpp | 47 +- src/tools/db-generator/CMakeLists.txt | 2 +- src/tools/db-generator/LmsDbGenerator.cpp | 107 +- src/tools/metadata/CMakeLists.txt | 2 +- src/tools/metadata/LmsMetadata.cpp | 28 +- .../recommendation/LmsRecommendation.cpp | 144 +- .../GeneticAlgorithm.hpp | 174 +- .../LmsSimilarityParameters.cpp | 678 ++-- 501 files changed, 12605 insertions(+), 12631 deletions(-) rename src/libs/{utils => core}/CMakeLists.txt (72%) create mode 100644 src/libs/core/bench/CMakeLists.txt create mode 100644 src/libs/core/bench/TraceLoggerBench.cpp create mode 100644 src/libs/core/impl/ArchiveZipper.cpp create mode 100644 src/libs/core/impl/ArchiveZipper.hpp create mode 100644 src/libs/core/impl/ChildProcess.cpp rename src/libs/{utils => core}/impl/ChildProcess.hpp (51%) rename src/libs/{utils => core}/impl/ChildProcessManager.cpp (56%) rename src/libs/{utils => core}/impl/ChildProcessManager.hpp (52%) create mode 100644 src/libs/core/impl/Config.cpp create mode 100644 src/libs/core/impl/Config.hpp create mode 100644 src/libs/core/impl/FileResourceHandler.cpp rename src/libs/{utils => core}/impl/FileResourceHandler.hpp (54%) create mode 100644 src/libs/core/impl/IOContextRunner.cpp create mode 100644 src/libs/core/impl/Logger.cpp rename src/libs/{utils => core}/impl/NetAddress.cpp (97%) rename src/libs/{utils => core}/impl/Path.cpp (94%) rename src/libs/{utils => core}/impl/Random.cpp (66%) create mode 100644 src/libs/core/impl/RecursiveSharedMutex.cpp rename src/libs/{utils => core}/impl/StreamLogger.cpp (59%) rename src/libs/{utils => core}/impl/String.cpp (99%) rename src/libs/{utils => core}/impl/TraceLogger.cpp (98%) rename src/libs/{utils => core}/impl/TraceLogger.hpp (97%) create mode 100644 src/libs/core/impl/UUID.cpp create mode 100644 src/libs/core/impl/WtLogger.cpp rename src/libs/{utils => core}/impl/http/Client.cpp (55%) rename src/libs/{utils => core}/impl/http/Client.hpp (63%) create mode 100644 src/libs/core/impl/http/ClientRequest.hpp create mode 100644 src/libs/core/impl/http/SendQueue.cpp create mode 100644 src/libs/core/impl/http/SendQueue.hpp rename src/libs/{utils/include/utils => core/include/core}/Crc32Calculator.hpp (65%) create mode 100644 src/libs/core/include/core/EnumSet.hpp rename src/libs/{utils/include/utils => core/include/core}/Exception.hpp (77%) rename src/libs/{utils/include/utils => core/include/core}/FileResourceHandlerCreator.hpp (81%) rename src/libs/{utils/include/utils => core/include/core}/IChildProcess.hpp (52%) rename src/libs/{utils/include/utils => core/include/core}/IChildProcessManager.hpp (68%) create mode 100644 src/libs/core/include/core/IConfig.hpp create mode 100644 src/libs/core/include/core/ILogger.hpp rename src/libs/{utils/include/utils => core/include/core}/IOContextRunner.hpp (57%) rename src/libs/{utils/include/utils => core/include/core}/IResourceHandler.hpp (86%) rename src/libs/{utils/include/utils => core/include/core}/ITraceLogger.hpp (91%) rename src/libs/{utils/include/utils => core/include/core}/IZipper.hpp (56%) create mode 100644 src/libs/core/include/core/LiteralString.hpp rename src/libs/{utils/include/utils => core/include/core}/NetAddress.hpp (100%) rename src/libs/{utils/include/utils => core/include/core}/Path.hpp (94%) create mode 100644 src/libs/core/include/core/Random.hpp rename src/libs/{utils/include/utils => core/include/core}/RecursiveSharedMutex.hpp (61%) create mode 100644 src/libs/core/include/core/Service.hpp create mode 100644 src/libs/core/include/core/StreamLogger.hpp rename src/libs/{utils/include/utils => core/include/core}/String.hpp (99%) rename src/libs/{utils/include/utils => core/include/core}/Tuple.hpp (92%) rename src/libs/{utils/include/utils => core/include/core}/UUID.hpp (66%) rename src/libs/{utils/include/utils => core/include/core}/Utils.hpp (97%) rename src/libs/{utils/include/utils => core/include/core}/WtLogger.hpp (64%) rename src/libs/{utils/include/utils => core/include/core}/ZipperResourceHandlerCreator.hpp (79%) rename src/libs/{utils/include/utils => core/include/core}/http/ClientRequestParameters.hpp (51%) rename src/libs/{utils/include/utils => core/include/core}/http/IClient.hpp (76%) rename src/libs/{utils => core}/test/CMakeLists.txt (65%) create mode 100644 src/libs/core/test/EnumSet.cpp create mode 100644 src/libs/core/test/LiteralString.cpp create mode 100644 src/libs/core/test/Path.cpp create mode 100644 src/libs/core/test/RecursiveSharedMutex.cpp create mode 100644 src/libs/core/test/String.cpp rename src/libs/{utils => core}/test/TraceLogger.cpp (95%) rename src/libs/{utils => core}/test/Utils.cpp (100%) delete mode 100644 src/libs/utils/bench/CMakeLists.txt delete mode 100644 src/libs/utils/bench/TraceLoggerBench.cpp delete mode 100644 src/libs/utils/impl/ArchiveZipper.cpp delete mode 100644 src/libs/utils/impl/ArchiveZipper.hpp delete mode 100644 src/libs/utils/impl/ChildProcess.cpp delete mode 100644 src/libs/utils/impl/Config.cpp delete mode 100644 src/libs/utils/impl/Config.hpp delete mode 100644 src/libs/utils/impl/FileResourceHandler.cpp delete mode 100644 src/libs/utils/impl/IOContextRunner.cpp delete mode 100644 src/libs/utils/impl/Logger.cpp delete mode 100644 src/libs/utils/impl/RecursiveSharedMutex.cpp delete mode 100644 src/libs/utils/impl/UUID.cpp delete mode 100644 src/libs/utils/impl/WtLogger.cpp delete mode 100644 src/libs/utils/impl/http/ClientRequest.hpp delete mode 100644 src/libs/utils/impl/http/SendQueue.cpp delete mode 100644 src/libs/utils/impl/http/SendQueue.hpp delete mode 100644 src/libs/utils/include/utils/EnumSet.hpp delete mode 100644 src/libs/utils/include/utils/IConfig.hpp delete mode 100644 src/libs/utils/include/utils/ILogger.hpp delete mode 100644 src/libs/utils/include/utils/LiteralString.hpp delete mode 100644 src/libs/utils/include/utils/Random.hpp delete mode 100644 src/libs/utils/include/utils/Service.hpp delete mode 100644 src/libs/utils/include/utils/StreamLogger.hpp delete mode 100644 src/libs/utils/test/EnumSet.cpp delete mode 100644 src/libs/utils/test/LiteralString.cpp delete mode 100644 src/libs/utils/test/Path.cpp delete mode 100644 src/libs/utils/test/RecursiveSharedMutex.cpp delete mode 100644 src/libs/utils/test/String.cpp diff --git a/src/libs/CMakeLists.txt b/src/libs/CMakeLists.txt index 72f9ecf1..72a280ca 100644 --- a/src/libs/CMakeLists.txt +++ b/src/libs/CMakeLists.txt @@ -1,8 +1,8 @@ add_subdirectory(av) +add_subdirectory(core) add_subdirectory(database) add_subdirectory(image) add_subdirectory(metadata) add_subdirectory(services) add_subdirectory(som) add_subdirectory(subsonic) -add_subdirectory(utils) diff --git a/src/libs/av/CMakeLists.txt b/src/libs/av/CMakeLists.txt index fee1201e..e4023c96 100644 --- a/src/libs/av/CMakeLists.txt +++ b/src/libs/av/CMakeLists.txt @@ -18,7 +18,7 @@ target_include_directories(lmsav PRIVATE ) target_link_libraries(lmsav PUBLIC - lmsutils + lmscore std::filesystem ) diff --git a/src/libs/av/impl/AudioFile.cpp b/src/libs/av/impl/AudioFile.cpp index e739c57b..9184e6b6 100644 --- a/src/libs/av/impl/AudioFile.cpp +++ b/src/libs/av/impl/AudioFile.cpp @@ -31,10 +31,10 @@ extern "C" #include #include -#include "utils/ILogger.hpp" -#include "utils/String.hpp" +#include "core/ILogger.hpp" +#include "core/String.hpp" -namespace Av +namespace lms::av { namespace { @@ -48,11 +48,11 @@ namespace Av return "Unknown error"; } - class AudioFileException : public Av::Exception + class AudioFileException : public Exception { public: AudioFileException(int avError) - : Av::Exception{ "AudioFileException: " + averror_to_string(avError) } + : Exception{ "AudioFileException: " + averror_to_string(avError) } {} }; @@ -64,7 +64,7 @@ namespace Av AVDictionaryEntry* tag = NULL; while ((tag = ::av_dict_get(dictionnary, "", tag, AV_DICT_IGNORE_SUFFIX))) { - res[StringUtils::stringToUpper(tag->key)] = tag->value; + res[core::stringUtils::stringToUpper(tag->key)] = tag->value; } } @@ -320,10 +320,10 @@ namespace Av {".mka", "audio/x-matroska"}, }; - auto it{ entries.find(StringUtils::stringToLower(fileExtension.string())) }; + auto it{ entries.find(core::stringUtils::stringToLower(fileExtension.string())) }; if (it == std::cend(entries)) return ""; return it->second; } -} // namespace Av +} // namespace lms::av diff --git a/src/libs/av/impl/AudioFile.hpp b/src/libs/av/impl/AudioFile.hpp index 46e8f830..838e6302 100644 --- a/src/libs/av/impl/AudioFile.hpp +++ b/src/libs/av/impl/AudioFile.hpp @@ -25,7 +25,7 @@ struct AVFormatContext; -namespace Av +namespace lms::av { class AudioFile final : public IAudioFile @@ -53,5 +53,5 @@ namespace Av AVFormatContext* _context{}; }; -} // namespace Av +} // namespace lms::av diff --git a/src/libs/av/impl/RawResourceHandlerCreator.cpp b/src/libs/av/impl/RawResourceHandlerCreator.cpp index 04ea6f88..5d446743 100644 --- a/src/libs/av/impl/RawResourceHandlerCreator.cpp +++ b/src/libs/av/impl/RawResourceHandlerCreator.cpp @@ -20,13 +20,13 @@ #include "av/RawResourceHandlerCreator.hpp" #include "av/IAudioFile.hpp" -#include "utils/FileResourceHandlerCreator.hpp" +#include "core/FileResourceHandlerCreator.hpp" -namespace Av +namespace lms::av { std::unique_ptr createRawResourceHandler(const std::filesystem::path& path) { - std::string_view mimeType{ Av::getMimeType(path.extension()) }; + std::string_view mimeType{ getMimeType(path.extension()) }; return createFileResourceHandler(path, mimeType.empty() ? "application/octet-stream" : mimeType); } } diff --git a/src/libs/av/impl/Transcoder.cpp b/src/libs/av/impl/Transcoder.cpp index 074675bc..663c94b2 100644 --- a/src/libs/av/impl/Transcoder.cpp +++ b/src/libs/av/impl/Transcoder.cpp @@ -22,13 +22,13 @@ #include #include -#include "utils/IChildProcessManager.hpp" -#include "utils/IConfig.hpp" -#include "utils/Path.hpp" -#include "utils/ILogger.hpp" -#include "utils/Service.hpp" +#include "core/IChildProcessManager.hpp" +#include "core/IConfig.hpp" +#include "core/Path.hpp" +#include "core/ILogger.hpp" +#include "core/Service.hpp" -namespace Av::Transcoding +namespace lms::av::transcoding { #define LOG(severity, message) LMS_LOG(TRANSCODING, severity, "[" << _debugId << "] - " << message) @@ -52,7 +52,7 @@ namespace Av::Transcoding void Transcoder::init() { - ffmpegPath = Service::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg"); + ffmpegPath = core::Service::get()->getPath("ffmpeg-file", "/usr/bin/ffmpeg"); if (!std::filesystem::exists(ffmpegPath)) throw Exception{ "File '" + ffmpegPath.string() + "' does not exist!" }; } @@ -183,9 +183,9 @@ namespace Av::Transcoding // Caution: stdin must have been closed before try { - _childProcess = Service::get()->spawnChildProcess(ffmpegPath, args); + _childProcess = core::Service::get()->spawnChildProcess(ffmpegPath, args); } - catch (ChildProcessException& exception) + catch (core::ChildProcessException& exception) { throw Exception{ "Cannot execute '" + ffmpegPath.string() + "': " + exception.what() }; } @@ -195,7 +195,7 @@ namespace Av::Transcoding { assert(_childProcess); - return _childProcess->asyncRead(buffer, bufferSize, [readCallback{ std::move(readCallback) }](IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead) + return _childProcess->asyncRead(buffer, bufferSize, [readCallback{ std::move(readCallback) }](core::IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead) { readCallback(nbBytesRead); }); @@ -215,4 +215,4 @@ namespace Av::Transcoding return _childProcess->finished(); } -} // namespace Av::Transcoding +} // namespace lms::av::Transcoding diff --git a/src/libs/av/impl/Transcoder.hpp b/src/libs/av/impl/Transcoder.hpp index 0bd3e1d2..febc37d5 100644 --- a/src/libs/av/impl/Transcoder.hpp +++ b/src/libs/av/impl/Transcoder.hpp @@ -25,9 +25,12 @@ #include "av/TranscodingParameters.hpp" #include "av/Types.hpp" -class IChildProcess; +namespace lms::core +{ + class IChildProcess; +} -namespace Av::Transcoding +namespace lms::av::transcoding { class Transcoder { @@ -60,7 +63,6 @@ namespace Av::Transcoding const OutputParameters _outputParameters; std::string _outputMimeType; - std::unique_ptr _childProcess; + std::unique_ptr _childProcess; }; - -} // namespace Av::Transcoding +} \ No newline at end of file diff --git a/src/libs/av/impl/TranscodingResourceHandler.cpp b/src/libs/av/impl/TranscodingResourceHandler.cpp index 8de8f705..d8882103 100644 --- a/src/libs/av/impl/TranscodingResourceHandler.cpp +++ b/src/libs/av/impl/TranscodingResourceHandler.cpp @@ -18,9 +18,9 @@ */ #include "TranscodingResourceHandler.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" -namespace Av::Transcoding +namespace lms::av::transcoding { namespace { diff --git a/src/libs/av/impl/TranscodingResourceHandler.hpp b/src/libs/av/impl/TranscodingResourceHandler.hpp index 6821d42a..8da15af5 100644 --- a/src/libs/av/impl/TranscodingResourceHandler.hpp +++ b/src/libs/av/impl/TranscodingResourceHandler.hpp @@ -24,10 +24,10 @@ #include #include "av/TranscodingParameters.hpp" -#include "utils/IResourceHandler.hpp" +#include "core/IResourceHandler.hpp" #include "Transcoder.hpp" -namespace Av::Transcoding +namespace lms::av::transcoding { class TranscodingResourceHandler final : public IResourceHandler { diff --git a/src/libs/av/include/av/IAudioFile.hpp b/src/libs/av/include/av/IAudioFile.hpp index c4368b78..3dc06edb 100644 --- a/src/libs/av/include/av/IAudioFile.hpp +++ b/src/libs/av/include/av/IAudioFile.hpp @@ -32,7 +32,7 @@ #include "Types.hpp" -namespace Av +namespace lms::av { // List should be sync with the codecs shipped in the lms's docker version enum class DecodingCodec @@ -107,5 +107,5 @@ namespace Av std::string_view getMimeType(const std::filesystem::path& fileExtension); -} // namespace Av +} // namespace lms::av diff --git a/src/libs/av/include/av/RawResourceHandlerCreator.hpp b/src/libs/av/include/av/RawResourceHandlerCreator.hpp index ed7b0e14..e2282de2 100644 --- a/src/libs/av/include/av/RawResourceHandlerCreator.hpp +++ b/src/libs/av/include/av/RawResourceHandlerCreator.hpp @@ -22,9 +22,9 @@ #include #include -#include "utils/IResourceHandler.hpp" +#include "core/IResourceHandler.hpp" -namespace Av +namespace lms::av { std::unique_ptr createRawResourceHandler(const std::filesystem::path& path); } \ No newline at end of file diff --git a/src/libs/av/include/av/TranscodingParameters.hpp b/src/libs/av/include/av/TranscodingParameters.hpp index 13d8f961..2fd7e358 100644 --- a/src/libs/av/include/av/TranscodingParameters.hpp +++ b/src/libs/av/include/av/TranscodingParameters.hpp @@ -25,7 +25,7 @@ #include "Types.hpp" -namespace Av::Transcoding +namespace lms::av::transcoding { struct InputParameters { @@ -52,5 +52,5 @@ namespace Av::Transcoding std::chrono::milliseconds offset{ 0 }; bool stripMetadata{ true }; }; -} // namespace Av::Transcoding +} // namespace lms::av::Transcoding diff --git a/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp b/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp index 16e0fcbf..35c13ed7 100644 --- a/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp +++ b/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp @@ -21,9 +21,9 @@ #include -#include "utils/IResourceHandler.hpp" +#include "core/IResourceHandler.hpp" -namespace Av::Transcoding +namespace lms::av::transcoding { struct InputParameters; struct OutputParameters; diff --git a/src/libs/av/include/av/Types.hpp b/src/libs/av/include/av/Types.hpp index 249529e3..3de4d3fe 100644 --- a/src/libs/av/include/av/Types.hpp +++ b/src/libs/av/include/av/Types.hpp @@ -19,11 +19,11 @@ #pragma once -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -namespace Av +namespace lms::av { - class Exception : public LmsException + class Exception : public core::LmsException { public: using LmsException::LmsException; diff --git a/src/libs/utils/CMakeLists.txt b/src/libs/core/CMakeLists.txt similarity index 72% rename from src/libs/utils/CMakeLists.txt rename to src/libs/core/CMakeLists.txt index 7d00aaa1..869357a8 100644 --- a/src/libs/utils/CMakeLists.txt +++ b/src/libs/core/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(lmsutils SHARED +add_library(lmscore SHARED impl/http/Client.cpp impl/http/SendQueue.cpp impl/ArchiveZipper.cpp @@ -19,26 +19,26 @@ add_library(lmsutils SHARED impl/WtLogger.cpp ) -target_include_directories(lmsutils INTERFACE +target_include_directories(lmscore INTERFACE include ) -target_include_directories(lmsutils PRIVATE +target_include_directories(lmscore PRIVATE include ) -target_link_libraries(lmsutils PRIVATE +target_link_libraries(lmscore PRIVATE PkgConfig::Config++ PkgConfig::Archive ) -target_link_libraries(lmsutils PUBLIC +target_link_libraries(lmscore PUBLIC Boost::system std::filesystem Wt::Wt ) -install(TARGETS lmsutils DESTINATION lib) +install(TARGETS lmscore DESTINATION lib) if(BUILD_TESTING) add_subdirectory(test) diff --git a/src/libs/core/bench/CMakeLists.txt b/src/libs/core/bench/CMakeLists.txt new file mode 100644 index 00000000..9b2a3aa6 --- /dev/null +++ b/src/libs/core/bench/CMakeLists.txt @@ -0,0 +1,9 @@ + +add_executable(bench-core + TraceLoggerBench.cpp + ) + +target_link_libraries(bench-core PRIVATE + lmscore + benchmark + ) diff --git a/src/libs/core/bench/TraceLoggerBench.cpp b/src/libs/core/bench/TraceLoggerBench.cpp new file mode 100644 index 00000000..7c037c0e --- /dev/null +++ b/src/libs/core/bench/TraceLoggerBench.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2024 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 . + */ + +#include +#include +#include + +#include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" +#include "core/StreamLogger.hpp" + +namespace lms::core +{ + // The trace logger is meant to built/destroyed once + Service logger{ std::make_unique(std::cout, logging::StreamLogger::allSeverities) }; + Service traceLogger{ tracing::createTraceLogger(tracing::Level::Overview) }; + + static void BM_TraceLogger_Overview(benchmark::State& state) + { + for (auto _ : state) + { + LMS_SCOPED_TRACE_OVERVIEW("Cat", "Test"); + } + } + + static void BM_TraceLogger_Detailed(benchmark::State& state) + { + for (auto _ : state) + { + // Should do nothing + LMS_SCOPED_TRACE_DETAILED("Cat", "Test"); + } + } + + BENCHMARK(BM_TraceLogger_Overview)->Threads(1)->Threads(std::thread::hardware_concurrency()); + BENCHMARK(BM_TraceLogger_Detailed)->Threads(1)->Threads(std::thread::hardware_concurrency()); +} + +BENCHMARK_MAIN(); \ No newline at end of file diff --git a/src/libs/core/impl/ArchiveZipper.cpp b/src/libs/core/impl/ArchiveZipper.cpp new file mode 100644 index 00000000..91102262 --- /dev/null +++ b/src/libs/core/impl/ArchiveZipper.cpp @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2020 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 . + */ + +#include "ArchiveZipper.hpp" + +#include +#include +#include // strerror +#include +#include +#include + +#include "core/ILogger.hpp" + +namespace lms::zip +{ + std::unique_ptr createArchiveZipper(const EntryContainer& entries) + { + return std::make_unique(entries); + } + + class FileException : public Exception + { + public: + FileException(const std::filesystem::path& p, std::string_view message) + : Exception{ "File '" + p.string() + "': " + std::string {message} } + {} + + FileException(const std::filesystem::path& p, std::string_view message, int err) + : Exception{ "File '" + p.string() + "': " + std::string {message} + ": " + ::strerror(err) } + {} + }; + + class ArchiveException : public Exception + { + public: + ArchiveException(struct ::archive* arch) + : Exception{ getError(arch) } + {} + + static std::string_view getError(struct ::archive* arch) + { + const char* str{ archive_error_string(arch) }; + if (!str) + { + static std::string unknownError{ "Unknown archive error" }; + return unknownError; + } + + return str; + } + }; + + void ArchiveZipper::ArchiveDeleter::operator()(struct ::archive* arch) + { + const int res{ ::archive_write_free(arch) }; + if (res != ARCHIVE_OK) + LMS_LOG(UTILS, ERROR, "Failure while freeing archive control struct: " << std::string{ ::strerror(res) }); + } + + void ArchiveZipper::ArchiveEntryDeleter::operator()(struct ::archive_entry* archEntry) + { + ::archive_entry_free(archEntry); + } + + ArchiveZipper::ArchiveZipper(const EntryContainer& entries) + : _entries{ entries } + , _readBuffer(_readBufferSize, {}) + , _currentEntry{ std::cbegin(_entries) } + { + _archive = ArchivePtr{ ::archive_write_new() }; + if (!_archive) + throw Exception{ "Cannot create archive control struct" }; + + auto archiveOpen{ [](struct ::archive*, void*) + { + return ARCHIVE_OK; + } }; + + auto archiveWrite{ [](struct ::archive*, void* clientData, const void* buff, ::size_t n) -> la_ssize_t + { + ArchiveZipper* zipper {static_cast(clientData)}; + return zipper->onWriteCallback(static_cast(buff), n); + } }; + + auto archiveClose{ [](struct ::archive*, void*) + { + return ARCHIVE_OK; + } }; + + if (::archive_write_set_bytes_per_block(_archive.get(), _writeBlockSize) != ARCHIVE_OK) + throw ArchiveException{ _archive.get() }; + + // 1 => no padding for last block + if (::archive_write_set_bytes_in_last_block(_archive.get(), 1) != ARCHIVE_OK) + throw ArchiveException{ _archive.get() }; + + if (::archive_write_set_format_zip(_archive.get()) != ARCHIVE_OK) + throw ArchiveException{ _archive.get() }; + + if (::archive_write_set_option(_archive.get(), "zip", "compression", "deflate") != ARCHIVE_OK) + throw ArchiveException{ _archive.get() }; + + int res{ ::archive_write_open(_archive.get(), this, archiveOpen, archiveWrite, archiveClose) }; + if (res != ARCHIVE_OK) + throw ArchiveException{ _archive.get() }; + } + + std::uint64_t ArchiveZipper::writeSome(std::ostream& output) + { + assert(!_currentOutputStream); + + _currentOutputStream = &output; + _bytesWrittenInCurrentOutputStream = 0; + + while (_bytesWrittenInCurrentOutputStream == 0) + { + if (!_currentArchiveEntry) + { + if (_currentEntry == std::cend(_entries)) + { + if (::archive_write_close(_archive.get()) != ARCHIVE_OK) + throw ArchiveException{ _archive.get() }; + + _archive.reset(); + break; + } + + _currentArchiveEntry = createArchiveEntry(*_currentEntry); + _currentEntryOffset = 0; + if (::archive_write_header(_archive.get(), _currentArchiveEntry.get()) != ARCHIVE_OK) + throw ArchiveException{ _archive.get() }; + } + + if (writeSomeCurrentFileData()) + { + // entry complete + if (::archive_write_finish_entry(_archive.get()) != ARCHIVE_OK) + throw ArchiveException{ _archive.get() }; + + _currentArchiveEntry.reset(); + _currentEntry++; + } + } + + _currentOutputStream = nullptr; + return _bytesWrittenInCurrentOutputStream; + } + + bool ArchiveZipper::isComplete() const + { + return !_archive; + } + + void ArchiveZipper::abort() + { + LMS_LOG(UTILS, DEBUG, "Aborting zip creation"); + if (_archive) + { + ::archive_write_fail(_archive.get()); + _archive.reset(); + } + } + + static ::mode_t permsToMode(const std::filesystem::perms p) + { + using std::filesystem::perms; + ::mode_t mode{}; + + auto testPerm{ [](perms p, perms permToTest) + { + return (p & permToTest) == permToTest; + } }; + + if (testPerm(p, perms::owner_read)) + mode |= S_IRUSR; + if (testPerm(p, perms::owner_write)) + mode |= S_IWUSR; + if (testPerm(p, perms::owner_exec)) + mode |= S_IXUSR; + if (testPerm(p, perms::group_read)) + mode |= S_IRGRP; + if (testPerm(p, perms::group_write)) + mode |= S_IWGRP; + if (testPerm(p, perms::group_exec)) + mode |= S_IXGRP; + if (testPerm(p, perms::others_read)) + mode |= S_IROTH; + if (testPerm(p, perms::others_write)) + mode |= S_IWOTH; + if (testPerm(p, perms::others_exec)) + mode |= S_IXOTH; + + return mode; + } + + ArchiveZipper::ArchiveEntryPtr ArchiveZipper::createArchiveEntry(const Entry& entry) + { + try + { + if (!std::filesystem::is_regular_file(entry.filePath)) + throw FileException{ entry.filePath, "not a regular file" }; + + ArchiveEntryPtr archiveEntry{ archive_entry_new() }; + if (!archiveEntry) + throw Exception{ "Cannot create archive entry control struct" }; + + archive_entry_set_pathname(archiveEntry.get(), entry.fileName.c_str()); + archive_entry_set_size(archiveEntry.get(), std::filesystem::file_size(entry.filePath)); + archive_entry_set_mode(archiveEntry.get(), permsToMode(std::filesystem::status(entry.filePath).permissions())); + archive_entry_set_filetype(archiveEntry.get(), AE_IFREG); + + return archiveEntry; + } + catch (const std::filesystem::filesystem_error& error) + { + throw FileException{ entry.filePath, error.what() }; + } + } + + bool ArchiveZipper::writeSomeCurrentFileData() + { + assert(_currentEntry != std::cend(_entries)); + + std::ifstream ifs{ _currentEntry->filePath.c_str(), std::ios_base::binary }; + if (!ifs) + throw FileException{ _currentEntry->filePath, "cannot open file", errno }; + + ifs.seekg(0, std::ios::end); + const std::uint64_t fileSize{ static_cast(ifs.tellg()) }; + ifs.seekg(0, std::ios::beg); + + // TODO: store file size? + if (fileSize < _currentEntryOffset) + throw FileException{ _currentEntry->filePath, "size changed?" }; + + const std::uint64_t bytesToRead{ std::min(fileSize - _currentEntryOffset, static_cast(_readBufferSize)) }; + + // read from file + if (!ifs.seekg(_currentEntryOffset, std::ios::beg)) + throw FileException{ _currentEntry->filePath, "seek failed", errno }; + + if (!ifs.read(reinterpret_cast(&_readBuffer[0]), bytesToRead)) + throw FileException{ _currentEntry->filePath, "read failed", errno }; + + const std::uint64_t actualBytesRead{ static_cast(ifs.gcount()) }; + + // write to archive + { + std::uint64_t remainingBytesToWrite{ actualBytesRead }; + while (remainingBytesToWrite > 0) + { + const auto writtenBytes{ archive_write_data(_archive.get(), &_readBuffer[actualBytesRead - remainingBytesToWrite], remainingBytesToWrite) }; + if (writtenBytes < 0) + throw ArchiveException{ _archive.get() }; + + assert(static_cast(writtenBytes) <= remainingBytesToWrite); + remainingBytesToWrite -= writtenBytes; + } + } + + _currentEntryOffset += actualBytesRead; + return (_currentEntryOffset >= fileSize); + } + + std::int64_t ArchiveZipper::onWriteCallback(const std::byte* buffer, std::size_t bufferSize) + { + if (!_currentOutputStream) + { + archive_set_error(_archive.get(), EIO, "IO error: operation cancelled"); + return -1; + } + + _currentOutputStream->write(reinterpret_cast(buffer), bufferSize); + if (!*_currentOutputStream) + throw Exception{ "Failed to write " + std::to_string(bufferSize) + " bytes in final archive output!" }; + + _bytesWrittenInCurrentOutputStream += bufferSize; + + return bufferSize; + } +} diff --git a/src/libs/core/impl/ArchiveZipper.hpp b/src/libs/core/impl/ArchiveZipper.hpp new file mode 100644 index 00000000..43fead7e --- /dev/null +++ b/src/libs/core/impl/ArchiveZipper.hpp @@ -0,0 +1,80 @@ +/* + * 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 . + */ + +#pragma once + +#include +#include +#include "core/IZipper.hpp" + +extern "C" +{ + struct archive; + struct archive_entry; +} + +namespace lms::zip +{ + class ArchiveZipper : public IZipper + { + public: + ArchiveZipper(const EntryContainer& files); + ArchiveZipper(const ArchiveZipper&) = delete; + ArchiveZipper& operator=(const ArchiveZipper&) = delete; + + private: + std::uint64_t writeSome(std::ostream& output) override; + bool isComplete() const override; + void abort() override; + + class ArchiveDeleter + { + public: + void operator()(struct ::archive* arch); + }; + using ArchivePtr = std::unique_ptr; + + class ArchiveEntryDeleter + { + public: + void operator()(struct ::archive_entry* archEntry); + }; + using ArchiveEntryPtr = std::unique_ptr; + + void prepareCurrentEntry(); + static ArchiveEntryPtr createArchiveEntry(const Entry& entry); + bool writeSomeCurrentFileData(); + std::int64_t onWriteCallback(const std::byte* buff, std::size_t size); + + const EntryContainer _entries; + ArchivePtr _archive; + + static inline constexpr std::size_t _writeBlockSize{ 65536 }; + static inline constexpr std::size_t _readBufferSize{ 65536 }; + std::vector _readBuffer; + + EntryContainer::const_iterator _currentEntry; + ArchiveEntryPtr _currentArchiveEntry; + + std::uint64_t _currentEntryOffset{}; + std::ostream* _currentOutputStream{}; + std::uint64_t _bytesWrittenInCurrentOutputStream{}; + }; + +} \ No newline at end of file diff --git a/src/libs/core/impl/ChildProcess.cpp b/src/libs/core/impl/ChildProcess.cpp new file mode 100644 index 00000000..3c9cd158 --- /dev/null +++ b/src/libs/core/impl/ChildProcess.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2020 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 . + */ + +#include "ChildProcess.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include "core/Exception.hpp" +#include "core/ILogger.hpp" + +namespace lms::core +{ + namespace + { + class SystemException : public ChildProcessException + { + public: + SystemException(int err, const std::string& errMsg) + : ChildProcessException{ errMsg + ": " + ::strerror(err) } + {} + + SystemException(boost::system::error_code ec, const std::string& errMsg) + : ChildProcessException{ errMsg + ": " + ec.message() } + {} + }; + } + + ChildProcess::ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args) + : _ioContext{ ioContext } + , _childStdout{ _ioContext } + + { + // make sure only one thread is executing this part of code + static std::mutex mutex; + std::unique_lock lock{ mutex }; + + int pipe[2]; + + int res{ pipe2(pipe, O_NONBLOCK | O_CLOEXEC) }; + if (res < 0) + throw SystemException{ errno, "pipe2 failed!" }; + + { +#if defined(__linux__) && defined(F_SETPIPE_SZ) + // Just a hint here to prevent the writer from writing too many bytes ahead of the reader + constexpr std::size_t pipeSize{ 65536 * 4 }; + + if (fcntl(pipe[0], F_SETPIPE_SZ, pipeSize) == -1) + throw SystemException{ errno, "fcntl failed!" }; + if (fcntl(pipe[1], F_SETPIPE_SZ, pipeSize) == -1) + throw SystemException{ errno, "fcntl failed!" }; +#endif + } + + res = fork(); + if (res == -1) + throw SystemException{ errno, "fork failed!" }; + + if (res == 0) // CHILD + { + close(pipe[0]); + close(STDIN_FILENO); + close(STDERR_FILENO); + + // Replace stdout with pipe write + if (dup2(pipe[1], STDOUT_FILENO) == -1) + exit(-1); + + std::vector execArgs; + std::transform(std::cbegin(args), std::cend(args), std::back_inserter(execArgs), [](const std::string& arg) { return arg.c_str(); }); + execArgs.push_back(nullptr); + + res = execv(path.string().c_str(), (char* const*)&execArgs[0]); + if (res == -1) + exit(-1); + } + else // PARENT + { + close(pipe[1]); + { + boost::system::error_code assignError; + _childStdout.assign(pipe[0], assignError); + if (assignError) + throw SystemException{ assignError, "fork failed!" }; + } + _childPID = res; + } + } + + ChildProcess::~ChildProcess() + { + LMS_LOG(CHILDPROCESS, DEBUG, "Closing child process..."); + { + boost::system::error_code closeError; + _childStdout.close(closeError); + if (closeError) + LMS_LOG(CHILDPROCESS, ERROR, "Closed failed: " << closeError.message()); + } + + if (!_finished) + kill(); + + wait(true); + } + + void ChildProcess::kill() + { + // process may already have finished + LMS_LOG(CHILDPROCESS, DEBUG, "Killing child process..."); + if (::kill(_childPID, SIGKILL) == -1) + LMS_LOG(CHILDPROCESS, DEBUG, "Kill failed: " << ::strerror(errno)); + } + + bool ChildProcess::wait(bool block) + { + assert(!_waited); + + int wstatus{}; + const pid_t pid{ waitpid(_childPID, &wstatus, block ? 0 : WNOHANG) }; + + if (pid == -1) + throw SystemException{ errno, "waitpid failed!" }; + else if (pid == 0) + return false; + + if (WIFEXITED(wstatus)) + { + _exitCode = WEXITSTATUS(wstatus); + LMS_LOG(CHILDPROCESS, DEBUG, "Exit code = " << *_exitCode); + } + + _waited = true; + return true; + } + + void ChildProcess::asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) + { + assert(!finished()); + + LMS_LOG(CHILDPROCESS, DEBUG, "Async read, bufferSize = " << bufferSize); + + boost::asio::async_read(_childStdout, boost::asio::buffer(data, bufferSize), + [this, callback{ std::move(callback) }](const boost::system::error_code& error, std::size_t bytesTransferred) + { + LMS_LOG(CHILDPROCESS, DEBUG, "Async read cb - ec = '" << error.message() << "' (" << error.value() << "), bytesTransferred = " << bytesTransferred); + + ReadResult readResult{ ReadResult::Success }; + if (error) + { + if (error != boost::asio::error::eof) + { + // forbidden to read any captured param here as the ChildProcess instance may already have been killed + return; + } + + readResult = ReadResult::EndOfFile; + _finished = true; + } + + callback(readResult, bytesTransferred); + }); + } + + std::size_t ChildProcess::readSome(std::byte* data, std::size_t bufferSize) + { + boost::system::error_code ec; + const std::size_t res{ _childStdout.read_some(boost::asio::buffer(data, bufferSize), ec) }; + LMS_LOG(CHILDPROCESS, DEBUG, "read some " << res << " bytes, ec = " << ec.message()); + if (ec) + _childStdout.close(ec); + + return res; + } + + bool ChildProcess::finished() const + { + return _finished; + } +} \ No newline at end of file diff --git a/src/libs/utils/impl/ChildProcess.hpp b/src/libs/core/impl/ChildProcess.hpp similarity index 51% rename from src/libs/utils/impl/ChildProcess.hpp rename to src/libs/core/impl/ChildProcess.hpp index 8b5752dc..9f5aea09 100644 --- a/src/libs/utils/impl/ChildProcess.hpp +++ b/src/libs/core/impl/ChildProcess.hpp @@ -29,29 +29,31 @@ #include #include -#include "utils/IChildProcess.hpp" +#include "core/IChildProcess.hpp" - -class ChildProcess : public IChildProcess +namespace lms::core { - public: - ~ChildProcess(); - ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args); + class ChildProcess : public IChildProcess + { + public: + ~ChildProcess(); + ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args); - private: - void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) override; - std::size_t readSome(std::byte* data, std::size_t bufferSize) override; - bool finished() const override; + private: + void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) override; + std::size_t readSome(std::byte* data, std::size_t bufferSize) override; + bool finished() const override; - void kill(); - bool wait(bool block); // return true if waited + void kill(); + bool wait(bool block); // return true if waited - using FileDescriptor = boost::asio::posix::stream_descriptor; + using FileDescriptor = boost::asio::posix::stream_descriptor; - boost::asio::io_context& _ioContext; - FileDescriptor _childStdout; - ::pid_t _childPID {}; - bool _waited {}; - bool _finished {}; - std::optional _exitCode; -}; + boost::asio::io_context& _ioContext; + FileDescriptor _childStdout; + ::pid_t _childPID{}; + bool _waited{}; + bool _finished{}; + std::optional _exitCode; + }; +} \ No newline at end of file diff --git a/src/libs/utils/impl/ChildProcessManager.cpp b/src/libs/core/impl/ChildProcessManager.cpp similarity index 56% rename from src/libs/utils/impl/ChildProcessManager.cpp rename to src/libs/core/impl/ChildProcessManager.cpp index 4d58c49f..abe6de1e 100644 --- a/src/libs/utils/impl/ChildProcessManager.cpp +++ b/src/libs/core/impl/ChildProcessManager.cpp @@ -19,26 +19,26 @@ #include "ChildProcessManager.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "ChildProcess.hpp" - -std::unique_ptr -createChildProcessManager(boost::asio::io_context& ioContext) +namespace lms::core { - return std::make_unique(ioContext); -} - -ChildProcessManager::ChildProcessManager(boost::asio::io_context& ioContext) -: _ioContext {ioContext} -{ -} - -std::unique_ptr -ChildProcessManager::spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args) -{ - return std::make_unique(_ioContext, path, args); -} + std::unique_ptr + createChildProcessManager(boost::asio::io_context& ioContext) + { + return std::make_unique(ioContext); + } + ChildProcessManager::ChildProcessManager(boost::asio::io_context& ioContext) + : _ioContext{ ioContext } + { + } + std::unique_ptr + ChildProcessManager::spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args) + { + return std::make_unique(_ioContext, path, args); + } +} \ No newline at end of file diff --git a/src/libs/utils/impl/ChildProcessManager.hpp b/src/libs/core/impl/ChildProcessManager.hpp similarity index 52% rename from src/libs/utils/impl/ChildProcessManager.hpp rename to src/libs/core/impl/ChildProcessManager.hpp index 79cf3943..39a56998 100644 --- a/src/libs/utils/impl/ChildProcessManager.hpp +++ b/src/libs/core/impl/ChildProcessManager.hpp @@ -24,23 +24,24 @@ #include -#include "utils/IChildProcessManager.hpp" +#include "core/IChildProcessManager.hpp" -class ChildProcessManager : public IChildProcessManager +namespace lms::core { - public: - ChildProcessManager(boost::asio::io_context& ioContext); - ~ChildProcessManager() = default; + class ChildProcessManager : public IChildProcessManager + { + public: + ChildProcessManager(boost::asio::io_context& ioContext); + ~ChildProcessManager() = default; - ChildProcessManager(const ChildProcessManager&) = delete; - ChildProcessManager(ChildProcessManager&&) = delete; - ChildProcessManager& operator=(const ChildProcessManager&) = delete; - ChildProcessManager& operator=(ChildProcessManager&&) = delete; - - private: - std::unique_ptr spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args) override; - - boost::asio::io_context& _ioContext; -}; + ChildProcessManager(const ChildProcessManager&) = delete; + ChildProcessManager(ChildProcessManager&&) = delete; + ChildProcessManager& operator=(const ChildProcessManager&) = delete; + ChildProcessManager& operator=(ChildProcessManager&&) = delete; + private: + std::unique_ptr spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args) override; + boost::asio::io_context& _ioContext; + }; +} \ No newline at end of file diff --git a/src/libs/core/impl/Config.cpp b/src/libs/core/impl/Config.cpp new file mode 100644 index 00000000..334f634a --- /dev/null +++ b/src/libs/core/impl/Config.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2016 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 . + */ + +#include "Config.hpp" + +#include "core/Exception.hpp" +#include "core/ILogger.hpp" + +namespace lms::core +{ + std::unique_ptr createConfig(const std::filesystem::path& p) + { + return std::make_unique(p); + } + + Config::Config(const std::filesystem::path& p) + { + try + { + _config.readFile(p.string().c_str()); + } + catch (libconfig::FileIOException& e) + { + throw LmsException{ "Cannot open config file '" + p.string() + "'" }; + } + catch (libconfig::ParseException& e) + { + throw LmsException{ "Cannot parse config file '" + p.string() + "', line = " + std::to_string(e.getLine()) + ", error = '" + e.getError() + "'" }; + } + catch (libconfig::ConfigException& e) + { + throw LmsException{ "Cannot open config file '" + p.string() + "': " + e.what() }; + } + } + + std::string_view Config::getString(std::string_view setting, std::string_view def) + { + try + { + return static_cast(_config.lookup(std::string{ setting })); + } + catch (libconfig::ConfigException&) + { + return def; + } + } + + void Config::visitStrings(std::string_view setting, std::function _func, std::initializer_list defs) + { + try + { + const libconfig::Setting& values{ _config.lookup(std::string {setting}) }; + for (int i{}; i < values.getLength(); ++i) + _func(static_cast(values[i])); + } + catch (const libconfig::SettingNotFoundException&) + { + for (std::string_view def : defs) + _func(def); + } + catch (libconfig::ConfigException&) + { + } + } + + std::filesystem::path Config::getPath(std::string_view setting, const std::filesystem::path& path) + { + try + { + const char* res{ _config.lookup(std::string {setting}) }; + return std::filesystem::path{ std::string(res) }; + } + catch (libconfig::ConfigException&) + { + return path; + } + } + + unsigned long Config::getULong(std::string_view setting, unsigned long def) + { + try + { + return static_cast(_config.lookup(std::string{ setting })); + } + catch (libconfig::ConfigException&) + { + return def; + } + } + + long Config::getLong(std::string_view setting, long def) + { + try + { + return _config.lookup(std::string{ setting }); + } + catch (libconfig::ConfigException&) + { + return def; + } + } + + bool Config::getBool(std::string_view setting, bool def) + { + try + { + return _config.lookup(std::string{ setting }); + } + catch (libconfig::ConfigException&) + { + return def; + } + } +} \ No newline at end of file diff --git a/src/libs/core/impl/Config.hpp b/src/libs/core/impl/Config.hpp new file mode 100644 index 00000000..a267d47b --- /dev/null +++ b/src/libs/core/impl/Config.hpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 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 . + */ +#pragma once + +#include "core/IConfig.hpp" + +#include + +namespace lms::core +{ + // Used to get config values from configuration files + class Config final : public IConfig + { + public: + Config(const std::filesystem::path& p); + ~Config() = default; + + Config(const Config&) = delete; + Config& operator=(const Config&) = delete; + Config(Config&&) = delete; + Config& operator=(Config&&) = delete; + + // Default values are returned in case of setting not found + std::string_view getString(std::string_view setting, std::string_view def = "") override; + void visitStrings(std::string_view setting, std::function _func, std::initializer_list defs) override; + std::filesystem::path getPath(std::string_view setting, const std::filesystem::path& def = std::filesystem::path()) override; + unsigned long getULong(std::string_view setting, unsigned long def = 0) override; + long getLong(std::string_view setting, long def = 0) override; + bool getBool(std::string_view setting, bool def = false) override; + + private: + + libconfig::Config _config; + }; +} \ No newline at end of file diff --git a/src/libs/core/impl/FileResourceHandler.cpp b/src/libs/core/impl/FileResourceHandler.cpp new file mode 100644 index 00000000..5a5ef8b8 --- /dev/null +++ b/src/libs/core/impl/FileResourceHandler.cpp @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 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 . + */ + +#include "FileResourceHandler.hpp" + +#include + +#include "core/ILogger.hpp" + +namespace lms +{ + std::unique_ptr createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType) + { + return std::make_unique(path, mimeType); + } + + FileResourceHandler::FileResourceHandler(const std::filesystem::path& path, std::string_view mimeType) + : _path{ path } + , _mimeType{ mimeType } + { + } + + Wt::Http::ResponseContinuation* FileResourceHandler::processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) + { + ::uint64_t startByte{ _offset }; + std::ifstream ifs{ _path.string().c_str(), std::ios::in | std::ios::binary }; + + if (startByte == 0) + { + if (!ifs) + { + LMS_LOG(UTILS, ERROR, "Cannot open file stream for '" << _path.string() << "'"); + response.setStatus(404); + return {}; + } + + ifs.seekg(0, std::ios::end); + const ::uint64_t fileSize{ static_cast<::uint64_t>(ifs.tellg()) }; + ifs.seekg(0, std::ios::beg); + + LMS_LOG(UTILS, DEBUG, "File '" << _path.string() << "', fileSize = " << fileSize); + + response.addHeader("Accept-Ranges", "bytes"); + + const Wt::Http::Request::ByteRangeSpecifier ranges{ request.getRanges(fileSize) }; + if (!ranges.isSatisfiable()) + { + std::ostringstream contentRange; + contentRange << "bytes */" << fileSize; + response.setStatus(416); // Requested range not satisfiable + response.addHeader("Content-Range", contentRange.str()); + + LMS_LOG(UTILS, DEBUG, "Range not satisfiable"); + return {}; + } + + if (ranges.size() == 1) + { + LMS_LOG(UTILS, DEBUG, "Range requested = " << ranges[0].firstByte() << "-" << ranges[0].lastByte()); + + response.setStatus(206); + startByte = ranges[0].firstByte(); + _beyondLastByte = ranges[0].lastByte() + 1; + + std::ostringstream contentRange; + contentRange << "bytes " << startByte << "-" + << _beyondLastByte - 1 << "/" << fileSize; + + response.addHeader("Content-Range", contentRange.str()); + response.setContentLength(_beyondLastByte - startByte); + } + else + { + LMS_LOG(UTILS, DEBUG, "No range requested"); + + response.setStatus(200); + _beyondLastByte = fileSize; + response.setContentLength(_beyondLastByte); + } + + LMS_LOG(UTILS, DEBUG, "Mimetype set to '" << _mimeType << "'"); + response.setMimeType(_mimeType); + } + else if (!ifs) + { + LMS_LOG(UTILS, ERROR, "Cannot reopen file stream for '" << _path.string() << "'"); + return {}; + } + + ifs.seekg(static_cast(startByte)); + + std::vector buf; + buf.resize(_chunkSize); + + ::uint64_t restSize = _beyondLastByte - startByte; + ::uint64_t pieceSize = buf.size() > restSize ? restSize : buf.size(); + + ifs.read(&buf[0], pieceSize); + const ::uint64_t actualPieceSize{ static_cast<::uint64_t>(ifs.gcount()) }; + if (actualPieceSize > 0) + { + response.out().write(&buf[0], actualPieceSize); + LMS_LOG(UTILS, DEBUG, "Written " << actualPieceSize << " bytes, range = " << startByte << "-" << startByte + actualPieceSize - 1 << ""); + } + else + { + LMS_LOG(UTILS, DEBUG, "Written 0 byte"); + } + + if (ifs.good() && actualPieceSize < restSize) + { + _offset = startByte + actualPieceSize; + LMS_LOG(UTILS, DEBUG, "Job not complete! Remaining range: " << _offset << "-" << _beyondLastByte - 1); + + return response.createContinuation(); + } + + LMS_LOG(UTILS, DEBUG, "Job complete!"); + return nullptr; + } +} \ No newline at end of file diff --git a/src/libs/utils/impl/FileResourceHandler.hpp b/src/libs/core/impl/FileResourceHandler.hpp similarity index 54% rename from src/libs/utils/impl/FileResourceHandler.hpp rename to src/libs/core/impl/FileResourceHandler.hpp index a6eab694..5c239277 100644 --- a/src/libs/utils/impl/FileResourceHandler.hpp +++ b/src/libs/core/impl/FileResourceHandler.hpp @@ -22,22 +22,24 @@ #include #include #include -#include "utils/IResourceHandler.hpp" +#include "core/IResourceHandler.hpp" -class FileResourceHandler final : public IResourceHandler +namespace lms { -public: - FileResourceHandler(const std::filesystem::path& filePath, std::string_view mimeType); + class FileResourceHandler final : public IResourceHandler + { + public: + FileResourceHandler(const std::filesystem::path& filePath, std::string_view mimeType); -private: - Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override; - void abort() override {}; + private: + Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override; + void abort() override {}; - static constexpr std::size_t _chunkSize{ 262'144 }; - - std::filesystem::path _path; - std::string _mimeType; - ::uint64_t _beyondLastByte{}; - ::uint64_t _offset{}; -}; + static constexpr std::size_t _chunkSize{ 262'144 }; + std::filesystem::path _path; + std::string _mimeType; + ::uint64_t _beyondLastByte{}; + ::uint64_t _offset{}; + }; +} \ No newline at end of file diff --git a/src/libs/core/impl/IOContextRunner.cpp b/src/libs/core/impl/IOContextRunner.cpp new file mode 100644 index 00000000..c78fcd80 --- /dev/null +++ b/src/libs/core/impl/IOContextRunner.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2021 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 . + */ + +#include "core/IOContextRunner.hpp" + +#include + +#include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" + +namespace lms::core +{ + IOContextRunner::IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name) + : _ioService{ ioService } + , _work{ ioService } + { + LMS_LOG(UTILS, INFO, "Starting IO context with " << threadCount << " threads..."); + + for (std::size_t i{}; i < threadCount; ++i) + { + std::string threadName{ name }; + if (!threadName.empty()) + { + threadName += "Thread_"; + threadName += std::to_string(i); + } + + _threads.emplace_back([this, threadName] + { + if (!threadName.empty()) + { + if (auto * traceLogger{ Service::get() }) + traceLogger->setThreadName(std::this_thread::get_id(), threadName); + } + + try + { + _ioService.run(); + } + catch (const std::exception& e) + { + LMS_LOG(UTILS, FATAL, "Exception caught in IO context: " << e.what()); + std::abort(); + } + }); + } + } + + void IOContextRunner::stop() + { + LMS_LOG(UTILS, DEBUG, "Stopping IO context..."); + _work.reset(); + _ioService.stop(); + LMS_LOG(UTILS, DEBUG, "IO context stopped!"); + } + + std::size_t IOContextRunner::getThreadCount() const + { + return _threads.size(); + } + + IOContextRunner::~IOContextRunner() + { + stop(); + + for (std::thread& t : _threads) + t.join(); + } +} \ No newline at end of file diff --git a/src/libs/core/impl/Logger.cpp b/src/libs/core/impl/Logger.cpp new file mode 100644 index 00000000..a859e730 --- /dev/null +++ b/src/libs/core/impl/Logger.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2013 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 . + */ + +#include "core/ILogger.hpp" + +namespace lms::core::logging +{ + const char* getModuleName(Module mod) + { + switch (mod) + { + case Module::API_SUBSONIC: return "API_SUBSONIC"; + case Module::AUTH: return "AUTH"; + case Module::AV: return "AV"; + case Module::CHILDPROCESS: return "CHILDPROC"; + case Module::COVER: return "COVER"; + case Module::DB: return "DB"; + case Module::DBUPDATER: return "DB UPDATER"; + case Module::FEATURE: return "FEATURE"; + case Module::FEEDBACK: return "FEEDBACK"; + case Module::HTTP: return "HTTP"; + case Module::MAIN: return "MAIN"; + case Module::METADATA: return "METADATA"; + case Module::REMOTE: return "REMOTE"; + case Module::SCROBBLING: return "SCROBBLING"; + case Module::SERVICE: return "SERVICE"; + case Module::RECOMMENDATION: return "RECOMMENDATION"; + case Module::TRANSCODING: return "TRANSCODING"; + case Module::UI: return "UI"; + case Module::UTILS: return "UTILS"; + } + return ""; + } + + const char* getSeverityName(Severity sev) + { + switch (sev) + { + case Severity::FATAL: return "fatal"; + case Severity::ERROR: return "error"; + case Severity::WARNING: return "warning"; + case Severity::INFO: return "info"; + case Severity::DEBUG: return "debug"; + } + return ""; + } + + Log::Log(ILogger& logger, Module module, Severity severity) + : _logger{ logger } + , _module{ module } + , _severity{ severity } + + {} + + Log::~Log() + { + assert(_logger.isSeverityActive(_severity)); + _logger.processLog(*this); + } + + std::string Log::getMessage() const + { + return _oss.str(); + } +} \ No newline at end of file diff --git a/src/libs/utils/impl/NetAddress.cpp b/src/libs/core/impl/NetAddress.cpp similarity index 97% rename from src/libs/utils/impl/NetAddress.cpp rename to src/libs/core/impl/NetAddress.cpp index b41b07ac..7b8bc427 100644 --- a/src/libs/utils/impl/NetAddress.cpp +++ b/src/libs/core/impl/NetAddress.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "utils/NetAddress.hpp" +#include "core/NetAddress.hpp" #ifndef BOOST_ASIO_HAS_STD_HASH diff --git a/src/libs/utils/impl/Path.cpp b/src/libs/core/impl/Path.cpp similarity index 94% rename from src/libs/utils/impl/Path.cpp rename to src/libs/core/impl/Path.cpp index bc3de353..5e55bf72 100644 --- a/src/libs/utils/impl/Path.cpp +++ b/src/libs/core/impl/Path.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "utils/Path.hpp" +#include "core/Path.hpp" #include #include @@ -28,16 +28,16 @@ #include -#include "utils/Crc32Calculator.hpp" -#include "utils/Exception.hpp" -#include "utils/ILogger.hpp" -#include "utils/String.hpp" +#include "core/Crc32Calculator.hpp" +#include "core/Exception.hpp" +#include "core/ILogger.hpp" +#include "core/String.hpp" -namespace PathUtils +namespace lms::core::pathUtils { std::uint32_t computeCrc32(const std::filesystem::path& p) { - Utils::Crc32Calculator crc32; + core::Crc32Calculator crc32; std::ifstream ifs{ p.string().c_str(), std::ios_base::binary }; if (ifs) @@ -134,7 +134,7 @@ namespace PathUtils bool hasFileAnyExtension(const std::filesystem::path& file, const std::vector& supportedExtensions) { - const std::filesystem::path extension{ StringUtils::stringToLower(file.extension().string()) }; + const std::filesystem::path extension{ stringUtils::stringToLower(file.extension().string()) }; return (std::find(std::cbegin(supportedExtensions), std::cend(supportedExtensions), extension) != std::cend(supportedExtensions)); } @@ -157,10 +157,10 @@ namespace PathUtils if (curPath == rootPath) return true; - + if (curPath == curPath.root_path()) break; - + curPath = curPath.parent_path(); } @@ -183,4 +183,4 @@ namespace PathUtils return longestCommonPath; } -} // ns PathUtils +} diff --git a/src/libs/utils/impl/Random.cpp b/src/libs/core/impl/Random.cpp similarity index 66% rename from src/libs/utils/impl/Random.cpp rename to src/libs/core/impl/Random.cpp index 12ef2863..18b65462 100644 --- a/src/libs/utils/impl/Random.cpp +++ b/src/libs/core/impl/Random.cpp @@ -17,22 +17,22 @@ * along with LMS. If not, see . */ -#include "utils/Random.hpp" +#include "core/Random.hpp" -namespace Random { - -RandGenerator& getRandGenerator() +namespace lms::core::random { - static thread_local std::random_device rd; - static thread_local RandGenerator randGenerator(rd()); - return randGenerator; -} + RandGenerator& getRandGenerator() + { + static thread_local std::random_device rd; + static thread_local RandGenerator randGenerator(rd()); -RandGenerator createSeededGenerator(uint_fast32_t seed) -{ - return RandGenerator {seed}; -} + return randGenerator; + } -} // Random + RandGenerator createSeededGenerator(uint_fast32_t seed) + { + return RandGenerator{ seed }; + } +} \ No newline at end of file diff --git a/src/libs/core/impl/RecursiveSharedMutex.cpp b/src/libs/core/impl/RecursiveSharedMutex.cpp new file mode 100644 index 00000000..74ff9bab --- /dev/null +++ b/src/libs/core/impl/RecursiveSharedMutex.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2021 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 . + */ + +#include "core/RecursiveSharedMutex.hpp" + +#include + +namespace lms::core +{ + void RecursiveSharedMutex::lock() + { + const auto thisThreadId{ std::this_thread::get_id() }; + + if (_uniqueOwner == thisThreadId) + { + // already locked + _uniqueCount++; + } + else + { + _mutex.lock(); + _uniqueOwner = thisThreadId; + assert(_uniqueCount == 0); + _uniqueCount = 1; + } + } + + void RecursiveSharedMutex::unlock() + { + assert(_uniqueCount > 0); + + if (--_uniqueCount == 0) + { + _uniqueOwner = {}; + _mutex.unlock(); + } + } + + void RecursiveSharedMutex::lock_shared() + { + const auto thisThreadId{ std::this_thread::get_id() }; + + if (_uniqueOwner == thisThreadId) + { + // alone here, no need to lock + _sharedCounts[thisThreadId]++; + + return; + } + + bool needLock{}; + + { + std::scoped_lock lock{ _sharedCountMutex }; + + auto& sharedCount{ _sharedCounts[thisThreadId] }; + if (sharedCount == 0) + needLock = true; + else + ++sharedCount; + } + + if (needLock) + { + _mutex.lock_shared(); + + assert(_uniqueOwner == std::thread::id{}); + + std::scoped_lock lock{ _sharedCountMutex }; + _sharedCounts[thisThreadId]++; + } + } + + void RecursiveSharedMutex::unlock_shared() + { + const auto thisThreadId{ std::this_thread::get_id() }; + + if (_uniqueOwner == thisThreadId) + { + // alone here, no need to lock + auto& sharedCount{ _sharedCounts[thisThreadId] }; + assert(sharedCount > 0); + --sharedCount; + + return; + } + + bool needUnlock{}; + + { + std::scoped_lock lock{ _sharedCountMutex }; + + auto& sharedCount{ _sharedCounts[thisThreadId] }; + assert(sharedCount > 0); + needUnlock = (--sharedCount == 0); + } + + if (needUnlock) + _mutex.unlock_shared(); + } + +#ifndef NDEBUG + bool RecursiveSharedMutex::isUniqueLocked() + { + return _uniqueOwner == std::this_thread::get_id(); + } + + bool RecursiveSharedMutex::isSharedLocked() + { + const auto thisThreadId{ std::this_thread::get_id() }; + if (_uniqueOwner == thisThreadId) + return true; + + std::scoped_lock lock{ _sharedCountMutex }; + return _sharedCounts[thisThreadId] > 0; + } +#endif +} \ No newline at end of file diff --git a/src/libs/utils/impl/StreamLogger.cpp b/src/libs/core/impl/StreamLogger.cpp similarity index 59% rename from src/libs/utils/impl/StreamLogger.cpp rename to src/libs/core/impl/StreamLogger.cpp index 4bbca70b..4e6e26d5 100644 --- a/src/libs/utils/impl/StreamLogger.cpp +++ b/src/libs/core/impl/StreamLogger.cpp @@ -20,17 +20,19 @@ #include #include -#include "utils/StreamLogger.hpp" +#include "core/StreamLogger.hpp" -StreamLogger::StreamLogger(std::ostream& os, EnumSet severities) - : _os{ os } - , _severities{ severities } +namespace lms::core::logging { -} - -void StreamLogger::processLog(const Log& log) -{ - assert(isSeverityActive(log.getSeverity())); - _os << std::this_thread::get_id() << " [" << getSeverityName(log.getSeverity()) << "] [" << getModuleName(log.getModule()) << "] " << log.getMessage() << std::endl; -} + StreamLogger::StreamLogger(std::ostream& os, EnumSet severities) + : _os{ os } + , _severities{ severities } + { + } + void StreamLogger::processLog(const Log& log) + { + assert(isSeverityActive(log.getSeverity())); + _os << std::this_thread::get_id() << " [" << getSeverityName(log.getSeverity()) << "] [" << getModuleName(log.getModule()) << "] " << log.getMessage() << std::endl; + } +} \ No newline at end of file diff --git a/src/libs/utils/impl/String.cpp b/src/libs/core/impl/String.cpp similarity index 99% rename from src/libs/utils/impl/String.cpp rename to src/libs/core/impl/String.cpp index f938b8fb..35fb876e 100644 --- a/src/libs/utils/impl/String.cpp +++ b/src/libs/core/impl/String.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "utils/String.hpp" +#include "core/String.hpp" #include #include @@ -26,9 +26,8 @@ #include #include -namespace StringUtils +namespace lms::core::stringUtils { - namespace details { constexpr std::pair jsEscapeChars[] @@ -437,5 +436,4 @@ namespace StringUtils // assume UTC return date.toString("yyyy-MM-dd").toUTF8(); } -} // StringUtils - +} diff --git a/src/libs/utils/impl/TraceLogger.cpp b/src/libs/core/impl/TraceLogger.cpp similarity index 98% rename from src/libs/utils/impl/TraceLogger.cpp rename to src/libs/core/impl/TraceLogger.cpp index 4cde78c5..9c891e54 100644 --- a/src/libs/utils/impl/TraceLogger.cpp +++ b/src/libs/core/impl/TraceLogger.cpp @@ -22,10 +22,10 @@ #include #include #include -#include "utils/Exception.hpp" -#include "utils/ILogger.hpp" +#include "core/Exception.hpp" +#include "core/ILogger.hpp" -namespace tracing +namespace lms::core::tracing { namespace { diff --git a/src/libs/utils/impl/TraceLogger.hpp b/src/libs/core/impl/TraceLogger.hpp similarity index 97% rename from src/libs/utils/impl/TraceLogger.hpp rename to src/libs/core/impl/TraceLogger.hpp index c786f282..9addf082 100644 --- a/src/libs/utils/impl/TraceLogger.hpp +++ b/src/libs/core/impl/TraceLogger.hpp @@ -26,9 +26,9 @@ #include #include -#include "utils/ITraceLogger.hpp" +#include "core/ITraceLogger.hpp" -namespace tracing +namespace lms::core::tracing { class TraceLogger : public ITraceLogger { diff --git a/src/libs/core/impl/UUID.cpp b/src/libs/core/impl/UUID.cpp new file mode 100644 index 00000000..6573b44a --- /dev/null +++ b/src/libs/core/impl/UUID.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2020 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 . + */ + +#include "core/UUID.hpp" + +#include +#include +#include +#include + +#include "core/Random.hpp" +#include "core/String.hpp" + +namespace lms::core +{ + namespace stringUtils + { + template <> + std::optional + readAs(std::string_view str) + { + return UUID::fromString(str); + } + } + namespace + { + bool stringIsUUID(std::string_view str) + { + static const std::regex re{ R"([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})" }; + + return std::regex_match(std::cbegin(str), std::cend(str), re); + } + } + + UUID::UUID(std::string_view str) + : _value{ stringUtils::stringToLower(str) } + { + } + + std::optional UUID::fromString(std::string_view str) + { + if (!stringIsUUID(str)) + return std::nullopt; + + return UUID{ str }; + } + + UUID UUID::generate() + { + // Form is "123e4567-e89b-12d3-a456-426614174000" + // TODO: store 128 bits and only convert to string when necessary + + std::ostringstream oss; + + auto concatRandomBytes{ [](std::ostream& os, std::size_t byteCount) + { + for (std::size_t i {}; i < byteCount; ++i) + os << std::hex << std::setfill('0') << std::setw(2) << static_cast(random::getRandom(0, 255)); + } }; + + concatRandomBytes(oss, 4); + oss << "-"; + concatRandomBytes(oss, 2); + oss << "-"; + concatRandomBytes(oss, 2); + oss << "-"; + concatRandomBytes(oss, 2); + oss << "-"; + concatRandomBytes(oss, 6); + + const auto uuid{ fromString(oss.str()) }; + assert(uuid); + return uuid.value(); + } +} \ No newline at end of file diff --git a/src/libs/core/impl/WtLogger.cpp b/src/libs/core/impl/WtLogger.cpp new file mode 100644 index 00000000..6d7ac150 --- /dev/null +++ b/src/libs/core/impl/WtLogger.cpp @@ -0,0 +1,69 @@ +/* + * 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 . + */ + +#include "core/WtLogger.hpp" + +#include +#include +#include +#include + +#include "core/Exception.hpp" + +namespace lms::core::logging +{ + namespace + { + std::string to_string(std::thread::id id) + { + std::ostringstream oss; + oss << id; + return oss.str(); + } + } + + WtLogger::WtLogger(Severity minSeverity) + : _minSeverity{ minSeverity } + { + } + + std::string WtLogger::computeLogConfig(Severity minSeverity) + { + switch (minSeverity) + { + case Severity::DEBUG: return "*"; + case Severity::INFO: return "* -debug"; + case Severity::WARNING: return "* -debug -info"; + case Severity::ERROR: return "* -debug -info -warning"; + case Severity::FATAL: return "* -debug -info -warning -error"; + } + + throw LmsException{ "Unhandled severity" }; + } + + bool WtLogger::isSeverityActive(Severity severity) const + { + return static_cast(severity) <= static_cast(_minSeverity); + } + + void WtLogger::processLog(const Log& log) + { + Wt::log(getSeverityName(log.getSeverity())) << Wt::WLogger::sep << to_string(std::this_thread::get_id()) << Wt::WLogger::sep << "[" << getModuleName(log.getModule()) << "]" << Wt::WLogger::sep << log.getMessage(); + } +} \ No newline at end of file diff --git a/src/libs/utils/impl/http/Client.cpp b/src/libs/core/impl/http/Client.cpp similarity index 55% rename from src/libs/utils/impl/http/Client.cpp rename to src/libs/core/impl/http/Client.cpp index ecc9d817..c49dc433 100644 --- a/src/libs/utils/impl/http/Client.cpp +++ b/src/libs/core/impl/http/Client.cpp @@ -18,26 +18,22 @@ */ #include "Client.hpp" -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -namespace Http +namespace lms::core::http { - std::unique_ptr - createClient(boost::asio::io_context& ioContext, std::string_view baseUrl) - { - return std::make_unique(ioContext, baseUrl); - } + std::unique_ptr createClient(boost::asio::io_context& ioContext, std::string_view baseUrl) + { + return std::make_unique(ioContext, baseUrl); + } - void - Client::sendGETRequest(ClientGETRequestParameters&& GETParams) - { - _sendQueue.sendRequest(std::make_unique(std::move(GETParams))); - } - - void - Client::sendPOSTRequest(ClientPOSTRequestParameters&& POSTParams) - { - _sendQueue.sendRequest(std::make_unique(std::move(POSTParams))); - } -} // namespace Http + void Client::sendGETRequest(ClientGETRequestParameters&& GETParams) + { + _sendQueue.sendRequest(std::make_unique(std::move(GETParams))); + } + void Client::sendPOSTRequest(ClientPOSTRequestParameters&& POSTParams) + { + _sendQueue.sendRequest(std::make_unique(std::move(POSTParams))); + } +} \ No newline at end of file diff --git a/src/libs/utils/impl/http/Client.hpp b/src/libs/core/impl/http/Client.hpp similarity index 63% rename from src/libs/utils/impl/http/Client.hpp rename to src/libs/core/impl/http/Client.hpp index 30b7bd61..6d00dac3 100644 --- a/src/libs/utils/impl/http/Client.hpp +++ b/src/libs/core/impl/http/Client.hpp @@ -23,23 +23,22 @@ #include #include -#include "utils/http/IClient.hpp" +#include "core/http/IClient.hpp" #include "SendQueue.hpp" -namespace Http +namespace lms::core::http { - class Client final : public IClient - { - public: - Client(boost::asio::io_context& ioContext, std::string_view baseUrl) - : _sendQueue {ioContext, baseUrl} - {} + class Client final : public IClient + { + public: + Client(boost::asio::io_context& ioContext, std::string_view baseUrl) + : _sendQueue{ ioContext, baseUrl } + {} - private: - void sendGETRequest(ClientGETRequestParameters&& request) override; - void sendPOSTRequest(ClientPOSTRequestParameters&& request) override; - - SendQueue _sendQueue; - }; -} // namespace Http + private: + void sendGETRequest(ClientGETRequestParameters&& request) override; + void sendPOSTRequest(ClientPOSTRequestParameters&& request) override; + SendQueue _sendQueue; + }; +} \ No newline at end of file diff --git a/src/libs/core/impl/http/ClientRequest.hpp b/src/libs/core/impl/http/ClientRequest.hpp new file mode 100644 index 00000000..d3e90c31 --- /dev/null +++ b/src/libs/core/impl/http/ClientRequest.hpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 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 . + */ + +#pragma once + +#include +#include +#include "core/http/ClientRequestParameters.hpp" + +namespace lms::core::http +{ + class ClientRequest + { + public: + ClientRequest(ClientGETRequestParameters&& GETParams) : _parameters{ std::move(GETParams) } {} + ClientRequest(ClientPOSTRequestParameters&& POSTParams) : _parameters{ std::move(POSTParams) } {} + + std::size_t retryCount{}; + + const ClientRequestParameters& getParameters() const + { + const ClientRequestParameters* res; + + std::visit([&](const auto& parameters) + { + res = &static_cast(parameters); + }, _parameters); + + return *res; + } + + + enum class Type + { + GET, + POST + }; + Type getType() const + { + if (std::holds_alternative(_parameters)) + return Type::GET; + else + return Type::POST; + } + + const ClientGETRequestParameters& getGETParameters() const + { + return std::get(_parameters); + } + + const ClientPOSTRequestParameters& getPOSTParameters() const + { + return std::get(_parameters); + } + + private: + std::variant _parameters; + }; +} diff --git a/src/libs/core/impl/http/SendQueue.cpp b/src/libs/core/impl/http/SendQueue.cpp new file mode 100644 index 00000000..221273af --- /dev/null +++ b/src/libs/core/impl/http/SendQueue.cpp @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2021 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 . + */ + +#include "SendQueue.hpp" + +#include +#include + +#include "core/Exception.hpp" +#include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" +#include "core/String.hpp" + +#define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[Http SendQueue] - " << message) + +namespace lms::core::stringUtils +{ + template<> + std::optional readAs(std::string_view str) + { + std::optional res; + + if (const std::optional value{ readAs(str) }) + res = std::chrono::seconds{ *value }; + + return res; + } +} + +namespace lms::core::http +{ + namespace + { + template + std::optional headerReadAs(const Wt::Http::Message& msg, std::string_view headerName) + { + std::optional res; + + if (const std::string * headerValue{ msg.getHeader(std::string {headerName}) }) + res = stringUtils::readAs(*headerValue); + + return res; + } + } + + SendQueue::SendQueue(boost::asio::io_context& ioContext, std::string_view baseUrl) + : _ioContext{ ioContext } + , _baseUrl{ baseUrl } + { + _client.done().connect([this](Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) + { + _strand.dispatch([this, ec, msg = std::move(msg)] + { + onClientDone(ec, msg); + }); + }); + } + + SendQueue::~SendQueue() + { + _client.abort(); + } + + void SendQueue::sendRequest(std::unique_ptr request) + { + boost::asio::dispatch(_strand, [this, request = std::move(request)]() mutable + { + _sendQueue[request->getParameters().priority].emplace_back(std::move(request)); + + if (_state == State::Idle) + sendNextQueuedRequest(); + }); + } + + void SendQueue::sendNextQueuedRequest() + { + assert(_state == State::Idle); + assert(!_currentRequest); + + for (auto& [prio, requests] : _sendQueue) + { + LOG(DEBUG, "Processing prio " << static_cast(prio) << ", request count = " << requests.size()); + while (!requests.empty()) + { + std::unique_ptr request{ std::move(requests.front()) }; + requests.pop_front(); + + if (!sendRequest(*request)) + continue; + + _state = State::Sending; + _currentRequest = std::move(request); + return; + } + } + } + + bool SendQueue::sendRequest(const ClientRequest& request) + { + LMS_SCOPED_TRACE_DETAILED("SendQueue", "SendRequest"); + + std::string url{ _baseUrl + request.getParameters().relativeUrl }; + LOG(DEBUG, "Sending request to url '" << url << "'"); + + bool res{}; + switch (request.getType()) + { + case ClientRequest::Type::GET: + res = _client.get(url, request.getGETParameters().headers); + break; + + case ClientRequest::Type::POST: + res = _client.post(url, request.getPOSTParameters().message); + break; + } + + if (!res) + LOG(ERROR, "Send failed, bad url or unsupported scheme?"); + + return res; + } + + void SendQueue::onClientDone(Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) + { + LMS_SCOPED_TRACE_DETAILED("SendQueue", "OnClientDone"); + + if (ec == boost::asio::error::operation_aborted) + { + LOG(DEBUG, "Client aborted"); + return; + } + + assert(_currentRequest); + _state = State::Idle; + + LOG(DEBUG, "Client done. status = " << msg.status()); + if (ec) + onClientDoneError(std::move(_currentRequest), ec); + else + onClientDoneSuccess(std::move(_currentRequest), msg); + } + + void SendQueue::onClientDoneError(std::unique_ptr request, Wt::AsioWrapper::error_code ec) + { + LOG(ERROR, "Retry " << request->retryCount << ", client error: '" << ec.message() << "'"); + + // may be a network error, try again later + throttle(_defaultRetryWaitDuration); + + if (request->retryCount++ < _maxRetryCount) + { + _sendQueue[request->getParameters().priority].emplace_front(std::move(request)); + } + else + { + LOG(ERROR, "Too many retries, giving up operation and throttle"); + if (request->getParameters().onFailureFunc) + request->getParameters().onFailureFunc(); + } + } + + void SendQueue::onClientDoneSuccess(std::unique_ptr request, const Wt::Http::Message& msg) + { + const ClientRequestParameters& requestParameters{ request->getParameters() }; + bool mustThrottle{}; + if (msg.status() == 429) + { + _sendQueue[requestParameters.priority].emplace_front(std::move(request)); + mustThrottle = true; + } + + const auto remainingCount{ headerReadAs(msg, "X-RateLimit-Remaining") }; + LOG(DEBUG, "Remaining messages = " << (remainingCount ? *remainingCount : 0)); + if (mustThrottle || (remainingCount && *remainingCount == 0)) + { + const auto waitDuration{ headerReadAs(msg, "X-RateLimit-Reset-In") }; + throttle(waitDuration.value_or(_defaultRetryWaitDuration)); + } + + if (!mustThrottle) + { + if (msg.status() == 200) + { + if (requestParameters.onSuccessFunc) + requestParameters.onSuccessFunc(msg.body()); + } + else + { + LOG(ERROR, "Send error: '" << msg.body() << "'"); + if (requestParameters.onFailureFunc) + requestParameters.onFailureFunc(); + } + } + + if (_state == State::Idle) + sendNextQueuedRequest(); + } + + void SendQueue::throttle(std::chrono::seconds requestedDuration) + { + assert(_state == State::Idle); + + const std::chrono::seconds duration{ clamp(requestedDuration, _minRetryWaitDuration, _maxRetryWaitDuration) }; + LOG(DEBUG, "Throttling for " << duration.count() << " seconds"); + + _throttleTimer.expires_after(duration); + _throttleTimer.async_wait([this](const boost::system::error_code& ec) + { + if (ec == boost::asio::error::operation_aborted) + { + LOG(DEBUG, "Throttle aborted"); + return; + } + else if (ec) + { + throw LmsException{ "Throttle timer failure: " + std::string {ec.message()} }; + } + + _state = State::Idle; + sendNextQueuedRequest(); + }); + _state = State::Throttled; + } +} diff --git a/src/libs/core/impl/http/SendQueue.hpp b/src/libs/core/impl/http/SendQueue.hpp new file mode 100644 index 00000000..439ef480 --- /dev/null +++ b/src/libs/core/impl/http/SendQueue.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2021 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 . + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include +#include "ClientRequest.hpp" + +namespace lms::core::http +{ + class SendQueue + { + public: + SendQueue(boost::asio::io_context& ioContext, std::string_view baseUrl); + ~SendQueue(); + + SendQueue(const SendQueue&) = delete; + SendQueue(const SendQueue&&) = delete; + SendQueue& operator=(const SendQueue&) = delete; + SendQueue& operator=(const SendQueue&&) = delete; + + void sendRequest(std::unique_ptr request); + + private: + void sendNextQueuedRequest(); + bool sendRequest(const ClientRequest& request); + void onClientDone(Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg); + void onClientDoneError(std::unique_ptr request, Wt::AsioWrapper::error_code ec); + void onClientDoneSuccess(std::unique_ptr request, const Wt::Http::Message& msg); + void throttle(std::chrono::seconds duration); + + const std::size_t _maxRetryCount{ 2 }; + const std::chrono::seconds _defaultRetryWaitDuration{ 30 }; + const std::chrono::seconds _minRetryWaitDuration{ 1 }; + const std::chrono::seconds _maxRetryWaitDuration{ 300 }; + + boost::asio::io_context& _ioContext; + boost::asio::io_context::strand _strand{ _ioContext }; + boost::asio::steady_timer _throttleTimer{ _ioContext }; + std::string _baseUrl; + + enum class State + { + Idle, + Throttled, + Sending, + }; + State _state{ State::Idle }; + Wt::Http::Client _client{ _ioContext }; + std::map>> _sendQueue; + std::unique_ptr _currentRequest; + }; + +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/Crc32Calculator.hpp b/src/libs/core/include/core/Crc32Calculator.hpp similarity index 65% rename from src/libs/utils/include/utils/Crc32Calculator.hpp rename to src/libs/core/include/core/Crc32Calculator.hpp index 1e258d65..e9d081ba 100644 --- a/src/libs/utils/include/utils/Crc32Calculator.hpp +++ b/src/libs/core/include/core/Crc32Calculator.hpp @@ -21,27 +21,23 @@ #include // for boost::crc_32_type -namespace Utils +namespace lms::core { + class Crc32Calculator + { + public: + void processBytes(const std::byte* _data, std::size_t dataSize) + { + _result.process_bytes(_data, dataSize); + } - class Crc32Calculator - { - public: - - void processBytes(const std::byte* _data, std::size_t dataSize) - { - _result.process_bytes(_data, dataSize); - } - - std::uint32_t getResult() const - { - return _result.checksum(); - } - - private: - using Crc32Type = boost::crc_32_type; - Crc32Type _result; - }; + std::uint32_t getResult() const + { + return _result.checksum(); + } + private: + using Crc32Type = boost::crc_32_type; + Crc32Type _result; + }; } - diff --git a/src/libs/core/include/core/EnumSet.hpp b/src/libs/core/include/core/EnumSet.hpp new file mode 100644 index 00000000..d880dff7 --- /dev/null +++ b/src/libs/core/include/core/EnumSet.hpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2020 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 . + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace lms::core +{ + template + class EnumSet + { + static_assert(std::is_enum::value); + static_assert(std::is_same::value || std::is_same::value); + + using IndexType = std::uint_fast8_t; + + public: + using ValueType = underlying_type; + + EnumSet() = default; + constexpr EnumSet(std::initializer_list values) + { + for (T value : values) + insert(value); + } + + template + constexpr EnumSet(It begin, It end) + { + assign(begin, end); + } + + template + constexpr void assign(It begin, It end) + { + clear(); + for (It it{ begin }; it != end; ++it) + insert(*it); + } + + constexpr void insert(T value) + { + assert(static_cast(value) < sizeof(_bitfield) * 8); + _bitfield |= (underlying_type{ 1 } << static_cast(value)); + } + + constexpr void erase(T value) + { + assert(static_cast(value) < sizeof(_bitfield) * 8); + _bitfield &= ~(underlying_type{ 1 } << static_cast(value)); + } + + constexpr bool empty() const + { + return _bitfield == 0; + } + + constexpr bool contains(T value) const + { + assert(static_cast(value) < sizeof(_bitfield) * 8); + return _bitfield & (underlying_type{ 1 } << static_cast(value)); + } + + constexpr void clear() + { + _bitfield = 0; + } + + class iterator + { + public: + using value_type = T; + + constexpr value_type operator*() const + { + return static_cast(_index); + } + + constexpr bool operator==(const iterator& _other) const + { + return &_container == &_other._container && _index == _other._index; + } + + constexpr bool operator!=(const iterator& _other) const + { + return !(*this == _other); + } + + constexpr iterator& operator++() + { + _index = _container.getFirstBitSetIndex(_index + 1); + return *this; + } + + private: + friend class EnumSet; + + constexpr iterator(const EnumSet& _container, IndexType _index) + : _container{ _container } + , _index{ _index } + { + } + + const EnumSet& _container; + IndexType _index; + }; + + constexpr iterator begin() const + { + return iterator{ *this, getFirstBitSetIndex() }; + } + + constexpr iterator end() const + { + return iterator{ *this, npos }; + } + + constexpr underlying_type getBitfield() const + { + return _bitfield; + } + + 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::max() >= sizeof(underlying_type) * 8); + enum : IndexType { npos = sizeof(underlying_type) * 8 }; + + constexpr IndexType getFirstBitSetIndex(IndexType start = {}) const + { + assert(start < npos); + + // return npos if no bit found + IndexType res{ countTrailingZero(_bitfield >> start) }; + if (res == npos) + return res; + + return res + start; + } + + static constexpr IndexType countTrailingZero(underlying_type bitField) + { + IndexType res{}; + + while (res < (sizeof(underlying_type) * 8) && (bitField & 1) == 0) + { + ++res; + bitField >>= 1; + } + + if (res == sizeof(underlying_type) * 8) + res = npos; + + return res; + } + + underlying_type _bitfield{}; + }; +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/Exception.hpp b/src/libs/core/include/core/Exception.hpp similarity index 77% rename from src/libs/utils/include/utils/Exception.hpp rename to src/libs/core/include/core/Exception.hpp index fe9182f6..8c68e165 100644 --- a/src/libs/utils/include/utils/Exception.hpp +++ b/src/libs/core/include/core/Exception.hpp @@ -23,9 +23,12 @@ #include #include -class LmsException : public std::runtime_error +namespace lms::core { - public: - LmsException(std::string_view error = "") : std::runtime_error {std::string {error}} {} -}; - + // TODO, rename to Exception + class LmsException : public std::runtime_error + { + public: + LmsException(std::string_view error = "") : std::runtime_error{ std::string{ error } } {} + }; +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/FileResourceHandlerCreator.hpp b/src/libs/core/include/core/FileResourceHandlerCreator.hpp similarity index 81% rename from src/libs/utils/include/utils/FileResourceHandlerCreator.hpp rename to src/libs/core/include/core/FileResourceHandlerCreator.hpp index 00106721..2ea0cb3d 100644 --- a/src/libs/utils/include/utils/FileResourceHandlerCreator.hpp +++ b/src/libs/core/include/core/FileResourceHandlerCreator.hpp @@ -23,7 +23,9 @@ #include #include -#include "utils/IResourceHandler.hpp" - -std::unique_ptr createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType); +#include "core/IResourceHandler.hpp" +namespace lms +{ + std::unique_ptr createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType); +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/IChildProcess.hpp b/src/libs/core/include/core/IChildProcess.hpp similarity index 52% rename from src/libs/utils/include/utils/IChildProcess.hpp rename to src/libs/core/include/core/IChildProcess.hpp index 72768892..40fc573a 100644 --- a/src/libs/utils/include/utils/IChildProcess.hpp +++ b/src/libs/core/include/core/IChildProcess.hpp @@ -23,32 +23,34 @@ #include #include -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -class ChildProcessException : public LmsException +namespace lms::core { - public: - using LmsException::LmsException; -}; + class ChildProcessException : public LmsException + { + public: + using LmsException::LmsException; + }; -class IChildProcess -{ - public: - using Args = std::vector; + class IChildProcess + { + public: + using Args = std::vector; - virtual ~IChildProcess() = default; + virtual ~IChildProcess() = default; - enum class ReadResult - { - Success, - Error, - EndOfFile, - }; + enum class ReadResult + { + Success, + Error, + EndOfFile, + }; - using ReadCallback = std::function; - virtual void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) = 0; - - virtual std::size_t readSome(std::byte* data, std::size_t bufferSize) = 0; - virtual bool finished() const = 0; -}; + using ReadCallback = std::function; + virtual void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) = 0; + virtual std::size_t readSome(std::byte* data, std::size_t bufferSize) = 0; + virtual bool finished() const = 0; + }; +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/IChildProcessManager.hpp b/src/libs/core/include/core/IChildProcessManager.hpp similarity index 68% rename from src/libs/utils/include/utils/IChildProcessManager.hpp rename to src/libs/core/include/core/IChildProcessManager.hpp index ab4b34d3..26c64df8 100644 --- a/src/libs/utils/include/utils/IChildProcessManager.hpp +++ b/src/libs/core/include/core/IChildProcessManager.hpp @@ -24,14 +24,15 @@ #include "IChildProcess.hpp" -class IChildProcessManager +namespace lms::core { - public: - virtual ~IChildProcessManager() = default; - - virtual std::unique_ptr spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args) = 0; -}; - -std::unique_ptr createChildProcessManager(boost::asio::io_service& ioService); + class IChildProcessManager + { + public: + virtual ~IChildProcessManager() = default; + virtual std::unique_ptr spawnChildProcess(const std::filesystem::path& path, const IChildProcess::Args& args) = 0; + }; + std::unique_ptr createChildProcessManager(boost::asio::io_service& ioService); +} \ No newline at end of file diff --git a/src/libs/core/include/core/IConfig.hpp b/src/libs/core/include/core/IConfig.hpp new file mode 100644 index 00000000..07e4b862 --- /dev/null +++ b/src/libs/core/include/core/IConfig.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 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 . + */ +#pragma once + +#include +#include +#include + +namespace lms::core +{ + // Used to get config values from configuration files + class IConfig + { + public: + virtual ~IConfig() = default; + + // Default values are returned in case of setting not found + virtual std::string_view getString(std::string_view setting, std::string_view def = "") = 0; + virtual void visitStrings(std::string_view setting, std::function _func, std::initializer_list def = {}) = 0; + virtual std::filesystem::path getPath(std::string_view setting, const std::filesystem::path& def = std::filesystem::path()) = 0; + virtual unsigned long getULong(std::string_view setting, unsigned long def = 0) = 0; + virtual long getLong(std::string_view setting, long def = 0) = 0; + virtual bool getBool(std::string_view setting, bool def = false) = 0; + }; + + std::unique_ptr createConfig(const std::filesystem::path& p); +} \ No newline at end of file diff --git a/src/libs/core/include/core/ILogger.hpp b/src/libs/core/include/core/ILogger.hpp new file mode 100644 index 00000000..339978ac --- /dev/null +++ b/src/libs/core/include/core/ILogger.hpp @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2013 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 . + */ + +#pragma once + +#include +#include + +#include "core/String.hpp" +#include "Service.hpp" + +namespace lms::core::logging +{ + enum class Severity + { + FATAL, + ERROR, + WARNING, + INFO, + DEBUG, + }; + + enum class Module + { + API_SUBSONIC, + AUTH, + AV, + CHILDPROCESS, + COVER, + DB, + DBUPDATER, + FEATURE, + FEEDBACK, + HTTP, + MAIN, + METADATA, + REMOTE, + SCROBBLING, + SERVICE, + RECOMMENDATION, + TRANSCODING, + UI, + UTILS, + }; + + const char* getModuleName(Module mod); + const char* getSeverityName(Severity sev); + + class ILogger; + class Log + { + public: + Log(ILogger& logger, Module module, Severity severity); + ~Log(); + + Module getModule() const { return _module; } + Severity getSeverity() const { return _severity; } + std::string getMessage() const; + + std::ostringstream& getOstream() { return _oss; } + + private: + Log(const Log&) = delete; + Log& operator=(const Log&) = delete; + + ILogger& _logger; + Module _module; + Severity _severity; + std::ostringstream _oss; + }; + + class ILogger + { + public: + virtual ~ILogger() = default; + + virtual bool isSeverityActive(Severity severity) const = 0; + virtual void processLog(const Log& log) = 0; + }; +} + +#define LMS_LOG(module, severity, message) \ + do \ + { \ + if (auto* logger_ {::lms::core::Service<::lms::core::logging::ILogger>::get()}; logger_ && logger_->isSeverityActive(::lms::core::logging::Severity::severity)) \ + ::lms::core::logging::Log{ *logger_, ::lms::core::logging::Module::module, ::lms::core::logging::Severity::severity }.getOstream() << message; \ + } while(0) diff --git a/src/libs/utils/include/utils/IOContextRunner.hpp b/src/libs/core/include/core/IOContextRunner.hpp similarity index 57% rename from src/libs/utils/include/utils/IOContextRunner.hpp rename to src/libs/core/include/core/IOContextRunner.hpp index 1a1857a2..9beeec6e 100644 --- a/src/libs/utils/include/utils/IOContextRunner.hpp +++ b/src/libs/core/include/core/IOContextRunner.hpp @@ -23,20 +23,23 @@ #include #include -class IOContextRunner +namespace lms::core { -public: - IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name); - ~IOContextRunner(); + class IOContextRunner + { + public: + IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name); + ~IOContextRunner(); - void stop(); - std::size_t getThreadCount() const; + void stop(); + std::size_t getThreadCount() const; -private: - IOContextRunner(const IOContextRunner&) = delete; - IOContextRunner& operator=(const IOContextRunner&) = delete; + private: + IOContextRunner(const IOContextRunner&) = delete; + IOContextRunner& operator=(const IOContextRunner&) = delete; - boost::asio::io_service& _ioService; - std::optional _work; - std::vector _threads; -}; + boost::asio::io_service& _ioService; + std::optional _work; + std::vector _threads; + }; +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/IResourceHandler.hpp b/src/libs/core/include/core/IResourceHandler.hpp similarity index 86% rename from src/libs/utils/include/utils/IResourceHandler.hpp rename to src/libs/core/include/core/IResourceHandler.hpp index 6dfbd255..7fbc8396 100644 --- a/src/libs/utils/include/utils/IResourceHandler.hpp +++ b/src/libs/core/include/core/IResourceHandler.hpp @@ -22,13 +22,16 @@ #include #include -// Helper class to serve a resource (must be saved as continuation data if not complete) -class IResourceHandler +// TODO, move elsewhere +namespace lms { + // Helper class to serve a resource (must be saved as continuation data if not complete) + class IResourceHandler + { public: virtual ~IResourceHandler() = default; [[nodiscard]] virtual Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) = 0; virtual void abort() = 0; -}; - + }; +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/ITraceLogger.hpp b/src/libs/core/include/core/ITraceLogger.hpp similarity index 91% rename from src/libs/utils/include/utils/ITraceLogger.hpp rename to src/libs/core/include/core/ITraceLogger.hpp index ea3a1f4a..89479dba 100644 --- a/src/libs/utils/include/utils/ITraceLogger.hpp +++ b/src/libs/core/include/core/ITraceLogger.hpp @@ -34,16 +34,16 @@ #define LMS_CONCAT(x, y) LMS_CONCAT_IMPL(x, y) #if LMS_SUPPORT_TRACING -#define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) ::tracing::ScopedTrace LMS_CONCAT(ScopedTrace_, __LINE__){ CATEGORY, LEVEL, NAME } +#define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) ::lms::core::tracing::ScopedTrace LMS_CONCAT(ScopedTrace_, __LINE__){ CATEGORY, LEVEL, NAME } #else #define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) (void)0 #endif -#define LMS_SCOPED_TRACE_OVERVIEW(CATEGORY, NAME) LMS_SCOPED_TRACE(CATEGORY, ::tracing::Level::Overview, NAME) -#define LMS_SCOPED_TRACE_DETAILED(CATEGORY, NAME) LMS_SCOPED_TRACE(CATEGORY, ::tracing::Level::Detailed, NAME) +#define LMS_SCOPED_TRACE_OVERVIEW(CATEGORY, NAME) LMS_SCOPED_TRACE(CATEGORY, ::lms::core::tracing::Level::Overview, NAME) +#define LMS_SCOPED_TRACE_DETAILED(CATEGORY, NAME) LMS_SCOPED_TRACE(CATEGORY, ::lms::core::tracing::Level::Detailed, NAME) -namespace tracing +namespace lms::core::tracing { using clock = std::chrono::steady_clock; diff --git a/src/libs/utils/include/utils/IZipper.hpp b/src/libs/core/include/core/IZipper.hpp similarity index 56% rename from src/libs/utils/include/utils/IZipper.hpp rename to src/libs/core/include/core/IZipper.hpp index 813df2d7..f8542a0b 100644 --- a/src/libs/utils/include/utils/IZipper.hpp +++ b/src/libs/core/include/core/IZipper.hpp @@ -25,30 +25,30 @@ #include "Exception.hpp" -namespace Zip + // TODO, move elsewhere? +namespace lms::zip { - struct Entry - { - std::string fileName; - std::filesystem::path filePath; - }; - using EntryContainer = std::vector; + struct Entry + { + std::string fileName; + std::filesystem::path filePath; + }; + using EntryContainer = std::vector; - class Exception : public LmsException - { - using LmsException::LmsException; - }; + class Exception : public core::LmsException + { + using core::LmsException::LmsException; + }; - class IZipper - { - public: - virtual ~IZipper() = default; + class IZipper + { + public: + virtual ~IZipper() = default; - virtual std::uint64_t writeSome(std::ostream& output) = 0; - virtual bool isComplete() const = 0; - virtual void abort() = 0; - }; - - std::unique_ptr createArchiveZipper(const EntryContainer& entries); -} // namespace Zip + virtual std::uint64_t writeSome(std::ostream& output) = 0; + virtual bool isComplete() const = 0; + virtual void abort() = 0; + }; + std::unique_ptr createArchiveZipper(const EntryContainer& entries); +} diff --git a/src/libs/core/include/core/LiteralString.hpp b/src/libs/core/include/core/LiteralString.hpp new file mode 100644 index 00000000..63bda2c1 --- /dev/null +++ b/src/libs/core/include/core/LiteralString.hpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2024 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 . + */ + +#pragma once + +#include +#include +#include + +namespace lms::core +{ + class LiteralString + { + public: + constexpr LiteralString() noexcept = default; + template + constexpr LiteralString(const char(&str)[N]) noexcept : _str{ str, N - 1 } { static_assert(N > 0); } + + constexpr const char* c_str() const noexcept { return _str.data(); } + constexpr std::size_t length() const noexcept { return _str.length(); } + constexpr std::string_view str() const noexcept { return _str; } + constexpr auto operator<=>(const LiteralString& other) const = default; + + private: + std::string_view _str; + }; +} + +namespace std +{ + template<> + struct hash + { + size_t operator()(const lms::core::LiteralString& str) const + { + return hash{}(str.str()); + } + }; +} + +namespace lms::core +{ + struct LiteralStringHash + { + using hash_type = std::hash; + using is_transparent = void; + + [[nodiscard]] size_t operator()(const LiteralString& str) const { + return hash_type{}(str.str()); + } + + [[nodiscard]] size_t operator()(std::string_view str) const { + return hash_type{}(str); + } + + [[nodiscard]] size_t operator()(const std::string& str) const { + return hash_type{}(str); + } + }; + + struct LiteralStringEqual + { + using is_transparent = void; + + [[nodiscard]] bool operator()(const LiteralString& lhs, const LiteralString& rhs) const { + return lhs == rhs; + } + + [[nodiscard]] bool operator()(const LiteralString& lhs, const std::string& rhs) const { + return lhs.str() == rhs; + } + + [[nodiscard]] bool operator()(const LiteralString& lhs, std::string_view rhs) const { + return lhs.str() == rhs; + } + + [[nodiscard]] bool operator()(const std::string_view& lhs, LiteralString rhs) const { + return lhs == rhs.str(); + } + + [[nodiscard]] bool operator()(const std::string& lhs, const LiteralString& rhs) const { + return lhs == rhs.str(); + } + }; +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/NetAddress.hpp b/src/libs/core/include/core/NetAddress.hpp similarity index 100% rename from src/libs/utils/include/utils/NetAddress.hpp rename to src/libs/core/include/core/NetAddress.hpp diff --git a/src/libs/utils/include/utils/Path.hpp b/src/libs/core/include/core/Path.hpp similarity index 94% rename from src/libs/utils/include/utils/Path.hpp rename to src/libs/core/include/core/Path.hpp index 418bc2e2..8f8d11df 100644 --- a/src/libs/utils/include/utils/Path.hpp +++ b/src/libs/core/include/core/Path.hpp @@ -26,7 +26,7 @@ #include -namespace PathUtils +namespace lms::core::pathUtils { std::uint32_t computeCrc32(const std::filesystem::path& p); @@ -60,7 +60,7 @@ namespace PathUtils longestCommonPath = *first++; while (first != last) - longestCommonPath = PathUtils::getLongestCommonPath(*first++, longestCommonPath); + longestCommonPath = core::pathUtils::getLongestCommonPath(*first++, longestCommonPath); return longestCommonPath; } diff --git a/src/libs/core/include/core/Random.hpp b/src/libs/core/include/core/Random.hpp new file mode 100644 index 00000000..7bfa03d4 --- /dev/null +++ b/src/libs/core/include/core/Random.hpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 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 . + */ + +#pragma once + +#include +#include + +namespace lms::core::random +{ + using RandGenerator = std::mt19937; + RandGenerator& getRandGenerator(); + + RandGenerator createSeededGenerator(uint_fast32_t seed); + + template + T getRandom(T min, T max) + { + std::uniform_int_distribution<> dist{ min, max }; + return dist(getRandGenerator()); + } + + template + T getRealRandom(T min, T max) + { + std::uniform_real_distribution<> dist{ min, max }; + return dist(getRandGenerator()); + } + + template + void shuffleContainer(Container& container) + { + std::shuffle(std::begin(container), std::end(container), getRandGenerator()); + } + + template + typename Container::const_iterator pickRandom(const Container& container) + { + if (container.empty()) + return std::end(container); + + return std::next(std::begin(container), getRandom(0, static_cast(container.size() - 1))); + } +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/RecursiveSharedMutex.hpp b/src/libs/core/include/core/RecursiveSharedMutex.hpp similarity index 61% rename from src/libs/utils/include/utils/RecursiveSharedMutex.hpp rename to src/libs/core/include/core/RecursiveSharedMutex.hpp index 57f6631e..b38a0dec 100644 --- a/src/libs/utils/include/utils/RecursiveSharedMutex.hpp +++ b/src/libs/core/include/core/RecursiveSharedMutex.hpp @@ -24,26 +24,28 @@ #include #include -// API compatible with shared_mutex -class RecursiveSharedMutex +namespace lms::core { - public: - void lock(); - void unlock(); + // API compatible with shared_mutex + class RecursiveSharedMutex + { + public: + void lock(); + void unlock(); - void lock_shared(); - void unlock_shared(); + void lock_shared(); + void unlock_shared(); #ifndef NDEBUG - bool isSharedLocked(); - bool isUniqueLocked(); + bool isSharedLocked(); + bool isUniqueLocked(); #endif // NDEBUG - private: - std::shared_mutex _mutex; - std::thread::id _uniqueOwner; - std::size_t _uniqueCount{}; - - std::mutex _sharedCountMutex; - std::unordered_map _sharedCounts; -}; + private: + std::shared_mutex _mutex; + std::thread::id _uniqueOwner; + std::size_t _uniqueCount{}; + std::mutex _sharedCountMutex; + std::unordered_map _sharedCounts; + }; +} \ No newline at end of file diff --git a/src/libs/core/include/core/Service.hpp b/src/libs/core/include/core/Service.hpp new file mode 100644 index 00000000..02141740 --- /dev/null +++ b/src/libs/core/include/core/Service.hpp @@ -0,0 +1,73 @@ +/* + * 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 . + */ + +#pragma once + +#include +#include + +namespace lms::core +{ + template + class Service + { + public: + Service() = default; + Service(std::unique_ptr service) + { + assign(std::move(service)); + } + + ~Service() + { + clear(); + } + + Service(const Service&) = delete; + Service(Service&&) = delete; + Service& operator=(const Service&) = delete; + Service& operator=(Service&&) = delete; + + Class* operator->() const + { + return Service::get(); + } + + Class& operator*() const + { + return *Service::get(); + } + + static Class* get() { return _service.get(); } + static bool exists() { return _service.get(); } + + template + static Class& assign(std::unique_ptr service) + { + assert(!_service); + _service = std::move(service); + return *get(); + } + + private: + static void clear() { _service.reset(); } + + static inline std::unique_ptr _service; + }; +} \ No newline at end of file diff --git a/src/libs/core/include/core/StreamLogger.hpp b/src/libs/core/include/core/StreamLogger.hpp new file mode 100644 index 00000000..f82d728a --- /dev/null +++ b/src/libs/core/include/core/StreamLogger.hpp @@ -0,0 +1,42 @@ +/* + * 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 . + */ + +#pragma once + +#include "core/EnumSet.hpp" +#include "core/ILogger.hpp" + +namespace lms::core::logging +{ + class StreamLogger final : public ILogger + { + public: + static constexpr EnumSet allSeverities{ Severity::FATAL, Severity::ERROR, Severity::WARNING, Severity::INFO, Severity::DEBUG }; + static constexpr EnumSet defaultSeverities{ Severity::FATAL, Severity::ERROR, Severity::WARNING, Severity::INFO }; + + StreamLogger(std::ostream& oss, EnumSet severities = defaultSeverities); + + bool isSeverityActive(Severity severity) const override { return _severities.contains(severity); } + void processLog(const Log& log) override; + + private: + std::ostream& _os; + const EnumSet _severities; + }; +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/String.hpp b/src/libs/core/include/core/String.hpp similarity index 99% rename from src/libs/utils/include/utils/String.hpp rename to src/libs/core/include/core/String.hpp index 4c7e5473..88122a49 100644 --- a/src/libs/utils/include/utils/String.hpp +++ b/src/libs/core/include/core/String.hpp @@ -36,7 +36,7 @@ namespace Wt class WDateTime; } -namespace StringUtils +namespace lms::core::stringUtils { [[nodiscard]] std::vector splitString(std::string_view string, char separator); [[nodiscard]] std::vector splitString(std::string_view string, std::string_view separator); @@ -100,5 +100,4 @@ namespace StringUtils [[nodiscard]] std::string toISO8601String(const Wt::WDateTime& dateTime); [[nodiscard]] std::string toISO8601String(const Wt::WDate& date); - -} // StringUtils \ No newline at end of file +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/Tuple.hpp b/src/libs/core/include/core/Tuple.hpp similarity index 92% rename from src/libs/utils/include/utils/Tuple.hpp rename to src/libs/core/include/core/Tuple.hpp index f8de7387..ad2f6adf 100644 --- a/src/libs/utils/include/utils/Tuple.hpp +++ b/src/libs/core/include/core/Tuple.hpp @@ -21,9 +21,9 @@ #include -namespace Utils +namespace lms::core { - namespace Details + namespace details { template struct Seq { }; @@ -44,8 +44,6 @@ namespace Utils template void forEachTypeInTuple(std::tuple const& t, Func f) { - Details::forEachTypeInTuple(t, f, Details::GenSeq()); + details::forEachTypeInTuple(t, f, details::GenSeq()); } -} - - +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/UUID.hpp b/src/libs/core/include/core/UUID.hpp similarity index 66% rename from src/libs/utils/include/utils/UUID.hpp rename to src/libs/core/include/core/UUID.hpp index 0522ffd2..47ceb1be 100644 --- a/src/libs/utils/include/utils/UUID.hpp +++ b/src/libs/core/include/core/UUID.hpp @@ -23,27 +23,29 @@ #include #include -#include "utils/String.hpp" +#include "core/String.hpp" -class UUID +namespace lms::core { -public: - static std::optional fromString(std::string_view str); - static UUID generate(); + class UUID + { + public: + static std::optional fromString(std::string_view str); + static UUID generate(); - std::string_view getAsString() const { return _value; } + std::string_view getAsString() const { return _value; } - bool operator<=>(const UUID&) const = default; + bool operator<=>(const core::UUID&) const = default; -private: - UUID(std::string_view value); - std::string _value; -}; + private: + UUID(std::string_view value); + std::string _value; + }; +} -namespace StringUtils +namespace lms::core::stringUtils { template <> std::optional readAs(std::string_view str); } - diff --git a/src/libs/utils/include/utils/Utils.hpp b/src/libs/core/include/core/Utils.hpp similarity index 97% rename from src/libs/utils/include/utils/Utils.hpp rename to src/libs/core/include/core/Utils.hpp index ebf1df61..3965859f 100644 --- a/src/libs/utils/include/utils/Utils.hpp +++ b/src/libs/core/include/core/Utils.hpp @@ -22,9 +22,8 @@ #include #include -namespace Utils +namespace lms::core::utils { - template> constexpr T clamp(T v, T lo, T hi, Compare comp = {}) { @@ -39,5 +38,4 @@ namespace Utils if (std::find(std::cbegin(container), std::cend(container), val) == std::cend(container)) container.push_back(val); } - -} +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/WtLogger.hpp b/src/libs/core/include/core/WtLogger.hpp similarity index 64% rename from src/libs/utils/include/utils/WtLogger.hpp rename to src/libs/core/include/core/WtLogger.hpp index c3e80461..6cb7b2be 100644 --- a/src/libs/utils/include/utils/WtLogger.hpp +++ b/src/libs/core/include/core/WtLogger.hpp @@ -21,17 +21,20 @@ #include -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" -class WtLogger final : public ILogger +namespace lms::core::logging { -public: - WtLogger(Severity minSeverity); + class WtLogger final : public ILogger + { + public: + WtLogger(Severity minSeverity); - static std::string computeLogConfig(Severity minSeverity); + static std::string computeLogConfig(Severity minSeverity); -private: - bool isSeverityActive(Severity severity) const override; - void processLog(const Log& log) override; - const Severity _minSeverity; -}; \ No newline at end of file + private: + bool isSeverityActive(Severity severity) const override; + void processLog(const Log& log) override; + const Severity _minSeverity; + }; +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/ZipperResourceHandlerCreator.hpp b/src/libs/core/include/core/ZipperResourceHandlerCreator.hpp similarity index 79% rename from src/libs/utils/include/utils/ZipperResourceHandlerCreator.hpp rename to src/libs/core/include/core/ZipperResourceHandlerCreator.hpp index d8e40157..e0903abc 100644 --- a/src/libs/utils/include/utils/ZipperResourceHandlerCreator.hpp +++ b/src/libs/core/include/core/ZipperResourceHandlerCreator.hpp @@ -21,8 +21,10 @@ #include -#include "utils/IResourceHandler.hpp" -#include "utils/IZipper.hpp" - -std::unique_ptr createZipperResourceHandler(std::unique_ptr zipper); +#include "core/IResourceHandler.hpp" +#include "core/IZipper.hpp" +namespace lms::zip +{ + std::unique_ptr createZipperResourceHandler(std::unique_ptr zipper); +} diff --git a/src/libs/utils/include/utils/http/ClientRequestParameters.hpp b/src/libs/core/include/core/http/ClientRequestParameters.hpp similarity index 51% rename from src/libs/utils/include/utils/http/ClientRequestParameters.hpp rename to src/libs/core/include/core/http/ClientRequestParameters.hpp index 94fc1cdd..ced63f9a 100644 --- a/src/libs/utils/include/utils/http/ClientRequestParameters.hpp +++ b/src/libs/core/include/core/http/ClientRequestParameters.hpp @@ -25,36 +25,35 @@ #include -namespace Http +namespace lms::core::http { - struct ClientRequestParameters - { - enum class Priority - { - High, - Normal, - Low, - }; + struct ClientRequestParameters + { + enum class Priority + { + High, + Normal, + Low, + }; - Priority priority {Priority::Normal}; - std::string relativeUrl; // relative to baseUrl used by the client + Priority priority{ Priority::Normal }; + std::string relativeUrl; // relative to baseUrl used by the client - using OnSuccessFunc = std::function; - OnSuccessFunc onSuccessFunc; + using OnSuccessFunc = std::function; + OnSuccessFunc onSuccessFunc; - using OnFailureFunc = std::function; - OnFailureFunc onFailureFunc; - }; + using OnFailureFunc = std::function; + OnFailureFunc onFailureFunc; + }; - struct ClientGETRequestParameters final : public ClientRequestParameters - { - std::vector headers; - }; + struct ClientGETRequestParameters final : public ClientRequestParameters + { + std::vector headers; + }; - struct ClientPOSTRequestParameters final : public ClientRequestParameters - { - Wt::Http::Message message; - }; - -} // namespace Http + struct ClientPOSTRequestParameters final : public ClientRequestParameters + { + Wt::Http::Message message; + }; +} \ No newline at end of file diff --git a/src/libs/utils/include/utils/http/IClient.hpp b/src/libs/core/include/core/http/IClient.hpp similarity index 76% rename from src/libs/utils/include/utils/http/IClient.hpp rename to src/libs/core/include/core/http/IClient.hpp index 09df5ee5..30cc2d01 100644 --- a/src/libs/utils/include/utils/http/IClient.hpp +++ b/src/libs/core/include/core/http/IClient.hpp @@ -22,19 +22,18 @@ #include #include -#include "utils/http/ClientRequestParameters.hpp" +#include "core/http/ClientRequestParameters.hpp" -namespace Http +namespace lms::core::http { class IClient { - public: - virtual ~IClient() = default; + public: + virtual ~IClient() = default; - virtual void sendGETRequest(ClientGETRequestParameters&& request) = 0; - virtual void sendPOSTRequest(ClientPOSTRequestParameters&& request) = 0; + virtual void sendGETRequest(ClientGETRequestParameters&& request) = 0; + virtual void sendPOSTRequest(ClientPOSTRequestParameters&& request) = 0; }; std::unique_ptr createClient(boost::asio::io_context& ioContext, std::string_view baseUrl); -} // namespace Http - +} \ No newline at end of file diff --git a/src/libs/utils/test/CMakeLists.txt b/src/libs/core/test/CMakeLists.txt similarity index 65% rename from src/libs/utils/test/CMakeLists.txt rename to src/libs/core/test/CMakeLists.txt index b6d2012e..e052ad84 100644 --- a/src/libs/utils/test/CMakeLists.txt +++ b/src/libs/core/test/CMakeLists.txt @@ -1,6 +1,6 @@ include(GoogleTest) -add_executable(test-utils +add_executable(test-core EnumSet.cpp LiteralString.cpp Path.cpp @@ -10,13 +10,13 @@ add_executable(test-utils Utils.cpp ) -target_link_libraries(test-utils PRIVATE - lmsutils +target_link_libraries(test-core PRIVATE + lmscore Threads::Threads GTest::GTest ) if (NOT CMAKE_CROSSCOMPILING) - gtest_discover_tests(test-utils) + gtest_discover_tests(test-core) endif() diff --git a/src/libs/core/test/EnumSet.cpp b/src/libs/core/test/EnumSet.cpp new file mode 100644 index 00000000..ad83ea11 --- /dev/null +++ b/src/libs/core/test/EnumSet.cpp @@ -0,0 +1,63 @@ +/* + * 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 . + */ + +#include + +#include "core/EnumSet.hpp" + +namespace lms::core +{ + TEST(EnumSet, ctr) + { + enum class Foo + { + One, + Two, + }; + + { + constexpr EnumSet 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 test{ Foo::One, Foo::Two }; + constexpr auto bitfield{ test.getBitfield() }; + + EnumSet 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); + } + } +} \ No newline at end of file diff --git a/src/libs/core/test/LiteralString.cpp b/src/libs/core/test/LiteralString.cpp new file mode 100644 index 00000000..4997b7e2 --- /dev/null +++ b/src/libs/core/test/LiteralString.cpp @@ -0,0 +1,72 @@ +/* + * 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 . + */ + +#include +#include + +#include "core/LiteralString.hpp" + +namespace lms::core +{ + TEST(LiteralString, ctr) + { + { + constexpr LiteralString foo{ "abc" }; + static_assert(foo.length() == 3); + static_assert(foo == "abc"); + static_assert(foo == LiteralString{ "abc" }); + static_assert(foo < LiteralString{ "abcd" }); + static_assert(foo > LiteralString{ "aac" }); + + EXPECT_EQ(::strlen(foo.c_str()), 3); + EXPECT_EQ(foo, LiteralString{ "abc" }); + EXPECT_GT(foo, LiteralString{ "abb" }); + EXPECT_LT(foo, LiteralString{ "abcd" }); + } + + { + constexpr LiteralString foo{ "" }; + static_assert(foo.length() == 0); + static_assert(foo == ""); + static_assert(foo == LiteralString{ "" }); + static_assert(foo < LiteralString{ "a" }); + EXPECT_EQ(::strlen(foo.c_str()), 0); + } + } + + TEST(LiteralString, unordered_map) + { + { + std::unordered_map myMap{ {"abc", 42} }; + + EXPECT_TRUE(myMap.contains("abc")); + EXPECT_TRUE(myMap.contains(LiteralString{ "abc" })); + EXPECT_FALSE(myMap.contains("abcd")); + } + + { + std::unordered_map myMap{ {"abc", 42} }; + EXPECT_TRUE(myMap.contains(std::string{ "abc" })); + EXPECT_TRUE(myMap.contains(std::string_view{ "abc" })); + EXPECT_FALSE(myMap.contains(std::string{ "abcd" })); + EXPECT_FALSE(myMap.contains(std::string_view{ "abcd" })); + } + + } +} \ No newline at end of file diff --git a/src/libs/core/test/Path.cpp b/src/libs/core/test/Path.cpp new file mode 100644 index 00000000..83fc7087 --- /dev/null +++ b/src/libs/core/test/Path.cpp @@ -0,0 +1,108 @@ +/* + * 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 . + */ + +#include + +#include "core/Path.hpp" + +namespace lms::core::pathUtils::tests +{ + TEST(Path, getLongestCommonPath) + { + struct TestCase + { + std::filesystem::path path1; + std::filesystem::path path2; + std::filesystem::path expectedCommonPath; + }; + + TestCase tests[] + { + {"foo.txt", "/foo/foo.txt", ""}, + {"/", "/file.txt", "/"}, + {"/foo/bar/file1.txt", "/foo/bar/file2.txt", "/foo/bar"}, + {"/foo/bar/file.txt", "/foo/bar/file.txt", "/foo/bar/file.txt"}, + {"/dir1/file.txt", "/dir2/file.txt", "/"}, + {"/prefix/folder/file.txt", "/prefix/folder/subfolder/file.txt", "/prefix/folder"}, + }; + + for (const TestCase& test : tests) + { + EXPECT_EQ(core::pathUtils::getLongestCommonPath(test.path1, test.path2), test.expectedCommonPath); + } + } + + + TEST(Path, getLongestCommonPathIterator) + { + struct TestCase + { + std::vector paths; + std::filesystem::path expectedCommonPath; + }; + + TestCase tests[] + { + {{}, ""}, + {{"/"}, "/"}, + {{"/foo", "/bar"}, "/"}, + {{"/foo/bar/file1.txt", "/foo/bar/file2.txt"}, "/foo/bar"}, + {{"/foo", "/foo/"}, "/foo"}, + {{"/foo", "/foo"}, "/foo"}, + {{"/foo/", "/foo/"}, "/foo/"}, + {{"/foo/", "/foo/", "/bar"}, "/"}, + {{"/foo/", "/foo/", "/foo/bar"}, "/foo"}, + }; + + for (const TestCase& test : tests) + { + EXPECT_EQ(core::pathUtils::getLongestCommonPath(std::cbegin(test.paths), std::cend(test.paths)), test.expectedCommonPath); + } + } + + TEST(Path, isPathInRootPath) + { + struct TestCase + { + std::filesystem::path path; + std::filesystem::path rootPath; + bool expectedResult; + }; + + TestCase tests[] + { + {"/file.txt", "/", true}, + {"/root/folder/file.txt", "/root", true}, + {"/root/file.txt", "/root", true}, + {"/root/file.txt", "/root/", true}, + {"/root", "/root", true}, + {"/root", "/root/", true}, + {"/root/", "/root", true}, + {"/root/", "/root/", true}, + {"/folder/file.txt", "/root", false}, + {"/folder/file.txt", "/root/", false}, + {"", "/root", false}, + }; + + for (const TestCase& test : tests) + { + EXPECT_EQ(core::pathUtils::isPathInRootPath(test.path, test.rootPath), test.expectedResult) << "Failed: path = " << test.path << ", rootPath = " << test.rootPath; + } + } +} \ No newline at end of file diff --git a/src/libs/core/test/RecursiveSharedMutex.cpp b/src/libs/core/test/RecursiveSharedMutex.cpp new file mode 100644 index 00000000..cb60d26c --- /dev/null +++ b/src/libs/core/test/RecursiveSharedMutex.cpp @@ -0,0 +1,105 @@ +/* + * 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 . + */ + +#include +#include +#include +#include + +#include + +#include "core/RecursiveSharedMutex.hpp" + +namespace lms::core +{ + TEST(RecursiveSharedMutex, SingleThreaded) + { + RecursiveSharedMutex mutex; + + { + std::unique_lock lock{ mutex }; + } + + { + std::shared_lock lock{ mutex }; + } + + { + std::unique_lock lock1{ mutex }; + std::unique_lock lock2{ mutex }; + } + + { + std::shared_lock lock1{ mutex }; + std::shared_lock lock2{ mutex }; + } + + { + std::unique_lock lock1{ mutex }; + std::shared_lock lock2{ mutex }; + } + } + + TEST(RecursiveSharedMutex, MultiThreaded) + { + constexpr std::size_t nbThreads{ 10 }; + std::vector threads; + + RecursiveSharedMutex mutex; + std::atomic nbUnique{}; + std::atomic nbShared{}; + for (std::size_t i{}; i < nbThreads; ++i) + { + threads.emplace_back([&] + { + { + std::unique_lock lock{ mutex }; + + std::shared_lock lock2{ mutex }; + + assert(nbUnique == 0); + assert(nbShared == 0); + nbUnique++; + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + assert(nbUnique == 1); + assert(nbShared == 0); + nbUnique--; + } + + { + std::shared_lock lock{ mutex }; + std::shared_lock lock2{ mutex }; + + assert(nbUnique == 0); + nbShared++; + + std::this_thread::sleep_for(std::chrono::milliseconds(15)); + + assert(nbShared > 0); + assert(nbShared <= nbThreads); + assert(nbUnique == 0); + nbShared--; + } + }); + } + + for (std::thread& t : threads) + t.join(); + } +} \ No newline at end of file diff --git a/src/libs/core/test/String.cpp b/src/libs/core/test/String.cpp new file mode 100644 index 00000000..71c45cde --- /dev/null +++ b/src/libs/core/test/String.cpp @@ -0,0 +1,297 @@ +/* + * 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 . + */ + +#include + +#include +#include +#include +#include "core/String.hpp" + +namespace lms::core::stringUtils::tests +{ + TEST(StringUtils, splitString_charDelim) + { + struct TestCase + { + std::string_view input; + char delimiter; + std::vector expectedOutput; + }; + + TestCase tests[] + { + {"abc", '-', {"abc"}}, + {"a", '-', {"a"}}, + {"", '-', {""}}, + {"a-b-c", '-', {"a", "b", "c"}}, + {"a|b|c", '|', {"a", "b", "c"}}, + {"a;b;c", ';', {"a", "b", "c"}}, + {";b;c", ';', {"", "b", "c"}}, + {" ;b;c", ';', {" ", "b", "c"}}, + {" ;;c", ';', {" ", "", "c"}}, + {" ; ;c", ';', {" ", " ", "c"}}, + {"a;b; ", ';', {"a", "b", " "}}, + {"a;b", ';', {"a", "b"}}, + {";b", ';', {"", "b"}}, + {";", ';', {"", ""}}, + {";;", ';', {"", "", ""}}, + {";;;", ';', {"", "", "", ""}}, + {";;a;;b;;", ';', {"", "", "a", "", "b", "", ""}}, + {"a b", ' ', {"a", "b"}}, + {"", ' ', {""}}, + {"a-b|c", '-', {"a","b|c"}}, + {"a|b-c", '-', {"a|b", "c"}}, + {"test=foo bar", '=', {"test", "foo bar"}}, + }; + + for (const TestCase& test : tests) + { + const std::vector res{ splitString(test.input, test.delimiter) }; + EXPECT_EQ(res, test.expectedOutput) << "Input = '" << test.input << "', delims = '" << test.delimiter << "'"; + } + } + + TEST(StringUtils, splitString_stringDelim) + { + struct TestCase + { + std::string_view input; + std::string_view delimiter; + std::vector expectedOutput; + }; + + TestCase tests[] + { + {"abc", "", {"abc"}}, + {"abc", "-", {"abc"}}, + {"abc", "b", {"a", "c"}}, + {"ab/cd", "/", {"ab", "cd"}}, + {"ab/cd", "/ ", {"ab/cd"}}, + {"ab/cd", " /", {"ab/cd"}}, + {"ab /cd", " /", {"ab", "cd"}}, + {"ab/ cd", "/ ", {"ab", "cd"}}, + {"ab / cd", " / ", {"ab", "cd"}}, + {"ab/cd", " / ", {"ab/cd"}}, + {"ab/cd / ", " / ", {"ab/cd", ""}}, + }; + + for (const TestCase& test : tests) + { + const std::vector res{ splitString(test.input, test.delimiter) }; + EXPECT_EQ(res, test.expectedOutput) << "Input = '" << test.input << "', delims = '" << test.delimiter << "'"; + } + } + + TEST(StringUtils, joinStrings) + { + struct TestCase + { + std::vector input; + std::string delimiter; + std::string expectedOutput; + }; + + TestCase tests[] + { + {{"a", "b", "c"}, "-", "a-b-c"}, + {{"a", "b", "c"}, ",", "a,b,c"}, + {{"a", "b", "c"}, "***", "a***b***c"}, + {{"a", "", "c"}, "-", "a--c"}, + {{"", "b", "c"}, "-", "-b-c"}, + {{"a"}, "-", "a"}, + {{"a"}, ",", "a"}, + }; + + for (const TestCase& test : tests) + { + const std::string str{ joinStrings(test.input, test.delimiter) }; + EXPECT_EQ(str, test.expectedOutput); + } + } + + TEST(StringUtils, escapeAndJoinStrings) + { + struct TestCase + { + std::vector input; + char delimiter; + char escapeChar; + std::string expectedOutput; + }; + + TestCase tests[] + { + {{""}, ';', '\\', ""}, + {{";"}, ';', '\\', "\\;"}, + {{";;"}, ';', '\\', "\\;\\;"}, + {{"a;", "b"}, ';', '\\', "a\\;;b"}, + {{"a;", "b;"}, ';', '\\', "a\\;;b\\;"}, + }; + + for (const TestCase& test : tests) + { + const std::string str{ escapeAndJoinStrings(test.input, test.delimiter, test.escapeChar) }; + EXPECT_EQ(str, test.expectedOutput); + } + } + + TEST(StringUtils, splitEscapedStrings) + { + struct TestCase + { + std::string input; + char delimiter; + char escapeChar; + std::vector expectedOutput; + }; + + TestCase tests[] + { + {"", ';', '\\', {}}, + {"\\;", ';', '\\', {";"}}, + {"\\;\\;", ';', '\\', {";;"}}, + {"a\\;;b", ';', '\\', {"a;", "b"}}, + {"a\\;;b\\;", ';', '\\', {"a;", "b;"}}, + }; + + for (const TestCase& test : tests) + { + const std::vector str{ splitEscapedStrings(test.input, test.delimiter, test.escapeChar) }; + EXPECT_EQ(str, test.expectedOutput); + } + } + + TEST(StringUtils, escapeJSString) + { + EXPECT_EQ(jsEscape(""), ""); + EXPECT_EQ(jsEscape(R"(Test'.mp3)"), R"(Test\'.mp3)"); + EXPECT_EQ(jsEscape(R"(Test"".mp3)"), R"(Test\"\".mp3)"); + EXPECT_EQ(jsEscape(R"(\Test\.mp3)"), R"(\\Test\\.mp3)"); + } + + TEST(StringUtils, escapeJsonString) + { + EXPECT_EQ(jsonEscape(""), ""); + EXPECT_EQ(jsonEscape(R"(Test'.mp3)"), R"(Test'.mp3)"); + EXPECT_EQ(jsonEscape(R"(Test"".mp3)"), R"(Test\"\".mp3)"); + EXPECT_EQ(jsonEscape(R"(\Test\.mp3)"), R"(\\Test\\.mp3)"); + } + + TEST(StringUtils, escapeString) + { + EXPECT_EQ(escapeString("", "*", ' '), ""); + EXPECT_EQ(escapeString("", "", ' '), ""); + EXPECT_EQ(escapeString("a", "", ' '), "a"); + EXPECT_EQ(escapeString("*", "*", '_'), "_*"); + EXPECT_EQ(escapeString("*a*", "*", '_'), "_*a_*"); + EXPECT_EQ(escapeString("*a|", "*|", '_'), "_*a_|"); + EXPECT_EQ(escapeString("**||", "*|", '_'), "_*_*_|_|"); + EXPECT_EQ(escapeString("one;two", ";", '\\'), "one\\;two"); + EXPECT_EQ(escapeString("one\\;two", ";", '\\'), "one\\\\;two"); + EXPECT_EQ(escapeString("one;", ";", '\\'), "one\\;"); + } + + TEST(StringUtils, unescapeString) + { + EXPECT_EQ(unescapeString("one\\", '\\'), "one\\"); + EXPECT_EQ(unescapeString("\\\\one", '\\'), "\\one"); + EXPECT_EQ(unescapeString("one\\;two", '\\'), "one;two"); + EXPECT_EQ(unescapeString("one\\\\;two", '\\'), "one\\;two"); + } + + TEST(StringUtils, readAs_bool) + { + EXPECT_EQ(readAs("true"), true); + EXPECT_EQ(readAs("1"), true); + EXPECT_EQ(readAs("false"), false); + EXPECT_EQ(readAs("0"), false); + EXPECT_EQ(readAs("foo"), std::nullopt); + EXPECT_EQ(readAs(""), std::nullopt); + } + + TEST(StringUtils, readAs_int) + { + EXPECT_EQ(readAs("1024"), 1024); + EXPECT_EQ(readAs("0"), 0); + EXPECT_EQ(readAs("-0"), 0); + EXPECT_EQ(readAs("-1"), -1); + EXPECT_EQ(readAs(""), std::nullopt); + EXPECT_EQ(readAs("a"), std::nullopt); + EXPECT_EQ(readAs("-"), std::nullopt); + EXPECT_EQ(readAs("1024-1"), 1024); + EXPECT_EQ(readAs("1024-"), 1024); + EXPECT_EQ(readAs("1024/5"), 1024); + EXPECT_EQ(readAs("1024a"), 1024); + EXPECT_EQ(readAs("a1024a"), std::nullopt); + } + + TEST(StringUtils, capitalize) + { + struct TestCase + { + std::string input; + std::string expectedOutput; + }; + + TestCase tests[] + { + {"", ""}, + {"C", "C"}, + {"c", "C"}, + {" c", " C"}, + {" cc", " Cc"}, + {"(c", "(c"}, + {"1c", "1c"}, + {"&c", "&c"}, + {"c c", "C c"} + }; + + for (const TestCase& test : tests) + { + std::string str{ test.input }; + capitalize(str); + EXPECT_EQ(str, test.expectedOutput) << " str was '" << test.input << "'"; + } + } + + TEST(Stringutils, date) + { + const Wt::WDate date{ 2020, 01, 03 }; + EXPECT_EQ(toISO8601String(date), "2020-01-03"); + } + + TEST(Stringutils, dateTime) + { + const Wt::WDateTime dateTime{ Wt::WDate {2020, 01, 03 }, Wt::WTime{9, 8, 11, 75} }; + EXPECT_EQ(toISO8601String(dateTime), "2020-01-03T09:08:11.075"); + } + + TEST(StringUtils, stringEndsWith) + { + EXPECT_TRUE(stringEndsWith("FooBar", "Bar")); + EXPECT_TRUE(stringEndsWith("FooBar", "")); + EXPECT_TRUE(stringEndsWith("", "")); + EXPECT_TRUE(stringEndsWith("FooBar", "ar")); + EXPECT_TRUE(stringEndsWith("FooBar", "FooBar")); + EXPECT_FALSE(stringEndsWith("FooBar", "1FooBar")); + EXPECT_FALSE(stringEndsWith("FooBar", "1FooBar")); + EXPECT_FALSE(stringEndsWith("FooBar", "R")); + } +} \ No newline at end of file diff --git a/src/libs/utils/test/TraceLogger.cpp b/src/libs/core/test/TraceLogger.cpp similarity index 95% rename from src/libs/utils/test/TraceLogger.cpp rename to src/libs/core/test/TraceLogger.cpp index f6aca813..6d7f1beb 100644 --- a/src/libs/utils/test/TraceLogger.cpp +++ b/src/libs/core/test/TraceLogger.cpp @@ -21,9 +21,9 @@ #include #include -#include "utils/ITraceLogger.hpp" +#include "core/ITraceLogger.hpp" -namespace tracing::tests +namespace lms::core::tracing::tests { // not much can be tested with this implementation TEST(TraceLogger, MultipleThreads) @@ -45,7 +45,7 @@ namespace tracing::tests std::ostringstream oss; traceLogger->dumpCurrentBuffer(oss); - + EXPECT_NE(oss.str().find("MyEventLogged"), std::string::npos); EXPECT_EQ(oss.str().find("MyEventNotLogged"), std::string::npos); } diff --git a/src/libs/utils/test/Utils.cpp b/src/libs/core/test/Utils.cpp similarity index 100% rename from src/libs/utils/test/Utils.cpp rename to src/libs/core/test/Utils.cpp diff --git a/src/libs/database/CMakeLists.txt b/src/libs/database/CMakeLists.txt index 7188f977..23b742df 100644 --- a/src/libs/database/CMakeLists.txt +++ b/src/libs/database/CMakeLists.txt @@ -37,7 +37,7 @@ target_link_libraries(lmsdatabase PRIVATE ) target_link_libraries(lmsdatabase PUBLIC - lmsutils + lmscore std::filesystem Wt::Dbo ) diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index 9c8eda7d..3c0bf85e 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -25,13 +25,13 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "SqlQuery.hpp" #include "Utils.hpp" #include "EnumSetTraits.hpp" #include "IdTypeTraits.hpp" -namespace Database +namespace lms::db { namespace { @@ -67,16 +67,16 @@ namespace Database for (std::string_view keyword : params.keywords) { clauses.push_back("a.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'"); - query.bind("%" + Utils::escapeLikeKeyword(keyword) + "%"); + query.bind("%" + utils::escapeLikeKeyword(keyword) + "%"); } for (std::string_view keyword : params.keywords) { sortClauses.push_back("a.sort_name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'"); - query.bind("%" + Utils::escapeLikeKeyword(keyword) + "%"); + query.bind("%" + utils::escapeLikeKeyword(keyword) + "%"); } - query.where("(" + StringUtils::joinStrings(clauses, " AND ") + ") OR (" + StringUtils::joinStrings(sortClauses, " AND ") + ")"); + query.where("(" + core::stringUtils::joinStrings(clauses, " AND ") + ") OR (" + core::stringUtils::joinStrings(sortClauses, " AND ") + ")"); } if (params.starringUser.isValid()) @@ -165,16 +165,16 @@ namespace Database } } - Artist::Artist(const std::string& name, const std::optional& MBID) + Artist::Artist(const std::string& name, const std::optional& MBID) : _name{ std::string(name, 0 , _maxNameLength) }, _sortName{ _name }, _MBID{ MBID ? MBID->getAsString() : "" } { } - Artist::pointer Artist::create(Session& session, const std::string& name, const std::optional& MBID) + Artist::pointer Artist::create(Session& session, const std::string& name, const std::optional& MBID) { - return session.getDboSession().add(std::unique_ptr {new Artist{ name, MBID }}); + return session.getDboSession().add(std::unique_ptr{ new Artist{ name, MBID } }); } std::size_t Artist::getCount(Session& session) @@ -195,7 +195,7 @@ namespace Database return std::vector(res.begin(), res.end()); } - Artist::pointer Artist::find(Session& session, const UUID& mbid) + Artist::pointer Artist::find(Session& session, const core::UUID& mbid) { session.checkReadTransaction(); return session.getDboSession().find().where("mbid = ?").bind(std::string{ mbid.getAsString() }).resultValue(); @@ -218,7 +218,7 @@ namespace Database { session.checkReadTransaction(); auto query{ session.getDboSession().query("SELECT DISTINCT a.id 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 Utils::execQuery(query, range); + return utils::execQuery(query, range); } RangeResults Artist::findIds(Session& session, const FindParameters& params) @@ -226,7 +226,7 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery(session, params) }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } RangeResults Artist::find(Session& session, const FindParameters& params) @@ -234,7 +234,7 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery>(session, params) }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } void Artist::find(Session& session, const FindParameters& params, std::function func) @@ -242,10 +242,10 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery>(session, params) }; - Utils::execQuery(query, params.range, func); + utils::execQuery(query, params.range, func); } - RangeResults Artist::findSimilarArtistIds(EnumSet artistLinkTypes, std::optional range) const + RangeResults Artist::findSimilarArtistIds(core::EnumSet artistLinkTypes, std::optional range) const { assert(session()); @@ -289,7 +289,7 @@ namespace Database for (TrackArtistLinkType type : artistLinkTypes) query.bind(type); - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } std::vector> Artist::getClusterGroups(std::vector clusterTypeIds, std::size_t size) const @@ -338,4 +338,4 @@ namespace Database _sortName = std::string(sortName, 0, _maxNameLength); } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/AuthToken.cpp b/src/libs/database/impl/AuthToken.cpp index 6632b843..db3e29bb 100644 --- a/src/libs/database/impl/AuthToken.cpp +++ b/src/libs/database/impl/AuthToken.cpp @@ -25,7 +25,7 @@ #include "StringViewTraits.hpp" #include "IdTypeTraits.hpp" -namespace Database +namespace lms::db { AuthToken::AuthToken(std::string_view value, const Wt::WDateTime& expiry, ObjectPtr user) diff --git a/src/libs/database/impl/Cluster.cpp b/src/libs/database/impl/Cluster.cpp index d9842ff0..37dc1f89 100644 --- a/src/libs/database/impl/Cluster.cpp +++ b/src/libs/database/impl/Cluster.cpp @@ -29,7 +29,7 @@ #include "SqlQuery.hpp" #include "Utils.hpp" -namespace Database +namespace lms::db { namespace { @@ -101,7 +101,7 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery(session, params) }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } RangeResults Cluster::find(Session& session, const FindParameters& params) @@ -109,7 +109,7 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery>(session, params) }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } RangeResults Cluster::findOrphanIds(Session& session, std::optional range) @@ -117,7 +117,7 @@ namespace Database session.checkReadTransaction(); auto query{ session.getDboSession().query("SELECT DISTINCT c.id FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)") }; - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } Cluster::pointer Cluster::find(Session& session, ClusterId id) @@ -155,7 +155,7 @@ namespace Database auto query{ session()->query("SELECT t.id FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id") .where("c.id = ?").bind(getId()) }; - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } ClusterType::ClusterType(std::string_view name) @@ -185,7 +185,7 @@ namespace Database " LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id") .where("c.id IS NULL") }; - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } RangeResults ClusterType::findUsed(Session& session, std::optional range) @@ -196,7 +196,7 @@ namespace Database "SELECT DISTINCT c_t.id from cluster_type c_t") .join("cluster c ON c_t.id = c.cluster_type_id") }; - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } ClusterType::pointer ClusterType::find(Session& session, std::string_view name) @@ -219,7 +219,7 @@ namespace Database auto query{ session.getDboSession().query("SELECT id from cluster_type") }; - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } Cluster::pointer ClusterType::getCluster(const std::string& name) const @@ -244,4 +244,4 @@ namespace Database return std::vector(res.begin(), res.end()); } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/Db.cpp b/src/libs/database/impl/Db.cpp index 1532965d..d756d1f5 100644 --- a/src/libs/database/impl/Db.cpp +++ b/src/libs/database/impl/Db.cpp @@ -24,11 +24,11 @@ #include "database/Session.hpp" #include "database/User.hpp" -#include "utils/IConfig.hpp" -#include "utils/Service.hpp" -#include "utils/ILogger.hpp" +#include "core/IConfig.hpp" +#include "core/Service.hpp" +#include "core/ILogger.hpp" -namespace Database +namespace lms::db { namespace { @@ -89,7 +89,7 @@ namespace Database LMS_LOG(DB, INFO, "Creating connection pool on file " << dbPath.string()); auto connection{ std::make_unique(dbPath.string()) }; - if (IConfig * config{ Service::get() })// may not be here on testU + if (core::IConfig * config{ core::Service::get() })// may not be here on testU connection->setProperty("show-queries", config->getBool("db-show-queries", false) ? "true" : "false"); auto connectionPool{ std::make_unique(std::move(connection), connectionCount) }; @@ -138,4 +138,4 @@ namespace Database return _connection.get(); } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/EnumSetTraits.hpp b/src/libs/database/impl/EnumSetTraits.hpp index 3c29a928..3852babd 100644 --- a/src/libs/database/impl/EnumSetTraits.hpp +++ b/src/libs/database/impl/EnumSetTraits.hpp @@ -22,22 +22,22 @@ #include #include -#include "utils/EnumSet.hpp" +#include "core/EnumSet.hpp" namespace Wt::Dbo { template - struct sql_value_traits, void> : public sql_value_traits + struct sql_value_traits, void> : public sql_value_traits { - using ValueType = typename EnumSet::ValueType; + using ValueType = typename lms::core::EnumSet::ValueType; static_assert(sizeof(long long) > sizeof(ValueType)); - static void bind(EnumSet v, SqlStatement* statement, int column, int size) + static void bind(lms::core::EnumSet v, SqlStatement* statement, int column, int size) { sql_value_traits::bind(static_cast(v.getBitfield()), statement, column, size); } - static bool read(EnumSet& v, SqlStatement* statement, int column, int size) + static bool read(lms::core::EnumSet& v, SqlStatement* statement, int column, int size) { long long val; if (sql_value_traits::read(val, statement, column, size)) diff --git a/src/libs/database/impl/IdTypeTraits.hpp b/src/libs/database/impl/IdTypeTraits.hpp index 42dd2b2e..8158d50a 100644 --- a/src/libs/database/impl/IdTypeTraits.hpp +++ b/src/libs/database/impl/IdTypeTraits.hpp @@ -27,9 +27,9 @@ namespace Wt::Dbo { template - struct sql_value_traits::value>::type> + struct sql_value_traits::value>::type> { - static_assert(!std::is_same_v, "Cannot use IdType, use derived types"); + static_assert(!std::is_same_v, "Cannot use IdType, use derived types"); static const bool specialized = true; static std::string type(SqlConnection* conn, int size) diff --git a/src/libs/database/impl/Listen.cpp b/src/libs/database/impl/Listen.cpp index ae173cd6..291fe119 100644 --- a/src/libs/database/impl/Listen.cpp +++ b/src/libs/database/impl/Listen.cpp @@ -25,7 +25,7 @@ #include "SqlQuery.hpp" #include "Utils.hpp" -namespace Database +namespace lms::db { namespace { @@ -205,7 +205,7 @@ namespace Database if (parameters.syncState) query.where("sync_state = ?").bind(*parameters.syncState); - return Utils::execQuery(query, parameters.range); + return utils::execQuery(query, parameters.range); } Listen::pointer Listen::find(Session& session, UserId userId, TrackId trackId, ScrobblingBackend backend, const Wt::WDateTime& dateTime) @@ -229,7 +229,7 @@ namespace Database .orderBy("COUNT(a.id) DESC") .groupBy("a.id") }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } RangeResults Listen::getTopReleases(Session& session, const StatsFindParameters& params) @@ -239,7 +239,7 @@ namespace Database .orderBy("COUNT(r.id) DESC") .groupBy("r.id") }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } RangeResults Listen::getTopTracks(Session& session, const StatsFindParameters& params) @@ -249,7 +249,7 @@ namespace Database .orderBy("COUNT(t.id) DESC") .groupBy("t.id") }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } RangeResults Listen::getRecentArtists(Session& session, const ArtistStatsFindParameters& params) @@ -259,7 +259,7 @@ namespace Database .groupBy("a.id").having("l.date_time = MAX(l.date_time)") .orderBy("l.date_time DESC") }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } RangeResults Listen::getRecentReleases(Session& session, const StatsFindParameters& params) @@ -269,7 +269,7 @@ namespace Database .groupBy("r.id").having("l.date_time = MAX(l.date_time)") .orderBy("l.date_time DESC") }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } RangeResults Listen::getRecentTracks(Session& session, const StatsFindParameters& params) @@ -279,7 +279,7 @@ namespace Database .groupBy("t.id").having("l.date_time = MAX(l.date_time)") .orderBy("l.date_time DESC") }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } std::size_t Listen::getCount(Session& session, UserId userId, TrackId trackId) @@ -339,4 +339,4 @@ namespace Database .limit(1) .resultValue(); } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/MediaLibrary.cpp b/src/libs/database/impl/MediaLibrary.cpp index 3732bb26..cd7e7032 100644 --- a/src/libs/database/impl/MediaLibrary.cpp +++ b/src/libs/database/impl/MediaLibrary.cpp @@ -26,7 +26,7 @@ #include "PathTraits.hpp" #include "StringViewTraits.hpp" -namespace Database +namespace lms::db { MediaLibrary::MediaLibrary(const std::filesystem::path& p, std::string_view name) : _path{ p }, @@ -75,4 +75,4 @@ namespace Database for (const auto& result : results) func(result); } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index 746d222b..214e64f0 100644 --- a/src/libs/database/impl/Migration.cpp +++ b/src/libs/database/impl/Migration.cpp @@ -25,10 +25,10 @@ #include "database/ScanSettings.hpp" #include "database/Session.hpp" #include "database/User.hpp" -#include "utils/Exception.hpp" -#include "utils/ILogger.hpp" +#include "core/Exception.hpp" +#include "core/ILogger.hpp" -namespace Database +namespace lms::db { namespace { @@ -58,7 +58,7 @@ namespace Database } } -namespace Database::Migration +namespace lms::db::Migration { class ScopedNoForeignKeys { @@ -473,14 +473,14 @@ SELECT catch (std::exception& e) { LMS_LOG(DB, ERROR, "Cannot get database version info: " << e.what()); - throw LmsException{ outdatedMsg }; + throw core::LmsException{ outdatedMsg }; } if (version > LMS_DATABASE_VERSION) - throw LmsException{ "Server binary outdated, please upgrade it to handle this database" }; + throw core::LmsException{ "Server binary outdated, please upgrade it to handle this database" }; if (version < migrationFunctions.begin()->first) - throw LmsException{ outdatedMsg }; + throw core::LmsException{ outdatedMsg }; while (version < LMS_DATABASE_VERSION) { diff --git a/src/libs/database/impl/Migration.hpp b/src/libs/database/impl/Migration.hpp index 559c7f29..5263e9e7 100644 --- a/src/libs/database/impl/Migration.hpp +++ b/src/libs/database/impl/Migration.hpp @@ -21,7 +21,7 @@ #include -namespace Database +namespace lms::db { class Session; diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/Release.cpp index 8997318d..f13769f1 100644 --- a/src/libs/database/impl/Release.cpp +++ b/src/libs/database/impl/Release.cpp @@ -26,14 +26,14 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "SqlQuery.hpp" #include "EnumSetTraits.hpp" #include "IdTypeTraits.hpp" #include "StringViewTraits.hpp" #include "Utils.hpp" -namespace Database +namespace lms::db { namespace { @@ -76,7 +76,7 @@ namespace Database } for (std::string_view keyword : params.keywords) - query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + Utils::escapeLikeKeyword(keyword) + "%"); + query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeLikeKeyword(keyword) + "%"); if (params.starringUser.isValid()) { @@ -227,13 +227,13 @@ namespace Database .resultValue(); } - Release::Release(const std::string& name, const std::optional& MBID) + Release::Release(const std::string& name, const std::optional& MBID) : _name{ std::string(name, 0 , _maxNameLength) }, _MBID{ MBID ? MBID->getAsString() : "" } { } - Release::pointer Release::create(Session& session, const std::string& name, const std::optional& MBID) + Release::pointer Release::create(Session& session, const std::string& name, const std::optional& MBID) { return session.getDboSession().add(std::unique_ptr {new Release{ name, MBID }}); } @@ -246,13 +246,13 @@ namespace Database .query>("SELECT DISTINCT r from release r") .join("track t ON t.release_id = r.id") .where("r.name = ?").bind(std::string(name, 0, _maxNameLength)) - .where("t.file_path LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind(Utils::escapeLikeKeyword(releaseDirectory.string()) + "%") + .where("t.file_path LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind(utils::escapeLikeKeyword(releaseDirectory.string()) + "%") .resultList() }; return std::vector(res.begin(), res.end()); } - Release::pointer Release::find(Session& session, const UUID& mbid) + Release::pointer Release::find(Session& session, const core::UUID& mbid) { session.checkReadTransaction(); @@ -290,7 +290,7 @@ namespace Database session.checkReadTransaction(); auto query{ session.getDboSession().query("select r.id from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL") }; - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } RangeResults Release::find(Session& session, const FindParameters& params) @@ -298,7 +298,7 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery>(session, "DISTINCT r", params) }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } void Release::find(Session& session, const FindParameters& params, std::function func) @@ -306,7 +306,7 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery>(session, "DISTINCT r", params) }; - Utils::execQuery(query, params.range, func); + utils::execQuery(query, params.range, func); } RangeResults Release::findIds(Session& session, const FindParameters& params) @@ -314,7 +314,7 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery(session, "DISTINCT r.id", params) }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } std::size_t Release::getCount(Session& session, const FindParameters& params) @@ -602,4 +602,4 @@ namespace Database return res; } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/ScanSettings.cpp b/src/libs/database/impl/ScanSettings.cpp index 5c8d35b6..6daa1496 100644 --- a/src/libs/database/impl/ScanSettings.cpp +++ b/src/libs/database/impl/ScanSettings.cpp @@ -23,9 +23,9 @@ #include "database/MediaLibrary.hpp" #include "database/Session.hpp" -#include "utils/String.hpp" +#include "core/String.hpp" -namespace Database +namespace lms::db { void ScanSettings::init(Session& session) { @@ -46,7 +46,7 @@ namespace Database std::vector ScanSettings::getAudioFileExtensions() const { - const auto extensions{ StringUtils::splitString(_audioFileExtensions, ' ') }; + const auto extensions{ core::stringUtils::splitString(_audioFileExtensions, ' ') }; std::vector res(std::cbegin(extensions), std::cend(extensions)); std::sort(std::begin(res), std::end(res)); @@ -57,22 +57,22 @@ namespace Database std::vector ScanSettings::getExtraTagsToScan() const { - return StringUtils::splitString(_extraTagsToScan, ';'); + return core::stringUtils::splitString(_extraTagsToScan, ';'); } std::vector ScanSettings::getArtistTagDelimiters() const { - return StringUtils::splitEscapedStrings(_artistTagDelimiters, ';', '\\'); + return core::stringUtils::splitEscapedStrings(_artistTagDelimiters, ';', '\\'); } std::vector ScanSettings::getDefaultTagDelimiters() const { - return StringUtils::splitEscapedStrings(_defaultTagDelimiters, ';', '\\'); + return core::stringUtils::splitEscapedStrings(_defaultTagDelimiters, ';', '\\'); } void ScanSettings::setExtraTagsToScan(const std::vector& extraTags) { - std::string newTagsToScan{ StringUtils::joinStrings(extraTags, ";") }; + std::string newTagsToScan{ core::stringUtils::joinStrings(extraTags, ";") }; if (newTagsToScan != _extraTagsToScan) incScanVersion(); @@ -81,7 +81,7 @@ namespace Database void ScanSettings::setArtistTagDelimiters(std::span delimiters) { - std::string tagDelimiters{ StringUtils::escapeAndJoinStrings(delimiters, ';', '\\') }; + std::string tagDelimiters{ core::stringUtils::escapeAndJoinStrings(delimiters, ';', '\\') }; if (tagDelimiters != _artistTagDelimiters) { _artistTagDelimiters.swap(tagDelimiters); @@ -91,7 +91,7 @@ namespace Database void ScanSettings::setDefaultTagDelimiters(std::span delimiters) { - std::string tagDelimiters{ StringUtils::escapeAndJoinStrings(delimiters, ';', '\\') }; + std::string tagDelimiters{ core::stringUtils::escapeAndJoinStrings(delimiters, ';', '\\') }; if (tagDelimiters != _defaultTagDelimiters) { _defaultTagDelimiters.swap(tagDelimiters); @@ -103,4 +103,4 @@ namespace Database { _scanVersion += 1; } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 9d977a27..f3f4e4af 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -21,9 +21,9 @@ #include -#include "utils/Exception.hpp" -#include "utils/ILogger.hpp" -#include "utils/ITraceLogger.hpp" +#include "core/Exception.hpp" +#include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" #include "database/Artist.hpp" #include "database/AuthToken.hpp" @@ -47,10 +47,9 @@ #include "PathTraits.hpp" #include "Migration.hpp" -namespace Database +namespace lms::db { - - WriteTransaction::WriteTransaction(RecursiveSharedMutex& mutex, Wt::Dbo::Session& session) + WriteTransaction::WriteTransaction(core::RecursiveSharedMutex& mutex, Wt::Dbo::Session& session) : _lock{ mutex }, _transaction{ session } { @@ -210,4 +209,4 @@ namespace Database LMS_LOG(DB, INFO, "Database optimizing complete"); } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/SqlQuery.cpp b/src/libs/database/impl/SqlQuery.cpp index 643f8939..ca88032c 100644 --- a/src/libs/database/impl/SqlQuery.cpp +++ b/src/libs/database/impl/SqlQuery.cpp @@ -23,177 +23,166 @@ #include #include -WhereClause& -WhereClause::And(const WhereClause& otherClause) +namespace lms::db { - if (!otherClause._clause.empty()) { - if (!_clause.empty()) - _clause += " AND "; - _clause += "(" + otherClause._clause + ")"; + WhereClause& WhereClause::And(const WhereClause& otherClause) + { + if (!otherClause._clause.empty()) { + if (!_clause.empty()) + _clause += " AND "; + _clause += "(" + otherClause._clause + ")"; - // Add associated bind args - for (const std::string& otherBindArg : otherClause._bindArgs) - { - _bindArgs.push_back(otherBindArg); - } - } - return *this; -} + // Add associated bind args + for (const std::string& otherBindArg : otherClause._bindArgs) + { + _bindArgs.push_back(otherBindArg); + } + } + return *this; + } -WhereClause& -WhereClause::Or(const WhereClause& otherClause) -{ - if (!otherClause._clause.empty()) { - if (!_clause.empty()) - _clause += " OR "; - _clause += "(" + otherClause._clause + ")"; + WhereClause& WhereClause::Or(const WhereClause& otherClause) + { + if (!otherClause._clause.empty()) { + if (!_clause.empty()) + _clause += " OR "; + _clause += "(" + otherClause._clause + ")"; - // Add associated bind args - for (const std::string& otherBindArg : otherClause._bindArgs) - { - _bindArgs.push_back(otherBindArg); - } - } - return *this; -} + // Add associated bind args + for (const std::string& otherBindArg : otherClause._bindArgs) + { + _bindArgs.push_back(otherBindArg); + } + } + return *this; + } -std::string -WhereClause::get() const -{ - if (!_clause.empty()) - return "WHERE " + _clause; - else - return ""; -} + std::string WhereClause::get() const + { + if (!_clause.empty()) + return "WHERE " + _clause; + else + return ""; + } -WhereClause& -WhereClause::bind(std::string_view bindArg) -{ - assert(_bindArgs.size() < static_cast(std::count(_clause.begin(), _clause.end(), '?'))); + WhereClause& WhereClause::bind(std::string_view bindArg) + { + assert(_bindArgs.size() < static_cast(std::count(_clause.begin(), _clause.end(), '?'))); - _bindArgs.push_back(std::string{ bindArg }); + _bindArgs.push_back(std::string{ bindArg }); - return *this; -} + return *this; + } -InnerJoinClause::InnerJoinClause(const std::string& clause) -:_clause(clause) -{ -} + InnerJoinClause::InnerJoinClause(const std::string& clause) + :_clause(clause) + { + } -InnerJoinClause& -InnerJoinClause::And(const InnerJoinClause& clause) -{ - if (!_clause.empty()) - _clause += " "; + InnerJoinClause& InnerJoinClause::And(const InnerJoinClause& clause) + { + if (!_clause.empty()) + _clause += " "; - _clause += "INNER JOIN " + clause._clause; + _clause += "INNER JOIN " + clause._clause; - return *this; -} + return *this; + } -SelectStatement::SelectStatement(const std::string& statement) -{ - And(statement); -} + SelectStatement::SelectStatement(const std::string& statement) + { + And(statement); + } -SelectStatement& -SelectStatement::And(const std::string& statement) -{ - _statement.push_back(statement); + SelectStatement& SelectStatement::And(const std::string& statement) + { + _statement.push_back(statement); - std::sort(_statement.begin(), _statement.end()); - _statement.erase(std::unique(_statement.begin(), _statement.end()), _statement.end()); + std::sort(_statement.begin(), _statement.end()); + _statement.erase(std::unique(_statement.begin(), _statement.end()), _statement.end()); - return *this; -} + return *this; + } -std::string -SelectStatement::get() const -{ - std::string res = "SELECT "; + std::string SelectStatement::get() const + { + std::string res = "SELECT "; - for (auto it = _statement.begin(); it != _statement.end(); ++it) - { - if (it != _statement.begin()) - res += ","; - res += *it; - } + for (auto it = _statement.begin(); it != _statement.end(); ++it) + { + if (it != _statement.begin()) + res += ","; + res += *it; + } - return res; -} + return res; + } + GroupByStatement& GroupByStatement::And(const GroupByStatement& statement) + { + if (_statement.empty() && !statement._statement.empty()) + _statement = "GROUP BY "; + else if (!_statement.empty() && !statement._statement.empty()) + _statement += ","; + _statement += statement._statement; + return *this; + } -GroupByStatement& -GroupByStatement::And(const GroupByStatement& statement) -{ - if( _statement.empty() && !statement._statement.empty()) - _statement = "GROUP BY "; - else if (!_statement.empty() && !statement._statement.empty()) - _statement += ","; + FromClause::FromClause(const std::string& clause) + { + _clause.push_back(clause); + } - _statement += statement._statement; - return *this; -} + FromClause& FromClause::And(const FromClause& clause) + { + for (const std::string& fromClause : clause._clause) + { + _clause.push_back(fromClause); + } -FromClause::FromClause(const std::string& clause) -{ - _clause.push_back(clause); -} + std::sort(_clause.begin(), _clause.end()); + _clause.erase(std::unique(_clause.begin(), _clause.end()), _clause.end()); -FromClause& -FromClause::And(const FromClause& clause) -{ - for (const std::string& fromClause : clause._clause) - { - _clause.push_back(fromClause); - } + return *this; + } - std::sort(_clause.begin(), _clause.end()); - _clause.erase(std::unique(_clause.begin(), _clause.end()), _clause.end()); + std::string FromClause::get() const + { + std::ostringstream oss; - return *this; -} + if (!_clause.empty()) + { + oss << "FROM "; + for (auto it = _clause.begin(); it != _clause.end(); ++it) { + if (it != _clause.begin()) + oss << ","; -std::string -FromClause::get() const -{ - std::ostringstream oss; + oss << *it; + } + } - if (!_clause.empty()) - { - oss << "FROM "; - for (auto it = _clause.begin(); it != _clause.end(); ++it) { - if (it != _clause.begin()) - oss << ","; + return oss.str(); + } - oss << *it; - } - } + std::string SqlQuery::get() const + { + std::ostringstream oss; - return oss.str(); -} + oss << _selectStatement.get(); -std::string -SqlQuery::get() const -{ - std::ostringstream oss; + if (!_fromClause.get().empty()) + oss << " " << _fromClause.get(); - oss << _selectStatement.get(); + if (!_innerJoinClause.get().empty()) + oss << " " << _innerJoinClause.get(); - if (!_fromClause.get().empty()) - oss << " " << _fromClause.get(); + if (!_whereClause.get().empty()) + oss << " " << _whereClause.get(); - if (!_innerJoinClause.get().empty()) - oss << " " << _innerJoinClause.get(); - - if (!_whereClause.get().empty()) - oss << " " << _whereClause.get(); - - if (!_groupByStatement.get().empty()) - oss << " " << _groupByStatement.get(); - - return oss.str(); -} + if (!_groupByStatement.get().empty()) + oss << " " << _groupByStatement.get(); + return oss.str(); + } +} \ No newline at end of file diff --git a/src/libs/database/impl/SqlQuery.hpp b/src/libs/database/impl/SqlQuery.hpp index e6625f7c..cd3afeec 100644 --- a/src/libs/database/impl/SqlQuery.hpp +++ b/src/libs/database/impl/SqlQuery.hpp @@ -22,102 +22,103 @@ #include #include - -class WhereClause +namespace lms::db { - public: - WhereClause() {} - WhereClause(const std::string& clause) { _clause = clause; } + class WhereClause + { + public: + WhereClause() {} + WhereClause(const std::string& clause) { _clause = clause; } - WhereClause& And(const WhereClause& clause); - WhereClause& Or(const WhereClause& clause); + WhereClause& And(const WhereClause& clause); + WhereClause& Or(const WhereClause& clause); - // Arguments binding (for each '?' in where clause) - WhereClause& bind(std::string_view arg); + // Arguments binding (for each '?' in where clause) + WhereClause& bind(std::string_view arg); - std::string get() const; - const std::vector& getBindArgs() const {return _bindArgs;} + std::string get() const; + const std::vector& getBindArgs() const { return _bindArgs; } - private: - std::string _clause; // WHERE clause - std::vector _bindArgs; -}; + private: + std::string _clause; // WHERE clause + std::vector _bindArgs; + }; -class InnerJoinClause -{ - public: - InnerJoinClause() {} - InnerJoinClause(const std::string& clause); + class InnerJoinClause + { + public: + InnerJoinClause() {} + InnerJoinClause(const std::string& clause); - InnerJoinClause& And(const InnerJoinClause& clause); - std::string get() const { return _clause;} + InnerJoinClause& And(const InnerJoinClause& clause); + std::string get() const { return _clause; } - private: - std::string _clause; -}; + private: + std::string _clause; + }; -class GroupByStatement -{ - public: - GroupByStatement() {} - GroupByStatement(const std::string& statement) { _statement = statement; } + class GroupByStatement + { + public: + GroupByStatement() {} + GroupByStatement(const std::string& statement) { _statement = statement; } - GroupByStatement& And(const GroupByStatement& statement); + GroupByStatement& And(const GroupByStatement& statement); - std::string get() const {return _statement;} + std::string get() const { return _statement; } - private: - std::string _statement; // SELECT statement -}; + private: + std::string _statement; // SELECT statement + }; -class SelectStatement -{ - public: - SelectStatement() {}; - SelectStatement(const std::string& item); + class SelectStatement + { + public: + SelectStatement() {}; + SelectStatement(const std::string& item); - SelectStatement& And(const std::string& item); + SelectStatement& And(const std::string& item); - std::string get() const; + std::string get() const; - private: - std::vector _statement; -}; + private: + std::vector _statement; + }; -class FromClause -{ - public: - FromClause() {} - FromClause(const std::string& clause); + class FromClause + { + public: + FromClause() {} + FromClause(const std::string& clause); - FromClause& And(const FromClause& clause); + FromClause& And(const FromClause& clause); - std::string get() const; + std::string get() const; - private: - std::vector _clause; -}; + private: + std::vector _clause; + }; -class SqlQuery -{ - public: - SelectStatement& select() { return _selectStatement;} - SelectStatement& select(const std::string& statement) { _selectStatement = SelectStatement(statement); return _selectStatement; } - FromClause& from() { return _fromClause; } - FromClause& from(const std::string& clause) { _whereClause = WhereClause(clause); return _fromClause; } - InnerJoinClause& innerJoin() { return _innerJoinClause; } - WhereClause& where() { return _whereClause; } - const WhereClause& where() const { return _whereClause; } - GroupByStatement& groupBy() { return _groupByStatement; } - const GroupByStatement& groupBy() const { return _groupByStatement; } + class SqlQuery + { + public: + SelectStatement& select() { return _selectStatement; } + SelectStatement& select(const std::string& statement) { _selectStatement = SelectStatement(statement); return _selectStatement; } + FromClause& from() { return _fromClause; } + FromClause& from(const std::string& clause) { _whereClause = WhereClause(clause); return _fromClause; } + InnerJoinClause& innerJoin() { return _innerJoinClause; } + WhereClause& where() { return _whereClause; } + const WhereClause& where() const { return _whereClause; } + GroupByStatement& groupBy() { return _groupByStatement; } + const GroupByStatement& groupBy() const { return _groupByStatement; } - std::string get() const; - - private: - SelectStatement _selectStatement; // SELECT statement - InnerJoinClause _innerJoinClause; // INNER JOIN - FromClause _fromClause; // FROM tables - WhereClause _whereClause; // WHERE clause - GroupByStatement _groupByStatement; // GROUP BY statement -}; + std::string get() const; + private: + SelectStatement _selectStatement; // SELECT statement + InnerJoinClause _innerJoinClause; // INNER JOIN + FromClause _fromClause; // FROM tables + WhereClause _whereClause; // WHERE clause + GroupByStatement _groupByStatement; // GROUP BY statement + }; +} \ No newline at end of file diff --git a/src/libs/database/impl/StarredArtist.cpp b/src/libs/database/impl/StarredArtist.cpp index be0c7b47..56c48d11 100644 --- a/src/libs/database/impl/StarredArtist.cpp +++ b/src/libs/database/impl/StarredArtist.cpp @@ -27,7 +27,7 @@ #include "IdTypeTraits.hpp" #include "Utils.hpp" -namespace Database +namespace lms::db { StarredArtist::StarredArtist(ObjectPtr artist, ObjectPtr user, FeedbackBackend backend) : _backend{ backend } @@ -76,6 +76,6 @@ namespace Database void StarredArtist::setDateTime(const Wt::WDateTime& dateTime) { - _dateTime = Utils::normalizeDateTime(dateTime); + _dateTime = utils::normalizeDateTime(dateTime); } } diff --git a/src/libs/database/impl/StarredRelease.cpp b/src/libs/database/impl/StarredRelease.cpp index e2aac889..1e01d4aa 100644 --- a/src/libs/database/impl/StarredRelease.cpp +++ b/src/libs/database/impl/StarredRelease.cpp @@ -27,7 +27,7 @@ #include "IdTypeTraits.hpp" #include "Utils.hpp" -namespace Database +namespace lms::db { StarredRelease::StarredRelease(ObjectPtr release, ObjectPtr user, FeedbackBackend backend) : _backend{ backend } @@ -76,6 +76,6 @@ namespace Database void StarredRelease::setDateTime(const Wt::WDateTime& dateTime) { - _dateTime = Utils::normalizeDateTime(dateTime); + _dateTime = utils::normalizeDateTime(dateTime); } } diff --git a/src/libs/database/impl/StarredTrack.cpp b/src/libs/database/impl/StarredTrack.cpp index df90fca6..e3061a1a 100644 --- a/src/libs/database/impl/StarredTrack.cpp +++ b/src/libs/database/impl/StarredTrack.cpp @@ -27,7 +27,7 @@ #include "IdTypeTraits.hpp" #include "Utils.hpp" -namespace Database +namespace lms::db { StarredTrack::StarredTrack(ObjectPtr track, ObjectPtr user, FeedbackBackend backend) : _backend{ backend } @@ -96,11 +96,11 @@ namespace Database if (params.user.isValid()) query.where("s_t.user_id = ?").bind(params.user); - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } void StarredTrack::setDateTime(const Wt::WDateTime& dateTime) { - _dateTime = Utils::normalizeDateTime(dateTime); + _dateTime = utils::normalizeDateTime(dateTime); } } diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp index 53fabd4e..d9f718ff 100644 --- a/src/libs/database/impl/Track.cpp +++ b/src/libs/database/impl/Track.cpp @@ -29,14 +29,14 @@ #include "database/TrackFeatures.hpp" #include "database/Session.hpp" #include "database/User.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "IdTypeTraits.hpp" #include "SqlQuery.hpp" #include "StringViewTraits.hpp" #include "Utils.hpp" -namespace Database +namespace lms::db { namespace { @@ -49,7 +49,7 @@ namespace Database assert(params.keywords.empty() || params.name.empty()); for (std::string_view keyword : params.keywords) - query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + Utils::escapeLikeKeyword(keyword) + "%"); + query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeLikeKeyword(keyword) + "%"); if (!params.name.empty()) query.where("t.name = ?").bind(params.name); @@ -235,7 +235,7 @@ namespace Database return session.getDboSession().query("SELECT 1 from track").where("id = ?").bind(id).resultValue() == 1; } - std::vector Track::findByMBID(Session& session, const UUID& mbid) + std::vector Track::findByMBID(Session& session, const core::UUID& mbid) { session.checkReadTransaction(); @@ -246,7 +246,7 @@ namespace Database return std::vector(res.begin(), res.end()); } - std::vector Track::findByRecordingMBID(Session& session, const UUID& mbid) + std::vector Track::findByRecordingMBID(Session& session, const core::UUID& mbid) { session.checkReadTransaction(); @@ -265,7 +265,7 @@ namespace Database // TODO Dbo traits on filesystem auto query{ session.getDboSession().query("SELECT id, file_path FROM track") }; - RangeResults queryResults{ Utils::execQuery(query, range) }; + RangeResults queryResults{ utils::execQuery(query, range) }; RangeResults res; res.range = queryResults.range; @@ -288,7 +288,7 @@ namespace Database auto query{ session.getDboSession().query("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)") .orderBy("track.release_id,track.disc_number,track.track_number,track.mbid") }; - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } RangeResults Track::findIdsWithRecordingMBIDAndMissingFeatures(Session& session, std::optional range) @@ -299,7 +299,7 @@ namespace Database .where("LENGTH(t.recording_mbid) > 0") .where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)") }; - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } std::vector Track::getClusters() const @@ -324,7 +324,7 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery(session, parameters) }; - return Utils::execQuery(query, parameters.range); + return utils::execQuery(query, parameters.range); } RangeResults Track::find(Session& session, const FindParameters& parameters) @@ -332,7 +332,7 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery>(session, parameters) }; - return Utils::execQuery(query, parameters.range); + return utils::execQuery(query, parameters.range); } void Track::find(Session& session, const FindParameters& params, std::function func) @@ -340,7 +340,7 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery>(session, params)}; - Utils::execQuery(query, params.range, func); + utils::execQuery(query, params.range, func); } RangeResults Track::findSimilarTrackIds(Session& session, const std::vector& tracks, std::optional range) @@ -370,7 +370,7 @@ namespace Database for (TrackId trackId : tracks) query.bind(trackId); - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } void Track::clearArtistLinks() @@ -400,7 +400,7 @@ namespace Database return _copyrightURL != "" ? std::make_optional(_copyrightURL) : std::nullopt; } - std::vector Track::getArtists(EnumSet linkTypes) const + std::vector Track::getArtists(core::EnumSet linkTypes) const { assert(session()); @@ -435,7 +435,7 @@ namespace Database return std::vector(std::begin(res), std::end(res)); } - std::vector Track::getArtistIds(EnumSet linkTypes) const + std::vector Track::getArtistIds(core::EnumSet linkTypes) const { assert(self()); assert(session()); @@ -544,4 +544,4 @@ namespace Database } } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/TrackArtistLink.cpp b/src/libs/database/impl/TrackArtistLink.cpp index dd0ff553..6a55dd82 100644 --- a/src/libs/database/impl/TrackArtistLink.cpp +++ b/src/libs/database/impl/TrackArtistLink.cpp @@ -26,7 +26,7 @@ #include "IdTypeTraits.hpp" #include "Utils.hpp" -namespace Database +namespace lms::db { namespace { @@ -84,19 +84,19 @@ namespace Database session.checkReadTransaction(); auto query{ createQuery(session, params) }; - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } - EnumSet TrackArtistLink::findUsedTypes(Session& session) + core::EnumSet TrackArtistLink::findUsedTypes(Session& session) { session.checkReadTransaction(); auto res{ session.getDboSession().query("SELECT DISTINCT type from track_artist_link").resultList() }; - return EnumSet(std::begin(res), std::end(res)); + return core::EnumSet(std::begin(res), std::end(res)); } - EnumSet TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId) + core::EnumSet TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId) { session.checkReadTransaction(); @@ -105,7 +105,7 @@ namespace Database .where("artist_id = ?").bind(artistId) .resultList() }; - return EnumSet(std::begin(res), std::end(res)); + return core::EnumSet(std::begin(res), std::end(res)); } } diff --git a/src/libs/database/impl/TrackBookmark.cpp b/src/libs/database/impl/TrackBookmark.cpp index a30f80a4..b14ad45d 100644 --- a/src/libs/database/impl/TrackBookmark.cpp +++ b/src/libs/database/impl/TrackBookmark.cpp @@ -25,8 +25,8 @@ #include "IdTypeTraits.hpp" #include "Utils.hpp" -namespace Database { - +namespace lms::db +{ TrackBookmark::TrackBookmark(ObjectPtr user, ObjectPtr track) : _user{ getDboPtr(user) }, _track{ getDboPtr(track) } @@ -52,7 +52,7 @@ namespace Database { auto query{ session.getDboSession().query("SELECT id from track_bookmark") .where("user_id = ?").bind(userId) }; - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } TrackBookmark::pointer TrackBookmark::find(Session& session, UserId userId, TrackId trackId) @@ -74,5 +74,5 @@ namespace Database { .resultValue(); } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/TrackFeatures.cpp b/src/libs/database/impl/TrackFeatures.cpp index 6468e826..fde099cb 100644 --- a/src/libs/database/impl/TrackFeatures.cpp +++ b/src/libs/database/impl/TrackFeatures.cpp @@ -24,11 +24,11 @@ #include "database/Session.hpp" #include "database/Track.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "IdTypeTraits.hpp" #include "Utils.hpp" -namespace Database { +namespace lms::db { TrackFeatures::TrackFeatures(ObjectPtr track, const std::string& jsonEncodedFeatures) : _data{ jsonEncodedFeatures }, @@ -72,7 +72,7 @@ namespace Database { auto query{ session.getDboSession().query("SELECT id from track_features") }; - return Utils::execQuery(query, range); + return utils::execQuery(query, range); } FeatureValues TrackFeatures::getFeatureValues(const FeatureName& featureNode) const @@ -118,4 +118,4 @@ namespace Database { return res; } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/TrackList.cpp b/src/libs/database/impl/TrackList.cpp index 974c5afc..e163970f 100644 --- a/src/libs/database/impl/TrackList.cpp +++ b/src/libs/database/impl/TrackList.cpp @@ -20,7 +20,7 @@ #include -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" @@ -33,14 +33,14 @@ #include "IdTypeTraits.hpp" #include "Utils.hpp" -namespace Database +namespace lms::db { TrackList::TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr user) : _name{ name } , _type{ type } , _isPublic{ isPublic } - , _creationDateTime{ Utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) } - , _lastModifiedDateTime{ Utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) } + , _creationDateTime{ utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) } + , _lastModifiedDateTime{ utils::normalizeDateTime(Wt::WDateTime::currentDateTime()) } , _user{ getDboPtr(user) } { assert(user); @@ -117,7 +117,7 @@ namespace Database break; } - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } TrackList::pointer TrackList::find(Session& session, TrackListId id) @@ -170,7 +170,7 @@ namespace Database return session()->find() .where("tracklist_id = ?").bind(getId()) .where("track_id = ?").bind(track->getId()) - .where("date_time = ?").bind(Utils::normalizeDateTime(dateTime)) + .where("date_time = ?").bind(utils::normalizeDateTime(dateTime)) .resultValue(); } @@ -294,11 +294,11 @@ namespace Database void TrackList::setLastModifiedDateTime(const Wt::WDateTime& dateTime) { - _lastModifiedDateTime = Utils::normalizeDateTime(dateTime); + _lastModifiedDateTime = utils::normalizeDateTime(dateTime); } TrackListEntry::TrackListEntry(ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime) - : _dateTime{ Utils::normalizeDateTime(dateTime) } + : _dateTime{ utils::normalizeDateTime(dateTime) } , _track{ getDboPtr(track) } , _tracklist{ getDboPtr(tracklist) } { @@ -313,12 +313,12 @@ namespace Database void TrackListEntry::onPostCreated() { - _tracklist.modify()->setLastModifiedDateTime(Utils::normalizeDateTime(Wt::WDateTime::currentDateTime())); + _tracklist.modify()->setLastModifiedDateTime(utils::normalizeDateTime(Wt::WDateTime::currentDateTime())); } void TrackListEntry::onPreRemove() { - _tracklist.modify()->setLastModifiedDateTime(Utils::normalizeDateTime(Wt::WDateTime::currentDateTime())); + _tracklist.modify()->setLastModifiedDateTime(utils::normalizeDateTime(Wt::WDateTime::currentDateTime())); } TrackListEntry::pointer TrackListEntry::getById(Session& session, TrackListEntryId id) @@ -327,5 +327,4 @@ namespace Database return session.getDboSession().find().where("id = ?").bind(id).resultValue(); } - -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/TransactionChecker.cpp b/src/libs/database/impl/TransactionChecker.cpp index 859cd44d..315c953b 100644 --- a/src/libs/database/impl/TransactionChecker.cpp +++ b/src/libs/database/impl/TransactionChecker.cpp @@ -29,7 +29,7 @@ #define LMS_CHECK_TRANSACTION_ACCESSES 0 #endif -namespace Database +namespace lms::db { #if LMS_CHECK_TRANSACTION_ACCESSES namespace diff --git a/src/libs/database/impl/Types.cpp b/src/libs/database/impl/Types.cpp index 456955cc..f40ac880 100644 --- a/src/libs/database/impl/Types.cpp +++ b/src/libs/database/impl/Types.cpp @@ -21,7 +21,7 @@ #include -namespace Database +namespace lms::db { static const std::set allowedAudioBitrates { diff --git a/src/libs/database/impl/User.cpp b/src/libs/database/impl/User.cpp index 88051069..1960a209 100644 --- a/src/libs/database/impl/User.cpp +++ b/src/libs/database/impl/User.cpp @@ -23,12 +23,12 @@ #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "IdTypeTraits.hpp" #include "StringViewTraits.hpp" #include "Utils.hpp" -namespace Database { +namespace lms::db { User::User(std::string_view loginName) : _loginName{ loginName } @@ -58,7 +58,7 @@ namespace Database { if (params.feedbackBackend) query.where("feedback_backend = ?").bind(*params.feedbackBackend); - return Utils::execQuery(query, params.range); + return utils::execQuery(query, params.range); } User::pointer User::findDemoUser(Session& session) @@ -91,4 +91,4 @@ namespace Database { _authTokens.clear(); } -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/impl/Utils.cpp b/src/libs/database/impl/Utils.cpp index ceb3af41..6bafab61 100644 --- a/src/libs/database/impl/Utils.cpp +++ b/src/libs/database/impl/Utils.cpp @@ -19,21 +19,19 @@ #include "Utils.hpp" -#include "utils/String.hpp" +#include "core/String.hpp" -namespace Database::Utils +namespace lms::db::utils { - std::string - escapeLikeKeyword(std::string_view keyword) - { - return StringUtils::escapeString(keyword, "%_", escapeChar); - } + std::string escapeLikeKeyword(std::string_view keyword) + { + return core::stringUtils::escapeString(keyword, "%_", escapeChar); + } - Wt::WDateTime - normalizeDateTime(const Wt::WDateTime& dateTime) - { - // force second resolution - return Wt::WDateTime::fromTime_t(dateTime.toTime_t()); - } -} // namespace Database::Utils + Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime) + { + // force second resolution + return Wt::WDateTime::fromTime_t(dateTime.toTime_t()); + } +} // namespace lms::db::Utils diff --git a/src/libs/database/impl/Utils.hpp b/src/libs/database/impl/Utils.hpp index 29be679c..d6fec25b 100644 --- a/src/libs/database/impl/Utils.hpp +++ b/src/libs/database/impl/Utils.hpp @@ -27,9 +27,9 @@ #include #include "database/Types.hpp" -#include "utils/ITraceLogger.hpp" +#include "core/ITraceLogger.hpp" -namespace Database::Utils +namespace lms::db::utils { #define ESCAPE_CHAR_STR "\\" static inline constexpr char escapeChar{ '\\' }; @@ -86,5 +86,5 @@ namespace Database::Utils } Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime); -} // namespace Database::Utils +} diff --git a/src/libs/database/include/database/Artist.hpp b/src/libs/database/include/database/Artist.hpp index f70fec40..9fb728c3 100644 --- a/src/libs/database/include/database/Artist.hpp +++ b/src/libs/database/include/database/Artist.hpp @@ -27,6 +27,8 @@ #include #include +#include "core/EnumSet.hpp" +#include "core/UUID.hpp" #include "database/ArtistId.hpp" #include "database/ClusterId.hpp" #include "database/MediaLibraryId.hpp" @@ -35,10 +37,8 @@ #include "database/Types.hpp" #include "database/UserId.hpp" #include "database/TrackId.hpp" -#include "utils/EnumSet.hpp" -#include "utils/UUID.hpp" -namespace Database +namespace lms::db { class Cluster; @@ -83,7 +83,7 @@ namespace Database // Accessors static std::size_t getCount(Session& session); - static pointer find(Session& session, const UUID& MBID); + static pointer find(Session& session, const core::UUID& MBID); static pointer find(Session& session, ArtistId id); static std::vector find(Session& session, std::string_view name); // exact match on name field static RangeResults find(Session& session, const FindParameters& parameters); @@ -95,10 +95,10 @@ namespace Database // Accessors const std::string& getName() const { return _name; } const std::string& getSortName() const { return _sortName; } - std::optional getMBID() const { return UUID::fromString(_MBID); } + std::optional getMBID() const { return core::UUID::fromString(_MBID); } // No artistLinkTypes means get them all - RangeResults findSimilarArtistIds(EnumSet artistLinkTypes = {}, std::optional range = std::nullopt) const; + RangeResults findSimilarArtistIds(core::EnumSet artistLinkTypes = {}, std::optional range = std::nullopt) const; // Get the cluster of the tracks made by this artist // Each clusters are grouped by cluster type, sorted by the number of occurence @@ -106,7 +106,7 @@ namespace Database std::vector>> getClusterGroups(std::vector clusterTypeIds, std::size_t size) const; void setName(std::string_view name) { _name = name; } - void setMBID(const std::optional& mbid) { _MBID = mbid ? mbid->getAsString() : ""; } + void setMBID(const std::optional& mbid) { _MBID = mbid ? mbid->getAsString() : ""; } void setSortName(const std::string& sortName); template @@ -125,8 +125,8 @@ namespace Database friend class Session; // Create - Artist(const std::string& name, const std::optional& MBID = {}); - static pointer create(Session& session, const std::string& name, const std::optional& UUID = {}); + Artist(const std::string& name, const std::optional& MBID = {}); + static pointer create(Session& session, const std::string& name, const std::optional& UUID = {}); std::string _name; std::string _sortName; @@ -136,5 +136,5 @@ namespace Database Wt::Dbo::collection> _starredArtists; // starred entries for this artist }; -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/AuthToken.hpp b/src/libs/database/include/database/AuthToken.hpp index da5e1437..0648e2b8 100644 --- a/src/libs/database/include/database/AuthToken.hpp +++ b/src/libs/database/include/database/AuthToken.hpp @@ -27,7 +27,7 @@ #include "database/AuthTokenId.hpp" #include "database/Object.hpp" -namespace Database +namespace lms::db { class Session; diff --git a/src/libs/database/include/database/Cluster.hpp b/src/libs/database/include/database/Cluster.hpp index f7374454..ae869778 100644 --- a/src/libs/database/include/database/Cluster.hpp +++ b/src/libs/database/include/database/Cluster.hpp @@ -34,8 +34,8 @@ #include "database/TrackId.hpp" #include "database/Types.hpp" -namespace Database { - +namespace lms::db +{ class Track; class ClusterType; class Session; @@ -149,5 +149,5 @@ namespace Database { Wt::Dbo::collection< Wt::Dbo::ptr > _clusters; }; -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/Db.hpp b/src/libs/database/include/database/Db.hpp index a0298eb1..fe444ebc 100644 --- a/src/libs/database/include/database/Db.hpp +++ b/src/libs/database/include/database/Db.hpp @@ -23,10 +23,10 @@ #include -#include "utils/RecursiveSharedMutex.hpp" - -namespace Database { +#include "core/RecursiveSharedMutex.hpp" +namespace lms::db +{ class Session; class Db { @@ -43,7 +43,7 @@ namespace Database { friend class Session; - RecursiveSharedMutex& getMutex() { return _sharedMutex; } + core::RecursiveSharedMutex& getMutex() { return _sharedMutex; } Wt::Dbo::SqlConnectionPool& getConnectionPool() { return *_connectionPool; } class ScopedConnection @@ -62,13 +62,11 @@ namespace Database { std::unique_ptr _connection; }; - RecursiveSharedMutex _sharedMutex; + core::RecursiveSharedMutex _sharedMutex; std::unique_ptr _connectionPool; std::mutex _tlsSessionsMutex; std::vector> _tlsSessions; }; -} // namespace Database - - +} diff --git a/src/libs/database/include/database/IdType.hpp b/src/libs/database/include/database/IdType.hpp index 04aaa2e4..e1c06504 100644 --- a/src/libs/database/include/database/IdType.hpp +++ b/src/libs/database/include/database/IdType.hpp @@ -24,7 +24,7 @@ #include #include -namespace Database +namespace lms::db { class IdType { @@ -49,7 +49,7 @@ namespace Database }; #define LMS_DECLARE_IDTYPE(name) \ - namespace Database { \ + namespace lms::db { \ class name : public IdType \ { \ public: \ @@ -59,14 +59,14 @@ namespace Database namespace std \ { \ template<> \ - class hash \ + class hash \ { \ public: \ - size_t operator()(Database::name id) const \ + size_t operator()(lms::db::name id) const \ { \ - return std::hash()(id.getValue()); \ + return std::hash()(id.getValue()); \ } \ }; \ } // ns std -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/Listen.hpp b/src/libs/database/include/database/Listen.hpp index 1f9c7c1f..bc4dbaa0 100644 --- a/src/libs/database/include/database/Listen.hpp +++ b/src/libs/database/include/database/Listen.hpp @@ -34,9 +34,8 @@ #include "database/Types.hpp" #include "database/UserId.hpp" -namespace Database +namespace lms::db { - class Session; class Track; class User; @@ -135,5 +134,5 @@ namespace Database Wt::Dbo::ptr _track; }; -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/MediaLibrary.hpp b/src/libs/database/include/database/MediaLibrary.hpp index 790a5719..d8f680dc 100644 --- a/src/libs/database/include/database/MediaLibrary.hpp +++ b/src/libs/database/include/database/MediaLibrary.hpp @@ -28,7 +28,7 @@ #include "database/MediaLibraryId.hpp" #include "database/Object.hpp" -namespace Database +namespace lms::db { class Session; @@ -63,11 +63,11 @@ namespace Database } private: - friend class ::Database::Session; + friend class Session; MediaLibrary(const std::filesystem::path& p, std::string_view name); static pointer create(Session& session, const std::filesystem::path& p = {}, std::string_view name = {}); std::filesystem::path _path; std::string _name; }; -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/Object.hpp b/src/libs/database/include/database/Object.hpp index 8d84ae3b..6f37a4df 100644 --- a/src/libs/database/include/database/Object.hpp +++ b/src/libs/database/include/database/Object.hpp @@ -24,7 +24,7 @@ #include "database/IdType.hpp" #include "database/TransactionChecker.hpp" -namespace Database +namespace lms::db { template class ObjectPtr @@ -57,8 +57,8 @@ namespace Database template class Object : public Wt::Dbo::Dbo { - static_assert(std::is_base_of_v); - static_assert(!std::is_same_v); + static_assert(std::is_base_of_v); + static_assert(!std::is_same_v); public: using pointer = ObjectPtr; diff --git a/src/libs/database/include/database/Release.hpp b/src/libs/database/include/database/Release.hpp index 52f5100d..30f4deae 100644 --- a/src/libs/database/include/database/Release.hpp +++ b/src/libs/database/include/database/Release.hpp @@ -28,6 +28,8 @@ #include #include +#include "core/EnumSet.hpp" +#include "core/UUID.hpp" #include "database/ArtistId.hpp" #include "database/ClusterId.hpp" #include "database/MediaLibraryId.hpp" @@ -36,10 +38,8 @@ #include "database/ReleaseTypeId.hpp" #include "database/Types.hpp" #include "database/UserId.hpp" -#include "utils/EnumSet.hpp" -#include "utils/UUID.hpp" -namespace Database +namespace lms::db { class Artist; class Cluster; @@ -91,8 +91,8 @@ namespace Database UserId starringUser; // only releases starred by this user std::optional feedbackBackend; // and for this backend ArtistId artist; // only releases that involved this user - EnumSet trackArtistLinkTypes; // and for these link types - EnumSet excludedTrackArtistLinkTypes; // but not for these link types + core::EnumSet trackArtistLinkTypes; // and for these link types + core::EnumSet excludedTrackArtistLinkTypes; // but not for these link types std::string releaseType; // If set, albums that has this release type MediaLibraryId mediaLibrary; // If set, releases that has at least a track in this library @@ -103,7 +103,7 @@ namespace Database FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; } FindParameters& setDateRange(const std::optional& _dateRange) { dateRange = _dateRange; return *this; } FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; } - FindParameters& setArtist(ArtistId _artist, EnumSet _trackArtistLinkTypes = {}, EnumSet _excludedTrackArtistLinkTypes = {}) + FindParameters& setArtist(ArtistId _artist, core::EnumSet _trackArtistLinkTypes = {}, core::EnumSet _excludedTrackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; @@ -119,7 +119,7 @@ namespace Database // Accessors static std::size_t getCount(Session& session); static bool exists(Session& session, ReleaseId id); - static pointer find(Session& session, const UUID& MBID); + static pointer find(Session& session, const core::UUID& MBID); static std::vector find(Session& session, const std::string& name, const std::filesystem::path& releaseDirectory); static pointer find(Session& session, ReleaseId id); static RangeResults find(Session& session, const FindParameters& parameters); @@ -145,8 +145,8 @@ namespace Database // Accessors std::string_view getName() const { return _name; } std::string_view getSortName() const { return _sortName; } - std::optional getMBID() const { return UUID::fromString(_MBID); } - std::optional getGroupMBID() const { return UUID::fromString(_groupMBID); } + std::optional getMBID() const { return core::UUID::fromString(_MBID); } + std::optional getGroupMBID() const { return core::UUID::fromString(_groupMBID); } std::optional getTotalDisc() const { return _totalDisc; } std::size_t getDiscCount() const; // may not be total disc (if incomplete for example) std::vector getDiscs() const; @@ -160,8 +160,8 @@ namespace Database // Setters void setName(std::string_view name) { _name = name; } void setSortName(std::string_view sortName) { _sortName = sortName; } - void setMBID(const std::optional& mbid) { _MBID = mbid ? mbid->getAsString() : ""; } - void setGroupMBID(const std::optional& mbid) { _groupMBID = mbid ? mbid->getAsString() : ""; } + void setMBID(const std::optional& mbid) { _MBID = mbid ? mbid->getAsString() : ""; } + void setGroupMBID(const std::optional& mbid) { _groupMBID = mbid ? mbid->getAsString() : ""; } void setTotalDisc(std::optional totalDisc) { _totalDisc = totalDisc; } void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; } void clearReleaseTypes(); @@ -188,8 +188,8 @@ namespace Database private: friend class Session; - Release(const std::string& name, const std::optional& MBID = {}); - static pointer create(Session& session, const std::string& name, const std::optional& MBID = {}); + Release(const std::string& name, const std::optional& MBID = {}); + static pointer create(Session& session, const std::string& name, const std::optional& MBID = {}); Wt::WDate getDate(bool original) const; std::optional getYear(bool original) const; @@ -207,4 +207,4 @@ namespace Database Wt::Dbo::collection> _releaseTypes; // Release types }; -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/ReleaseId.hpp b/src/libs/database/include/database/ReleaseId.hpp index 5ee986bc..c588661c 100644 --- a/src/libs/database/include/database/ReleaseId.hpp +++ b/src/libs/database/include/database/ReleaseId.hpp @@ -22,4 +22,3 @@ #include "database/IdType.hpp" LMS_DECLARE_IDTYPE(ReleaseId) - diff --git a/src/libs/database/include/database/ReleaseTypeId.hpp b/src/libs/database/include/database/ReleaseTypeId.hpp index 2559c4e5..e8261a7e 100644 --- a/src/libs/database/include/database/ReleaseTypeId.hpp +++ b/src/libs/database/include/database/ReleaseTypeId.hpp @@ -22,4 +22,3 @@ #include "database/IdType.hpp" LMS_DECLARE_IDTYPE(ReleaseTypeId) - diff --git a/src/libs/database/include/database/ScanSettings.hpp b/src/libs/database/include/database/ScanSettings.hpp index 8337a224..1de5fa40 100644 --- a/src/libs/database/include/database/ScanSettings.hpp +++ b/src/libs/database/include/database/ScanSettings.hpp @@ -33,7 +33,7 @@ LMS_DECLARE_IDTYPE(ScanSettingsId) -namespace Database +namespace lms::db { class Session; @@ -104,4 +104,4 @@ namespace Database std::string _artistTagDelimiters; std::string _defaultTagDelimiters; }; -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/Session.hpp b/src/libs/database/include/database/Session.hpp index 07edf49a..c0175e99 100644 --- a/src/libs/database/include/database/Session.hpp +++ b/src/libs/database/include/database/Session.hpp @@ -22,12 +22,12 @@ #include #include -#include "utils/ITraceLogger.hpp" -#include "utils/RecursiveSharedMutex.hpp" +#include "core/ITraceLogger.hpp" +#include "core/RecursiveSharedMutex.hpp" #include "database/Object.hpp" #include "database/TransactionChecker.hpp" -namespace Database +namespace lms::db { class WriteTransaction { @@ -36,13 +36,13 @@ namespace Database private: friend class Session; - WriteTransaction(RecursiveSharedMutex& mutex, Wt::Dbo::Session& session); + WriteTransaction(core::RecursiveSharedMutex& mutex, Wt::Dbo::Session& session); WriteTransaction(const WriteTransaction&) = delete; WriteTransaction& operator=(const WriteTransaction&) = delete; - std::unique_lock _lock; - tracing::ScopedTrace _trace{ "Database", tracing::Level::Detailed, "WriteTransaction" }; // before actual transaction + std::unique_lock _lock; + core::tracing::ScopedTrace _trace{ "Database", core::tracing::Level::Detailed, "WriteTransaction" }; // before actual transaction Wt::Dbo::Transaction _transaction; }; @@ -58,7 +58,7 @@ namespace Database ReadTransaction(const ReadTransaction&) = delete; ReadTransaction& operator=(const ReadTransaction&) = delete; - tracing::ScopedTrace _trace{ "Database", tracing::Level::Detailed, "ReadTransaction" }; // before actual transaction + core::tracing::ScopedTrace _trace{ "Database", core::tracing::Level::Detailed, "ReadTransaction" }; // before actual transaction Wt::Dbo::Transaction _transaction; }; @@ -103,4 +103,4 @@ namespace Database Db& _db; Wt::Dbo::Session _session; }; -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/StarredArtist.hpp b/src/libs/database/include/database/StarredArtist.hpp index 99573797..acf6196a 100644 --- a/src/libs/database/include/database/StarredArtist.hpp +++ b/src/libs/database/include/database/StarredArtist.hpp @@ -28,7 +28,7 @@ #include "database/Types.hpp" #include "database/UserId.hpp" -namespace Database +namespace lms::db { class Artist; class Session; @@ -79,5 +79,5 @@ namespace Database Wt::Dbo::ptr _artist; Wt::Dbo::ptr _user; }; -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/StarredArtistId.hpp b/src/libs/database/include/database/StarredArtistId.hpp index 8b320fd0..2ff32678 100644 --- a/src/libs/database/include/database/StarredArtistId.hpp +++ b/src/libs/database/include/database/StarredArtistId.hpp @@ -22,4 +22,3 @@ #include "database/IdType.hpp" LMS_DECLARE_IDTYPE(StarredArtistId) - diff --git a/src/libs/database/include/database/StarredRelease.hpp b/src/libs/database/include/database/StarredRelease.hpp index 25b4b4b5..13a9b7df 100644 --- a/src/libs/database/include/database/StarredRelease.hpp +++ b/src/libs/database/include/database/StarredRelease.hpp @@ -28,7 +28,7 @@ #include "database/Types.hpp" #include "database/UserId.hpp" -namespace Database +namespace lms::db { class Release; class Session; @@ -79,5 +79,5 @@ namespace Database Wt::Dbo::ptr _release; Wt::Dbo::ptr _user; }; -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/StarredReleaseId.hpp b/src/libs/database/include/database/StarredReleaseId.hpp index a2b1dbc7..bb0a63c0 100644 --- a/src/libs/database/include/database/StarredReleaseId.hpp +++ b/src/libs/database/include/database/StarredReleaseId.hpp @@ -22,4 +22,3 @@ #include "database/IdType.hpp" LMS_DECLARE_IDTYPE(StarredReleaseId) - diff --git a/src/libs/database/include/database/StarredTrack.hpp b/src/libs/database/include/database/StarredTrack.hpp index 787fcc31..4b037f47 100644 --- a/src/libs/database/include/database/StarredTrack.hpp +++ b/src/libs/database/include/database/StarredTrack.hpp @@ -22,14 +22,14 @@ #include #include +#include "core/EnumSet.hpp" #include "database/TrackId.hpp" #include "database/Object.hpp" #include "database/StarredTrackId.hpp" #include "database/Types.hpp" #include "database/UserId.hpp" -#include "utils/EnumSet.hpp" -namespace Database +namespace lms::db { class Track; class Session; @@ -94,5 +94,5 @@ namespace Database Wt::Dbo::ptr _track; Wt::Dbo::ptr _user; }; -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/StarredTrackId.hpp b/src/libs/database/include/database/StarredTrackId.hpp index 14782fae..54568afd 100644 --- a/src/libs/database/include/database/StarredTrackId.hpp +++ b/src/libs/database/include/database/StarredTrackId.hpp @@ -22,4 +22,3 @@ #include "database/IdType.hpp" LMS_DECLARE_IDTYPE(StarredTrackId) - diff --git a/src/libs/database/include/database/Track.hpp b/src/libs/database/include/database/Track.hpp index a1cd4fd8..d3b3586d 100644 --- a/src/libs/database/include/database/Track.hpp +++ b/src/libs/database/include/database/Track.hpp @@ -32,9 +32,8 @@ #include #include -#include "utils/EnumSet.hpp" -#include "utils/UUID.hpp" - +#include "core/EnumSet.hpp" +#include "core/UUID.hpp" #include "database/ArtistId.hpp" #include "database/ClusterId.hpp" #include "database/MediaLibraryId.hpp" @@ -45,8 +44,8 @@ #include "database/Types.hpp" #include "database/UserId.hpp" -namespace Database { - +namespace lms::db +{ class Artist; class Cluster; class ClusterType; @@ -72,7 +71,7 @@ namespace Database { std::optional feedbackBackend; // and for this feedback backend ArtistId artist; // only tracks that involve this artist std::string artistName; // only tracks that involve this artist name - EnumSet trackArtistLinkTypes; // and for these link types + core::EnumSet trackArtistLinkTypes; // and for these link types bool nonRelease{}; // only tracks that do not belong to a release ReleaseId release; // matching this release std::string releaseName; // matching this release name @@ -88,8 +87,8 @@ namespace Database { FindParameters& setRange(std::optional _range) { range = _range; return *this; } FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; } FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; } - FindParameters& setArtist(ArtistId _artist, EnumSet _trackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } - FindParameters& setArtistName(std::string_view _artistName, EnumSet _trackArtistLinkTypes = {}) { artistName = _artistName; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } + FindParameters& setArtist(ArtistId _artist, core::EnumSet _trackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } + FindParameters& setArtistName(std::string_view _artistName, core::EnumSet _trackArtistLinkTypes = {}) { artistName = _artistName; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } FindParameters& setNonRelease(bool _nonRelease) { nonRelease = _nonRelease; return *this; } FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } FindParameters& setReleaseName(std::string_view _releaseName) { releaseName = _releaseName; return *this; } @@ -112,8 +111,8 @@ namespace Database { static pointer findByPath(Session& session, const std::filesystem::path& p); static pointer find(Session& session, TrackId id); static bool exists(Session& session, TrackId id); - static std::vector findByRecordingMBID(Session& session, const UUID& MBID); - static std::vector findByMBID(Session& session, const UUID& MBID); + static std::vector findByRecordingMBID(Session& session, const core::UUID& MBID); + static std::vector findByMBID(Session& session, const core::UUID& MBID); static RangeResults findSimilarTrackIds(Session& session, const std::vector& trackIds, std::optional range = std::nullopt); static RangeResults findIds(Session& session, const FindParameters& parameters); @@ -140,8 +139,8 @@ namespace Database { void setOriginalDate(const Wt::WDate& date) { _originalDate = date; } void setOriginalYear(std::optional year) { _originalYear = year; } void setHasCover(bool hasCover) { _hasCover = hasCover; } - void setTrackMBID(const std::optional& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; } - void setRecordingMBID(const std::optional& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; } + void setTrackMBID(const std::optional& MBID) { _trackMBID = MBID ? MBID->getAsString() : ""; } + void setRecordingMBID(const std::optional& MBID) { _recordingMBID = MBID ? MBID->getAsString() : ""; } void setCopyright(const std::string& copyright) { _copyright = std::string(copyright, 0, _maxCopyrightLength); } void setCopyrightURL(const std::string& copyrightURL) { _copyrightURL = std::string(copyrightURL, 0, _maxCopyrightURLLength); } void setTrackReplayGain(std::optional replayGain) { _trackReplayGain = replayGain; } @@ -170,16 +169,16 @@ namespace Database { Wt::WDateTime getLastWriteTime() const { return _fileLastWrite; } Wt::WDateTime getAddedTime() const { return _fileAdded; } bool hasCover() const { return _hasCover; } - std::optional getTrackMBID() const { return UUID::fromString(_trackMBID); } - std::optional getRecordingMBID() const { return UUID::fromString(_recordingMBID); } + std::optional getTrackMBID() const { return core::UUID::fromString(_trackMBID); } + std::optional getRecordingMBID() const { return core::UUID::fromString(_recordingMBID); } std::optional getCopyright() const; std::optional getCopyrightURL() const; std::optional getTrackReplayGain() const { return _trackReplayGain; } std::optional getReleaseReplayGain() const { return _releaseReplayGain; } std::string_view getArtistDisplayName() const { return _artistDisplayName; } // no artistLinkTypes means get all - std::vector> getArtists(EnumSet artistLinkTypes) const; // no type means all - std::vector getArtistIds(EnumSet artistLinkTypes) const; // no type means all + std::vector> getArtists(core::EnumSet artistLinkTypes) const; // no type means all + std::vector getArtistIds(core::EnumSet artistLinkTypes) const; // no type means all std::vector> getArtistLinks() const; ObjectPtr getRelease() const { return _release; } std::vector> getClusters() const; @@ -221,7 +220,7 @@ namespace Database { } private: - friend class ::Database::Session; + friend class Session; Track(const std::filesystem::path& p); static pointer create(Session& session, const std::filesystem::path& p); @@ -268,7 +267,6 @@ namespace Database { }; std::ostream& operator<<(std::ostream& os, const TrackInfo& trackInfo); } - -} // namespace database +} // namespace lms::db diff --git a/src/libs/database/include/database/TrackArtistLink.hpp b/src/libs/database/include/database/TrackArtistLink.hpp index 6a4b5cd9..36af08ea 100644 --- a/src/libs/database/include/database/TrackArtistLink.hpp +++ b/src/libs/database/include/database/TrackArtistLink.hpp @@ -25,17 +25,17 @@ #include +#include "core/EnumSet.hpp" #include "database/ArtistId.hpp" #include "database/IdType.hpp" #include "database/Object.hpp" #include "database/ReleaseId.hpp" #include "database/TrackId.hpp" #include "database/Types.hpp" -#include "utils/EnumSet.hpp" LMS_DECLARE_IDTYPE(TrackArtistLinkId) -namespace Database +namespace lms::db { class Artist; class Session; @@ -65,8 +65,8 @@ namespace Database static RangeResults find(Session& session, const FindParameters& parameters); static pointer find(Session& session, TrackArtistLinkId linkId); static pointer create(Session& session, ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType = {}); - static EnumSet findUsedTypes(Session& session); - static EnumSet findUsedTypes(Session& session, ArtistId _artist); + static core::EnumSet findUsedTypes(Session& session); + static core::EnumSet findUsedTypes(Session& session, ArtistId _artist); ObjectPtr getTrack() const { return _track; } ObjectPtr getArtist() const { return _artist; } diff --git a/src/libs/database/include/database/TrackBookmark.hpp b/src/libs/database/include/database/TrackBookmark.hpp index 8d541ebc..d02287b0 100644 --- a/src/libs/database/include/database/TrackBookmark.hpp +++ b/src/libs/database/include/database/TrackBookmark.hpp @@ -32,56 +32,54 @@ LMS_DECLARE_IDTYPE(TrackBookmarkId) -namespace Database { - -class Session; -class Track; -class User; - -class TrackBookmark final : public Object +namespace lms::db { - public: - TrackBookmark () = default; + class Session; + class Track; + class User; - // Find utility functions - static std::size_t getCount(Session& session); - static pointer find(Session& session, TrackBookmarkId id); - static RangeResults find(Session& session, UserId userId, std::optional range = std::nullopt); - static pointer find(Session& session, UserId userId, TrackId trackId); + class TrackBookmark final : public Object + { + public: + TrackBookmark() = default; - // Setters - void setOffset(std::chrono::milliseconds offset) { _offset = offset; } - void setComment(std::string_view comment) { _comment = comment; } + // Find utility functions + static std::size_t getCount(Session& session); + static pointer find(Session& session, TrackBookmarkId id); + static RangeResults find(Session& session, UserId userId, std::optional range = std::nullopt); + static pointer find(Session& session, UserId userId, TrackId trackId); - // Getters - std::chrono::milliseconds getOffset() const { return _offset; } - std::string_view getComment() const { return _comment; } - ObjectPtr getTrack() const { return _track; } - ObjectPtr getUser() const { return _user; } + // Setters + void setOffset(std::chrono::milliseconds offset) { _offset = offset; } + void setComment(std::string_view comment) { _comment = comment; } - template - void persist(Action& a) - { - Wt::Dbo::field(a, _offset, "offset"); - Wt::Dbo::field(a, _comment, "comment"); - Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); - Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); - } + // Getters + std::chrono::milliseconds getOffset() const { return _offset; } + std::string_view getComment() const { return _comment; } + ObjectPtr getTrack() const { return _track; } + ObjectPtr getUser() const { return _user; } - private: - friend class Session; - TrackBookmark(ObjectPtr user, ObjectPtr track); - static pointer create(Session& session, ObjectPtr user, ObjectPtr track); + template + void persist(Action& a) + { + Wt::Dbo::field(a, _offset, "offset"); + Wt::Dbo::field(a, _comment, "comment"); + Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); + } - static const std::size_t _maxCommentLength = 128; + private: + friend class Session; + TrackBookmark(ObjectPtr user, ObjectPtr track); + static pointer create(Session& session, ObjectPtr user, ObjectPtr track); - std::chrono::duration _offset; - std::string _comment; + static const std::size_t _maxCommentLength = 128; - Wt::Dbo::ptr _user; - Wt::Dbo::ptr _track; -}; - -} // namespace database + std::chrono::duration _offset; + std::string _comment; + Wt::Dbo::ptr _user; + Wt::Dbo::ptr _track; + }; +} // namespace lms::db diff --git a/src/libs/database/include/database/TrackFeatures.hpp b/src/libs/database/include/database/TrackFeatures.hpp index 63abc5c6..7c10ac80 100644 --- a/src/libs/database/include/database/TrackFeatures.hpp +++ b/src/libs/database/include/database/TrackFeatures.hpp @@ -34,49 +34,46 @@ LMS_DECLARE_IDTYPE(TrackFeaturesId) -namespace Database { - -class Session; -class Track; - -using FeatureName = std::string; -using FeatureValues = std::vector; -using FeatureValuesMap = std::unordered_map; - -class TrackFeatures final : public Object +namespace lms::db { - public: - TrackFeatures() = default; + class Session; + class Track; - // Find utilities - static std::size_t getCount(Session& session); - static pointer find(Session& session, TrackFeaturesId id); - static pointer find(Session& session, TrackId trackId); - static RangeResults find(Session& session, std::optional range = std::nullopt); + using FeatureName = std::string; + using FeatureValues = std::vector; + using FeatureValuesMap = std::unordered_map; - FeatureValues getFeatureValues(const FeatureName& feature) const; - FeatureValuesMap getFeatureValuesMap(const std::unordered_set& featureNames) const; + class TrackFeatures final : public Object + { + public: + TrackFeatures() = default; - // Accessors - Wt::Dbo::ptr getTrack() const { return _track; } + // Find utilities + static std::size_t getCount(Session& session); + static pointer find(Session& session, TrackFeaturesId id); + static pointer find(Session& session, TrackId trackId); + static RangeResults find(Session& session, std::optional range = std::nullopt); - template - void persist(Action& a) - { - Wt::Dbo::field(a, _data, "data"); - Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); - } + FeatureValues getFeatureValues(const FeatureName& feature) const; + FeatureValuesMap getFeatureValuesMap(const std::unordered_set& featureNames) const; - private: - friend class Session; - TrackFeatures(ObjectPtr track, const std::string& jsonEncodedFeatures); - static pointer create(Session& session, ObjectPtr track, const std::string& jsonEncodedFeatures); + // Accessors + Wt::Dbo::ptr getTrack() const { return _track; } - std::string _data; - Wt::Dbo::ptr _track; -}; + template + void persist(Action& a) + { + Wt::Dbo::field(a, _data, "data"); + Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); + } + private: + friend class Session; + TrackFeatures(ObjectPtr track, const std::string& jsonEncodedFeatures); + static pointer create(Session& session, ObjectPtr track, const std::string& jsonEncodedFeatures); -} // namespace database - + std::string _data; + Wt::Dbo::ptr _track; + }; +} // namespace lms::db diff --git a/src/libs/database/include/database/TrackId.hpp b/src/libs/database/include/database/TrackId.hpp index f94ea4f2..a2bbd415 100644 --- a/src/libs/database/include/database/TrackId.hpp +++ b/src/libs/database/include/database/TrackId.hpp @@ -22,4 +22,3 @@ #include "database/IdType.hpp" LMS_DECLARE_IDTYPE(TrackId) - diff --git a/src/libs/database/include/database/TrackList.hpp b/src/libs/database/include/database/TrackList.hpp index e7bc780c..b961bb5e 100644 --- a/src/libs/database/include/database/TrackList.hpp +++ b/src/libs/database/include/database/TrackList.hpp @@ -34,8 +34,8 @@ #include "database/Types.hpp" #include "database/UserId.hpp" -namespace Database { - +namespace lms::db +{ class Artist; class Cluster; class ClusterType; @@ -168,5 +168,5 @@ namespace Database { Wt::Dbo::ptr _tracklist; }; -} // namespace Database +} // namespace lms::db diff --git a/src/libs/database/include/database/TrackListId.hpp b/src/libs/database/include/database/TrackListId.hpp index 16b63491..85945b53 100644 --- a/src/libs/database/include/database/TrackListId.hpp +++ b/src/libs/database/include/database/TrackListId.hpp @@ -23,4 +23,3 @@ LMS_DECLARE_IDTYPE(TrackListId) LMS_DECLARE_IDTYPE(TrackListEntryId) - diff --git a/src/libs/database/include/database/TransactionChecker.hpp b/src/libs/database/include/database/TransactionChecker.hpp index bde4f155..5fe92510 100644 --- a/src/libs/database/include/database/TransactionChecker.hpp +++ b/src/libs/database/include/database/TransactionChecker.hpp @@ -22,7 +22,7 @@ #include #include -namespace Database +namespace lms::db { class Session; diff --git a/src/libs/database/include/database/Types.hpp b/src/libs/database/include/database/Types.hpp index 44f08e94..e9cc5a2a 100644 --- a/src/libs/database/include/database/Types.hpp +++ b/src/libs/database/include/database/Types.hpp @@ -24,7 +24,7 @@ #include #include -namespace Database +namespace lms::db { // Caution: do not change enum values if they are set! diff --git a/src/libs/database/include/database/User.hpp b/src/libs/database/include/database/User.hpp index 8eb82e7c..9965c277 100644 --- a/src/libs/database/include/database/User.hpp +++ b/src/libs/database/include/database/User.hpp @@ -26,13 +26,13 @@ #include #include +#include "core/UUID.hpp" #include "database/Object.hpp" #include "database/Types.hpp" #include "database/UserId.hpp" -#include "utils/UUID.hpp" - -namespace Database { +namespace lms::db +{ class AuthToken; class Session; @@ -95,7 +95,7 @@ namespace Database { void setSubsonicArtistListMode(SubsonicArtistListMode mode) { _subsonicArtistListMode = mode; } void setFeedbackBackend(FeedbackBackend feedbackBackend) { _feedbackBackend = feedbackBackend; } void setScrobblingBackend(ScrobblingBackend scrobblingBackend) { _scrobblingBackend = scrobblingBackend; } - void setListenBrainzToken(const std::optional& MBID) { _listenbrainzToken = MBID ? MBID->getAsString() : ""; } + void setListenBrainzToken(const std::optional& MBID) { _listenbrainzToken = MBID ? MBID->getAsString() : ""; } // read bool isAdmin() const { return _type == UserType::ADMIN; } @@ -111,7 +111,7 @@ namespace Database { SubsonicArtistListMode getSubsonicArtistListMode() const { return _subsonicArtistListMode; } FeedbackBackend getFeedbackBackend() const { return _feedbackBackend; } ScrobblingBackend getScrobblingBackend() const { return _scrobblingBackend; } - std::optional getListenBrainzToken() const { return UUID::fromString(_listenbrainzToken); } + std::optional getListenBrainzToken() const { return core::UUID::fromString(_listenbrainzToken); } template void persist(Action& a) diff --git a/src/libs/database/include/database/UserId.hpp b/src/libs/database/include/database/UserId.hpp index 3ebb254e..ee6e0f2d 100644 --- a/src/libs/database/include/database/UserId.hpp +++ b/src/libs/database/include/database/UserId.hpp @@ -22,4 +22,3 @@ #include "database/IdType.hpp" LMS_DECLARE_IDTYPE(UserId) - diff --git a/src/libs/database/test/Artist.cpp b/src/libs/database/test/Artist.cpp index 3030706c..58c973b7 100644 --- a/src/libs/database/test/Artist.cpp +++ b/src/libs/database/test/Artist.cpp @@ -19,472 +19,473 @@ #include "Common.hpp" -using namespace Database; - -TEST_F(DatabaseFixture, Artist) +namespace lms::db::tests { + TEST_F(DatabaseFixture, Artist) { - auto transaction{ session.createReadTransaction() }; - EXPECT_FALSE(Artist::exists(session, 35)); - EXPECT_FALSE(Artist::exists(session, 0)); - EXPECT_FALSE(Artist::exists(session, 1)); - EXPECT_EQ(Artist::getCount(session), 0); - } - - ScopedArtist artist{ session, "MyArtist" }; - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_TRUE(artist.get()); - EXPECT_FALSE(!artist.get()); - EXPECT_EQ(artist.get()->getId(), artist.getId()); - - EXPECT_TRUE(Artist::exists(session, artist.getId())); - EXPECT_EQ(Artist::getCount(session), 1); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto artists{ Artist::findIds(session, Artist::FindParameters {}) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - - artists = Artist::findOrphanIds(session); - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - } - - - { - auto transaction{ session.createReadTransaction() }; - - auto artists{ Artist::find(session, Artist::FindParameters {}) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front()->getId(), artist.getId()); - } - - { - auto transaction{ session.createReadTransaction() }; - - bool visited{}; - Artist::find(session, Artist::FindParameters{}, [&](const Artist::pointer& a) - { - visited = true; - EXPECT_EQ(a->getId(), artist.getId()); - }); - EXPECT_TRUE(visited); - } -} - -TEST_F(DatabaseFixture, Artist_singleTrack) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedArtist artist{ session, "MyArtist" }; - - { - auto transaction{ session.createWriteTransaction() }; - - track.get().modify()->setName("MyTrackName"); - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - } - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto artists{ track->getArtists({TrackArtistLinkType::Artist}) }; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist.getId()); - - ASSERT_EQ(track->getArtistLinks().size(), 1); - auto artistLink{ track->getArtistLinks().front() }; - EXPECT_EQ(artistLink->getTrack()->getId(), track.getId()); - EXPECT_EQ(artistLink->getArtist()->getId(), artist.getId()); - - ASSERT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 1); - EXPECT_EQ(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).size(), 0); - EXPECT_EQ(track->getArtists({}).size(), 1); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto artists{ track->getArtistIds({TrackArtistLinkType::Artist}) }; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front(), artist.getId()); - - ASSERT_EQ(track->getArtistIds({ TrackArtistLinkType::Artist }).size(), 1); - EXPECT_EQ(track->getArtistIds({ TrackArtistLinkType::ReleaseArtist }).size(), 0); - EXPECT_EQ(track->getArtistIds({}).size(), 1); - } - - { - auto transaction{ session.createReadTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtist")) }; - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front(), track.getId()); - } - { - auto transaction{ session.createReadTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtistFoo")) }; - EXPECT_EQ(tracks.results.size(), 0); - } - { - auto transaction{ session.createReadTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackNameFoo").setArtistName("MyArtist")) }; - EXPECT_EQ(tracks.results.size(), 0); - } - { - auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters{}.setTrack(track->getId())) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - } -} - -TEST_F(DatabaseFixture, Artist_singleTrack_mediaLibrary) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedArtist artist{ session, "MyArtist" }; - ScopedMediaLibrary library{ session }; - ScopedMediaLibrary otherLibrary{ session }; - - { - auto transaction{ session.createWriteTransaction() }; - - track.get().modify()->setName("MyTrackName"); - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - track.get().modify()->setMediaLibrary(library.get()); - } - { - auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters{}.setTrack(track->getId())) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - } - { - auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters{}.setMediaLibrary(library->getId())) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - } - { - auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters{}.setMediaLibrary(otherLibrary->getId())) }; - EXPECT_EQ(artists.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedArtist artist{ session, "MyArtist" }; - { - auto transaction{ session.createWriteTransaction() }; - - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist); - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Writer); - } - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Artist::findOrphanIds(session, Range{}).results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).results.size(), 1); - EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Artist)).results.size(), 1); - EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::ReleaseArtist)).results.size(), 1); - EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Writer)).results.size(), 1); - EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Composer)).results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto artists{ track->getArtists({TrackArtistLinkType::Artist}) }; - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist.getId()); - - artists = track->getArtists({ TrackArtistLinkType::ReleaseArtist }); - ASSERT_EQ(artists.size(), 1); - EXPECT_EQ(artists.front()->getId(), artist.getId()); - - EXPECT_EQ(track->getArtistLinks().size(), 3); - - auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist.getId())) }; - EXPECT_EQ(tracks.results.size(), 1); - - tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist })); - EXPECT_EQ(tracks.results.size(), 1); - tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })); - EXPECT_EQ(tracks.results.size(), 1); - tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Writer })); - EXPECT_EQ(tracks.results.size(), 1); - - tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer })); - EXPECT_EQ(tracks.results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - EnumSet types{ TrackArtistLink::findUsedTypes(session, artist.getId()) }; - EXPECT_TRUE(types.contains(TrackArtistLinkType::ReleaseArtist)); - EXPECT_TRUE(types.contains(TrackArtistLinkType::Artist)); - EXPECT_TRUE(types.contains(TrackArtistLinkType::Writer)); - EXPECT_FALSE(types.contains(TrackArtistLinkType::Composer)); - } -} - -TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists) -{ - ScopedTrack track{ session, "track" }; - ScopedArtist artist1{ session, "artist1" }; - ScopedArtist artist2{ session, "artist2" }; - ASSERT_NE(artist1.getId(), artist2.getId()); - - { - auto transaction{ session.createWriteTransaction() }; - - TrackArtistLink::create(session, track.get(), artist1.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track.get(), artist2.get(), TrackArtistLinkType::Artist); - } - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto artists{ track->getArtists({TrackArtistLinkType::Artist}) }; - ASSERT_EQ(artists.size(), 2); - EXPECT_TRUE((artists[0]->getId() == artist1.getId() && artists[1]->getId() == artist2.getId()) - || (artists[0]->getId() == artist2.getId() && artists[1]->getId() == artist1.getId())); - - EXPECT_EQ(track->getArtists({}).size(), 2); - EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 2); - EXPECT_EQ(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).size(), 0); - EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).results.size(), 2); - EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Random)).results.size(), 2); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist1->getId())) }; - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front(), track->getId()); - - tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId())); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front(), track->getId()); - - tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::ReleaseArtist })); - EXPECT_EQ(tracks.results.size(), 0); - - tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::Artist })); - EXPECT_EQ(tracks.results.size(), 1); - - tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::ReleaseArtist })); - EXPECT_EQ(tracks.results.size(), 0); - - tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::Artist })); - EXPECT_EQ(tracks.results.size(), 1); - } -} - -TEST_F(DatabaseFixture, Artist_findByName) -{ - ScopedArtist artist{ session, "AAA" }; - ScopedTrack track{ session, "MyTrack" }; // filters does not work on orphans - - { - auto transaction{ session.createWriteTransaction() }; - artist.get().modify()->setSortName("ZZZ"); - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - } - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "N" })).results.size(), 0); - - const auto artistsByAAA{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"A"})) }; - ASSERT_EQ(artistsByAAA.results.size(), 1); - EXPECT_EQ(artistsByAAA.results.front(), artist.getId()); - - const auto artistsByZZZ{ Artist::Artist::findIds(session, Artist::FindParameters {}.setKeywords({"Z"})) }; - ASSERT_EQ(artistsByZZZ.results.size(), 1); - EXPECT_EQ(artistsByZZZ.results.front(), artist.getId()); - - EXPECT_EQ(Artist::find(session, "NNN").size(), 0); - EXPECT_EQ(Artist::find(session, "AAA").size(), 1); - } -} - -TEST_F(DatabaseFixture, Artist_findByNameEscaped) -{ - ScopedArtist artist1{ session, R"(MyArtist%)" }; - ScopedArtist artist2{ session, R"(%MyArtist)" }; - ScopedArtist artist3{ session, R"(%_MyArtist)" }; - - ScopedArtist artist4{ session, R"(MyArtist%foo)" }; - ScopedArtist artist5{ session, R"(foo%MyArtist)" }; - ScopedArtist artist6{ session, R"(%AMyArtist)" }; - - { - auto transaction{ session.createReadTransaction() }; { - const auto artists{ Artist::find(session, R"(MyArtist%)") }; - ASSERT_TRUE(artists.size() == 1); - EXPECT_EQ(artists.front()->getId(), artist1.getId()); - EXPECT_EQ(Artist::find(session, R"(MyArtistFoo)").size(), 0); + auto transaction{ session.createReadTransaction() }; + EXPECT_FALSE(Artist::exists(session, 35)); + EXPECT_FALSE(Artist::exists(session, 0)); + EXPECT_FALSE(Artist::exists(session, 1)); + EXPECT_EQ(Artist::getCount(session), 0); } - { - const auto artists{ Artist::find(session, R"(%MyArtist)") }; - ASSERT_TRUE(artists.size() == 1); - EXPECT_EQ(artists.front()->getId(), artist2.getId()); - EXPECT_EQ(Artist::find(session, R"(FooMyArtist)").size(), 0); - } - { - const auto artists{ Artist::find(session, R"(%_MyArtist)") }; - ASSERT_TRUE(artists.size() == 1); - ASSERT_EQ(artists.front()->getId(), artist3.getId()); - EXPECT_EQ(Artist::find(session, R"(%CMyArtist)").size(), 0); - } - } - { - auto transaction{ session.createReadTransaction() }; + ScopedArtist artist{ session, "MyArtist" }; + { - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"MyArtist"})) }; - EXPECT_EQ(artists.results.size(), 6); + auto transaction{ session.createReadTransaction() }; + + EXPECT_TRUE(artist.get()); + EXPECT_FALSE(!artist.get()); + EXPECT_EQ(artist.get()->getId(), artist.getId()); + + EXPECT_TRUE(Artist::exists(session, artist.getId())); + EXPECT_EQ(Artist::getCount(session), 1); } { - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"MyArtist%"}).setSortMethod(ArtistSortMethod::ByName)) }; - ASSERT_EQ(artists.results.size(), 2); - EXPECT_EQ(artists.results[0], artist1.getId()); - EXPECT_EQ(artists.results[1], artist4.getId()); - } + auto transaction{ session.createReadTransaction() }; - { - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"%MyArtist"}).setSortMethod(ArtistSortMethod::ByName)) }; - ASSERT_EQ(artists.results.size(), 2); - EXPECT_EQ(artists.results[0], artist2.getId()); - EXPECT_EQ(artists.results[1], artist5.getId()); - } - - { - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"_MyArtist"}).setSortMethod(ArtistSortMethod::ByName)) }; + auto artists{ Artist::findIds(session, Artist::FindParameters {}) }; ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist3.getId()); + EXPECT_EQ(artists.results.front(), artist.getId()); + + artists = Artist::findOrphanIds(session); + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + } + + + { + auto transaction{ session.createReadTransaction() }; + + auto artists{ Artist::find(session, Artist::FindParameters {}) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front()->getId(), artist.getId()); + } + + { + auto transaction{ session.createReadTransaction() }; + + bool visited{}; + Artist::find(session, Artist::FindParameters{}, [&](const Artist::pointer& a) + { + visited = true; + EXPECT_EQ(a->getId(), artist.getId()); + }); + EXPECT_TRUE(visited); } } -} - -TEST_F(DatabaseFixture, Artist_sortMethod) -{ - ScopedArtist artistA{ session, "artistA" }; - ScopedArtist artistB{ session, "artistB" }; + TEST_F(DatabaseFixture, Artist_singleTrack) { - auto transaction{ session.createWriteTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedArtist artist{ session, "MyArtist" }; - artistA.get().modify()->setSortName("sortNameB"); - artistB.get().modify()->setSortName("sortNameA"); + { + auto transaction{ session.createWriteTransaction() }; + + track.get().modify()->setName("MyTrackName"); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto artists{ track->getArtists({TrackArtistLinkType::Artist}) }; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front()->getId(), artist.getId()); + + ASSERT_EQ(track->getArtistLinks().size(), 1); + auto artistLink{ track->getArtistLinks().front() }; + EXPECT_EQ(artistLink->getTrack()->getId(), track.getId()); + EXPECT_EQ(artistLink->getArtist()->getId(), artist.getId()); + + ASSERT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 1); + EXPECT_EQ(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).size(), 0); + EXPECT_EQ(track->getArtists({}).size(), 1); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto artists{ track->getArtistIds({TrackArtistLinkType::Artist}) }; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front(), artist.getId()); + + ASSERT_EQ(track->getArtistIds({ TrackArtistLinkType::Artist }).size(), 1); + EXPECT_EQ(track->getArtistIds({ TrackArtistLinkType::ReleaseArtist }).size(), 0); + EXPECT_EQ(track->getArtistIds({}).size(), 1); + } + + { + auto transaction{ session.createReadTransaction() }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtist")) }; + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtistFoo")) }; + EXPECT_EQ(tracks.results.size(), 0); + } + { + auto transaction{ session.createReadTransaction() }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackNameFoo").setArtistName("MyArtist")) }; + EXPECT_EQ(tracks.results.size(), 0); + } + { + auto transaction{ session.createReadTransaction() }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setTrack(track->getId())) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + } } + TEST_F(DatabaseFixture, Artist_singleTrack_mediaLibrary) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedArtist artist{ session, "MyArtist" }; + ScopedMediaLibrary library{ session }; + ScopedMediaLibrary otherLibrary{ session }; - auto allArtistsByName{ Artist::findIds(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::ByName)) }; - auto allArtistsBySortName{ Artist::findIds(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::BySortName)) }; + { + auto transaction{ session.createWriteTransaction() }; - ASSERT_EQ(allArtistsByName.results.size(), 2); - EXPECT_EQ(allArtistsByName.results.front(), artistA.getId()); - EXPECT_EQ(allArtistsByName.results.back(), artistB.getId()); - - ASSERT_EQ(allArtistsBySortName.results.size(), 2); - EXPECT_EQ(allArtistsBySortName.results.front(), artistB.getId()); - EXPECT_EQ(allArtistsBySortName.results.back(), artistA.getId()); - } -} - -TEST_F(DatabaseFixture, Artist_nonReleaseTracks) -{ - ScopedArtist artist{ session, "artist" }; - ScopedTrack track1{ session, "MyTrack1" }; - ScopedTrack track2{ session, "MyTrack2" }; - ScopedRelease release{ session, "MyRelease" }; - - { - auto transaction{ session.createReadTransaction() }; - - auto tracks{ Track::findIds(session, Track::FindParameters {}.setNonRelease(true).setArtist(artist->getId())) }; - EXPECT_EQ(tracks.results.size(), 0); + track.get().modify()->setName("MyTrackName"); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + track.get().modify()->setMediaLibrary(library.get()); + } + { + auto transaction{ session.createReadTransaction() }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setTrack(track->getId())) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setMediaLibrary(library->getId())) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setMediaLibrary(otherLibrary->getId())) }; + EXPECT_EQ(artists.results.size(), 0); + } } + TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles) { - auto transaction{ session.createWriteTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedArtist artist{ session, "MyArtist" }; + { + auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, track1.get(), artist.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track2.get(), artist.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Writer); + } - track1.get().modify()->setRelease(release.get()); + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Artist::findOrphanIds(session, Range{}).results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).results.size(), 1); + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Artist)).results.size(), 1); + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::ReleaseArtist)).results.size(), 1); + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Writer)).results.size(), 1); + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Composer)).results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto artists{ track->getArtists({TrackArtistLinkType::Artist}) }; + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front()->getId(), artist.getId()); + + artists = track->getArtists({ TrackArtistLinkType::ReleaseArtist }); + ASSERT_EQ(artists.size(), 1); + EXPECT_EQ(artists.front()->getId(), artist.getId()); + + EXPECT_EQ(track->getArtistLinks().size(), 3); + + auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist.getId())) }; + EXPECT_EQ(tracks.results.size(), 1); + + tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist })); + EXPECT_EQ(tracks.results.size(), 1); + tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })); + EXPECT_EQ(tracks.results.size(), 1); + tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Writer })); + EXPECT_EQ(tracks.results.size(), 1); + + tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer })); + EXPECT_EQ(tracks.results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + core::EnumSet types{ TrackArtistLink::findUsedTypes(session, artist.getId()) }; + EXPECT_TRUE(types.contains(TrackArtistLinkType::ReleaseArtist)); + EXPECT_TRUE(types.contains(TrackArtistLinkType::Artist)); + EXPECT_TRUE(types.contains(TrackArtistLinkType::Writer)); + EXPECT_FALSE(types.contains(TrackArtistLinkType::Composer)); + } } + TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track{ session, "track" }; + ScopedArtist artist1{ session, "artist1" }; + ScopedArtist artist2{ session, "artist2" }; + ASSERT_NE(artist1.getId(), artist2.getId()); - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist.getId()).setNonRelease(true)) }; - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front(), track2.getId()); - } -} + { + auto transaction{ session.createWriteTransaction() }; -TEST_F(DatabaseFixture, Artist_findByRelease) -{ - ScopedArtist artist{ session, "artist" }; - ScopedTrack track{ session, "MyTrack" }; - ScopedRelease release{ session, "MyRelease" }; + TrackArtistLink::create(session, track.get(), artist1.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist2.get(), TrackArtistLinkType::Artist); + } - { - auto transaction{ session.createReadTransaction() }; - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setRelease(release.getId())) }; - EXPECT_EQ(artists.results.size(), 0); + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto artists{ track->getArtists({TrackArtistLinkType::Artist}) }; + ASSERT_EQ(artists.size(), 2); + EXPECT_TRUE((artists[0]->getId() == artist1.getId() && artists[1]->getId() == artist2.getId()) + || (artists[0]->getId() == artist2.getId() && artists[1]->getId() == artist1.getId())); + + EXPECT_EQ(track->getArtists({}).size(), 2); + EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 2); + EXPECT_EQ(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).size(), 0); + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).results.size(), 2); + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Random)).results.size(), 2); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist1->getId())) }; + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track->getId()); + + tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId())); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track->getId()); + + tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::ReleaseArtist })); + EXPECT_EQ(tracks.results.size(), 0); + + tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::Artist })); + EXPECT_EQ(tracks.results.size(), 1); + + tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::ReleaseArtist })); + EXPECT_EQ(tracks.results.size(), 0); + + tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::Artist })); + EXPECT_EQ(tracks.results.size(), 1); + } } + TEST_F(DatabaseFixture, Artist_findByName) { - auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + ScopedArtist artist{ session, "AAA" }; + ScopedTrack track{ session, "MyTrack" }; // filters does not work on orphans + + { + auto transaction{ session.createWriteTransaction() }; + artist.get().modify()->setSortName("ZZZ"); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction{ session.createReadTransaction() }; + + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "N" })).results.size(), 0); + + const auto artistsByAAA{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"A"})) }; + ASSERT_EQ(artistsByAAA.results.size(), 1); + EXPECT_EQ(artistsByAAA.results.front(), artist.getId()); + + const auto artistsByZZZ{ Artist::Artist::findIds(session, Artist::FindParameters {}.setKeywords({"Z"})) }; + ASSERT_EQ(artistsByZZZ.results.size(), 1); + EXPECT_EQ(artistsByZZZ.results.front(), artist.getId()); + + EXPECT_EQ(Artist::find(session, "NNN").size(), 0); + EXPECT_EQ(Artist::find(session, "AAA").size(), 1); + } } + TEST_F(DatabaseFixture, Artist_findByNameEscaped) { - auto transaction{ session.createReadTransaction() }; - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setRelease(release.getId())) }; - EXPECT_EQ(artists.results.size(), 0); + ScopedArtist artist1{ session, R"(MyArtist%)" }; + ScopedArtist artist2{ session, R"(%MyArtist)" }; + ScopedArtist artist3{ session, R"(%_MyArtist)" }; + + ScopedArtist artist4{ session, R"(MyArtist%foo)" }; + ScopedArtist artist5{ session, R"(foo%MyArtist)" }; + ScopedArtist artist6{ session, R"(%AMyArtist)" }; + + { + auto transaction{ session.createReadTransaction() }; + { + const auto artists{ Artist::find(session, R"(MyArtist%)") }; + ASSERT_TRUE(artists.size() == 1); + EXPECT_EQ(artists.front()->getId(), artist1.getId()); + EXPECT_EQ(Artist::find(session, R"(MyArtistFoo)").size(), 0); + } + { + const auto artists{ Artist::find(session, R"(%MyArtist)") }; + ASSERT_TRUE(artists.size() == 1); + EXPECT_EQ(artists.front()->getId(), artist2.getId()); + EXPECT_EQ(Artist::find(session, R"(FooMyArtist)").size(), 0); + } + { + const auto artists{ Artist::find(session, R"(%_MyArtist)") }; + ASSERT_TRUE(artists.size() == 1); + ASSERT_EQ(artists.front()->getId(), artist3.getId()); + EXPECT_EQ(Artist::find(session, R"(%CMyArtist)").size(), 0); + } + } + + { + auto transaction{ session.createReadTransaction() }; + { + const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"MyArtist"})) }; + EXPECT_EQ(artists.results.size(), 6); + } + + { + const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"MyArtist%"}).setSortMethod(ArtistSortMethod::ByName)) }; + ASSERT_EQ(artists.results.size(), 2); + EXPECT_EQ(artists.results[0], artist1.getId()); + EXPECT_EQ(artists.results[1], artist4.getId()); + } + + { + const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"%MyArtist"}).setSortMethod(ArtistSortMethod::ByName)) }; + ASSERT_EQ(artists.results.size(), 2); + EXPECT_EQ(artists.results[0], artist2.getId()); + EXPECT_EQ(artists.results[1], artist5.getId()); + } + + { + const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"_MyArtist"}).setSortMethod(ArtistSortMethod::ByName)) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist3.getId()); + } + } } + TEST_F(DatabaseFixture, Artist_sortMethod) { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setRelease(release.get()); + ScopedArtist artistA{ session, "artistA" }; + ScopedArtist artistB{ session, "artistB" }; + + { + auto transaction{ session.createWriteTransaction() }; + + artistA.get().modify()->setSortName("sortNameB"); + artistB.get().modify()->setSortName("sortNameA"); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto allArtistsByName{ Artist::findIds(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::ByName)) }; + auto allArtistsBySortName{ Artist::findIds(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::BySortName)) }; + + ASSERT_EQ(allArtistsByName.results.size(), 2); + EXPECT_EQ(allArtistsByName.results.front(), artistA.getId()); + EXPECT_EQ(allArtistsByName.results.back(), artistB.getId()); + + ASSERT_EQ(allArtistsBySortName.results.size(), 2); + EXPECT_EQ(allArtistsBySortName.results.front(), artistB.getId()); + EXPECT_EQ(allArtistsBySortName.results.back(), artistA.getId()); + } } + TEST_F(DatabaseFixture, Artist_nonReleaseTracks) { - auto transaction{ session.createReadTransaction() }; - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setRelease(release.getId())) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); + ScopedArtist artist{ session, "artist" }; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedRelease release{ session, "MyRelease" }; + + { + auto transaction{ session.createReadTransaction() }; + + auto tracks{ Track::findIds(session, Track::FindParameters {}.setNonRelease(true).setArtist(artist->getId())) }; + EXPECT_EQ(tracks.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + + TrackArtistLink::create(session, track1.get(), artist.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track2.get(), artist.get(), TrackArtistLinkType::Artist); + + track1.get().modify()->setRelease(release.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist.getId()).setNonRelease(true)) }; + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track2.getId()); + } } -} + + TEST_F(DatabaseFixture, Artist_findByRelease) + { + ScopedArtist artist{ session, "artist" }; + ScopedTrack track{ session, "MyTrack" }; + ScopedRelease release{ session, "MyRelease" }; + + { + auto transaction{ session.createReadTransaction() }; + const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setRelease(release.getId())) }; + EXPECT_EQ(artists.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction{ session.createReadTransaction() }; + const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setRelease(release.getId())) }; + EXPECT_EQ(artists.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setRelease(release.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setRelease(release.getId())) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + } + } +} \ No newline at end of file diff --git a/src/libs/database/test/Cluster.cpp b/src/libs/database/test/Cluster.cpp index 58ce6a3c..f332a43c 100644 --- a/src/libs/database/test/Cluster.cpp +++ b/src/libs/database/test/Cluster.cpp @@ -22,830 +22,831 @@ #include #include -using namespace Database; - -TEST_F(DatabaseFixture, Cluster) +namespace lms::db::tests { + TEST_F(DatabaseFixture, Cluster) { - auto transaction{ session.createWriteTransaction() }; - EXPECT_EQ(Cluster::getCount(session), 0); - EXPECT_EQ(ClusterType::getCount(session), 0); + { + auto transaction{ session.createWriteTransaction() }; + EXPECT_EQ(Cluster::getCount(session), 0); + EXPECT_EQ(ClusterType::getCount(session), 0); + } + + ScopedClusterType clusterType{ session, "MyType" }; + + { + auto transaction{ session.createWriteTransaction() }; + EXPECT_EQ(ClusterType::getCount(session), 1); + } + + { + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + + { + auto transaction{ session.createWriteTransaction() }; + + EXPECT_EQ(Cluster::getCount(session), 1); + EXPECT_EQ(cluster->getType()->getId(), clusterType.getId()); + + { + const auto clusters{ Cluster::findIds(session, Cluster::FindParameters {}) }; + ASSERT_EQ(clusters.results.size(), 1); + EXPECT_EQ(clusters.results.front(), cluster.getId()); + } + + { + const auto clusters{ Cluster::findOrphanIds(session) }; + ASSERT_EQ(clusters.results.size(), 1); + EXPECT_EQ(clusters.results.front(), cluster.getId()); + } + + auto clusterTypes{ ClusterType::findIds(session) }; + ASSERT_EQ(clusterTypes.results.size(), 1); + EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); + + clusterTypes = ClusterType::findUsed(session); + ASSERT_EQ(clusterTypes.results.size(), 1); + EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); + + clusterTypes = ClusterType::findOrphanIds(session); + EXPECT_EQ(clusterTypes.results.size(), 0); + } + } + + { + auto transaction{ session.createWriteTransaction() }; + + auto clusterTypes{ ClusterType::findOrphanIds(session) }; + ASSERT_EQ(clusterTypes.results.size(), 1); + EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); + + ASSERT_EQ(ClusterType::findUsed(session).results.size(), 0); + } } - ScopedClusterType clusterType{ session, "MyType" }; - + TEST_F(DatabaseFixture, Cluster_singleTrack) { - auto transaction{ session.createWriteTransaction() }; - EXPECT_EQ(ClusterType::getCount(session), 1); + ScopedTrack track{ session, "MyTrack" }; + ScopedClusterType clusterType{ session, "MyClusterType" }; + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); + auto clusterTypes{ ClusterType::findOrphanIds(session) }; + ASSERT_EQ(clusterTypes.results.size(), 1); + EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); + } + + ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; + ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; + + { + auto transaction{ session.createReadTransaction() }; + auto clusters{ Cluster::findOrphanIds(session) }; + EXPECT_EQ(clusters.results.size(), 2); + EXPECT_EQ(track->getClusters().size(), 0); + EXPECT_EQ(track->getClusterIds().size(), 0); + EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 0); + EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + + cluster1.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + auto clusters{ Cluster::findIds(session, Cluster::FindParameters {}.setTrack(track.getId())) }; + ASSERT_EQ(clusters.results.size(), 1); + EXPECT_EQ(clusters.results.front(), cluster1.getId()); + EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1); + EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + auto clusters{ Cluster::findOrphanIds(session) }; + ASSERT_EQ(clusters.results.size(), 1); + EXPECT_EQ(clusters.results.front(), cluster2.getId()); + + EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto tracks{ Track::findIds(session, Track::FindParameters {}.setClusters({cluster1.getId()})) }; + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track.getId()); + + tracks = Track::findIds(session, Track::FindParameters{}.setClusters({ cluster2.getId() })); + EXPECT_EQ(tracks.results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto clusters{ track->getClusters() }; + ASSERT_EQ(clusters.size(), 1); + EXPECT_EQ(clusters.front()->getId(), cluster1.getId()); + + auto clusterIds{ track->getClusterIds() }; + ASSERT_EQ(clusterIds.size(), 1); + EXPECT_EQ(clusterIds.front(), cluster1.getId()); + } } + TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters) { + ScopedTrack track{ session, "MyTrack" }; + ScopedClusterType clusterType{ session, "MyClusterType" }; + + ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; + ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; + + const std::vector clusterIds{ cluster1.getId(), cluster2.getId() }; + + { + auto transaction{ session.createReadTransaction() }; + + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; + EXPECT_EQ(tracks.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + + cluster1.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; + EXPECT_EQ(tracks.results.size(), 0); + EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1); + EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + + cluster2.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track.getId()); + EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1); + EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 1); + } + } + + TEST_F(DatabaseFixture, Cluster_multiTracks) + { + std::list tracks; + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + + for (std::size_t i{}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + { + auto transaction{ session.createWriteTransaction() }; + cluster.get().modify()->addTrack(tracks.back().get()); + } + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); + + EXPECT_EQ(Cluster::computeTrackCount(session, cluster.getId()), tracks.size()); + + for (TrackId trackId : cluster->getTracks().results) + { + auto it{ std::find_if(std::cbegin(tracks), std::cend(tracks), [&](const ScopedTrack& track) { return trackId == track.getId(); }) }; + EXPECT_TRUE(it != std::cend(tracks)); + } + } + } + + + TEST_F(DatabaseFixture, ClusterType_singleTrack) + { + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}).results.size(), 0); + EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).results.size(), 0); + } + + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + + { + auto transaction{ session.createReadTransaction() }; + auto clusters{ Cluster::findIds(session, Cluster::FindParameters{}).results }; + ASSERT_EQ(clusters.size(), 1); + EXPECT_EQ(clusters.front(), cluster.getId()); + + clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterType(clusterType.getId())).results; + ASSERT_EQ(clusters.size(), 1); + EXPECT_EQ(clusters.front(), cluster.getId()); + + clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).results; + EXPECT_EQ(clusters.size(), 0); + + clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterTypeName("MyClusterType")).results; + ASSERT_EQ(clusters.size(), 1); + EXPECT_EQ(clusters.front(), cluster.getId()); + } + } + + TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster) + { + ScopedTrack track{ session, "MyTrackFile" }; + ScopedRelease release{ session, "MyRelease" }; + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); + } + + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + ScopedCluster unusedCluster{ session, clusterType.lockAndGet(), "MyClusterUnused" }; + + { + auto transaction{ session.createReadTransaction() }; + ASSERT_EQ(Cluster::findOrphanIds(session).results.size(), 2); + EXPECT_EQ(Release::find(session, Release::FindParameters{}.setClusters({ unusedCluster.getId() })).results.size(), 0); + EXPECT_EQ(Release::find(session, Release::FindParameters{}).results.size(), 1); + EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0); + EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + + track.get().modify()->setRelease(release.get()); + cluster.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + { + auto clusters{ Cluster::findOrphanIds(session) }; + ASSERT_EQ(clusters.results.size(), 1); + EXPECT_EQ(clusters.results.front(), unusedCluster.getId()); + } + EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 1); + EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto clusters{ Cluster::findIds(session, Cluster::FindParameters{}.setRelease(release.getId())) }; + ASSERT_EQ(clusters.results.size(), 1); + EXPECT_EQ(clusters.results.front(), cluster.getId()); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters({cluster.getId()})) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters({unusedCluster.getId()})) }; + EXPECT_EQ(releases.results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + + EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 1); + EXPECT_EQ(Cluster::computeTrackCount(session, cluster.getId()), 1); + EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0); + EXPECT_EQ(Cluster::computeTrackCount(session, unusedCluster.getId()), 0); + } + } + + TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters) + { + ScopedTrack track{ session, "MyTrackFile" }; + ScopedArtist artist{ session, "MyArtist" }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster1{ session, clusterType.lockAndGet(), "Cluster1" }; + ScopedCluster cluster2{ session, clusterType.lockAndGet(), "Cluster2" }; + ScopedCluster cluster3{ session, clusterType.lockAndGet(), "Cluster3" }; + { + auto transaction{ session.createWriteTransaction() }; + + auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) }; + cluster1.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 2); + EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(track->getClusters().size(), 1); + EXPECT_EQ(track->getClusterIds().size(), 1); + } + + { + auto transaction{ session.createWriteTransaction() }; + + auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster1.getId()})) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster2.getId() })).results.size(), 0); + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster3.getId() })).results.size(), 0); + + cluster2.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster1.getId()})) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + + artists = Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster2.getId() })); + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + + artists = Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster1.getId() })); + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + + EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster3.getId() })).results.size(), 0); + } + } + + TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiRolesMultiClusters) + { + ScopedTrack track{ session, "MyTrackFile" }; + ScopedArtist artist{ session, "MyArtist" }; + ScopedClusterType clusterType{ session, "MyType" }; ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; { auto transaction{ session.createWriteTransaction() }; - EXPECT_EQ(Cluster::getCount(session), 1); - EXPECT_EQ(cluster->getType()->getId(), clusterType.getId()); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist); + cluster.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster.getId()})) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + } + } + + TEST_F(DatabaseFixture, MultiTracksSingleArtistMultiClusters) + { + constexpr std::size_t nbTracks{ 10 }; + constexpr std::size_t nbClusters{ 5 }; + + std::list tracks; + std::list clusters; + ScopedArtist artist{ session, "MyArtist" }; + ScopedClusterType clusterType{ session, "MyType" }; + + for (std::size_t i{}; i < nbClusters; ++i) + clusters.emplace_back(session, clusterType.lockAndGet(), "MyCluster" + std::to_string(i)); + + for (std::size_t i{}; i < nbTracks; ++i) + { + tracks.emplace_back(session, "MyTrackFile" + std::to_string(i)); + + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist); + + for (auto& cluster : clusters) + cluster.get().modify()->addTrack(tracks.back().get()); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + + std::vector clusterIds; + std::transform(std::cbegin(clusters), std::cend(clusters), std::back_inserter(clusterIds), [](const ScopedCluster& cluster) { return cluster.getId(); }); + + auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters(clusterIds)) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + } + } + + + TEST_F(DatabaseFixture, MultipleTracksSingleClusterSimilarity) + { + std::list tracks; + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyClusterType" }; + + for (std::size_t i{}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); { - const auto clusters{ Cluster::findIds(session, Cluster::FindParameters {}) }; - ASSERT_EQ(clusters.results.size(), 1); - EXPECT_EQ(clusters.results.front(), cluster.getId()); + auto transaction{ session.createWriteTransaction() }; + cluster.get().modify()->addTrack(tracks.back().get()); } - - { - const auto clusters{ Cluster::findOrphanIds(session) }; - ASSERT_EQ(clusters.results.size(), 1); - EXPECT_EQ(clusters.results.front(), cluster.getId()); - } - - auto clusterTypes{ ClusterType::findIds(session) }; - ASSERT_EQ(clusterTypes.results.size(), 1); - EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); - - clusterTypes = ClusterType::findUsed(session); - ASSERT_EQ(clusterTypes.results.size(), 1); - EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); - - clusterTypes = ClusterType::findOrphanIds(session); - EXPECT_EQ(clusterTypes.results.size(), 0); - } - } - - { - auto transaction{ session.createWriteTransaction() }; - - auto clusterTypes{ ClusterType::findOrphanIds(session) }; - ASSERT_EQ(clusterTypes.results.size(), 1); - EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); - - ASSERT_EQ(ClusterType::findUsed(session).results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Cluster_singleTrack) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedClusterType clusterType{ session, "MyClusterType" }; - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); - auto clusterTypes{ ClusterType::findOrphanIds(session) }; - ASSERT_EQ(clusterTypes.results.size(), 1); - EXPECT_EQ(clusterTypes.results.front(), clusterType.getId()); - } - - ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; - ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; - - { - auto transaction{ session.createReadTransaction() }; - auto clusters{ Cluster::findOrphanIds(session) }; - EXPECT_EQ(clusters.results.size(), 2); - EXPECT_EQ(track->getClusters().size(), 0); - EXPECT_EQ(track->getClusterIds().size(), 0); - EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 0); - EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - - cluster1.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - auto clusters{ Cluster::findIds(session, Cluster::FindParameters {}.setTrack(track.getId())) }; - ASSERT_EQ(clusters.results.size(), 1); - EXPECT_EQ(clusters.results.front(), cluster1.getId()); - EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1); - EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - auto clusters{ Cluster::findOrphanIds(session) }; - ASSERT_EQ(clusters.results.size(), 1); - EXPECT_EQ(clusters.results.front(), cluster2.getId()); - - EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto tracks{ Track::findIds(session, Track::FindParameters {}.setClusters({cluster1.getId()})) }; - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front(), track.getId()); - - tracks = Track::findIds(session, Track::FindParameters{}.setClusters({ cluster2.getId() })); - EXPECT_EQ(tracks.results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto clusters{ track->getClusters() }; - ASSERT_EQ(clusters.size(), 1); - EXPECT_EQ(clusters.front()->getId(), cluster1.getId()); - - auto clusterIds{ track->getClusterIds() }; - ASSERT_EQ(clusterIds.size(), 1); - EXPECT_EQ(clusterIds.front(), cluster1.getId()); - } -} - -TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedClusterType clusterType{ session, "MyClusterType" }; - - ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; - ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; - - const std::vector clusterIds{ cluster1.getId(), cluster2.getId() }; - - { - auto transaction{ session.createReadTransaction() }; - - const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; - EXPECT_EQ(tracks.results.size(), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - - cluster1.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; - EXPECT_EQ(tracks.results.size(), 0); - EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1); - EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - - cluster2.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) }; - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front(), track.getId()); - EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1); - EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 1); - } -} - -TEST_F(DatabaseFixture, Cluster_multiTracks) -{ - std::list tracks; - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - - for (std::size_t i{}; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - { - auto transaction{ session.createWriteTransaction() }; - cluster.get().modify()->addTrack(tracks.back().get()); - } - } - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); - - EXPECT_EQ(Cluster::computeTrackCount(session, cluster.getId()), tracks.size()); - - for (TrackId trackId : cluster->getTracks().results) - { - auto it{ std::find_if(std::cbegin(tracks), std::cend(tracks), [&](const ScopedTrack& track) { return trackId == track.getId(); }) }; - EXPECT_TRUE(it != std::cend(tracks)); - } - } -} - - -TEST_F(DatabaseFixture, ClusterType_singleTrack) -{ - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}).results.size(), 0); - EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).results.size(), 0); - } - - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - - { - auto transaction{ session.createReadTransaction() }; - auto clusters {Cluster::findIds(session, Cluster::FindParameters{}).results}; - ASSERT_EQ(clusters.size(), 1); - EXPECT_EQ(clusters.front(), cluster.getId()); - - clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterType(clusterType.getId())).results; - ASSERT_EQ(clusters.size(), 1); - EXPECT_EQ(clusters.front(), cluster.getId()); - - clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).results; - EXPECT_EQ(clusters.size(), 0); - - clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterTypeName("MyClusterType")).results; - ASSERT_EQ(clusters.size(), 1); - EXPECT_EQ(clusters.front(), cluster.getId()); - } -} - -TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster) -{ - ScopedTrack track{ session, "MyTrackFile" }; - ScopedRelease release{ session, "MyRelease" }; - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); - } - - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - ScopedCluster unusedCluster{ session, clusterType.lockAndGet(), "MyClusterUnused" }; - - { - auto transaction{ session.createReadTransaction() }; - ASSERT_EQ(Cluster::findOrphanIds(session).results.size(), 2); - EXPECT_EQ(Release::find(session, Release::FindParameters{}.setClusters({ unusedCluster.getId() })).results.size(), 0); - EXPECT_EQ(Release::find(session, Release::FindParameters{}).results.size(), 1); - EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0); - EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - - track.get().modify()->setRelease(release.get()); - cluster.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - { - auto clusters{ Cluster::findOrphanIds(session) }; - ASSERT_EQ(clusters.results.size(), 1); - EXPECT_EQ(clusters.results.front(), unusedCluster.getId()); - } - EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 1); - EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto clusters{ Cluster::findIds(session, Cluster::FindParameters{}.setRelease(release.getId())) }; - ASSERT_EQ(clusters.results.size(), 1); - EXPECT_EQ(clusters.results.front(), cluster.getId()); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters({cluster.getId()})) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters({unusedCluster.getId()})) }; - EXPECT_EQ(releases.results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 1); - EXPECT_EQ(Cluster::computeTrackCount(session, cluster.getId()), 1); - EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0); - EXPECT_EQ(Cluster::computeTrackCount(session, unusedCluster.getId()), 0); - } -} - -TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters) -{ - ScopedTrack track{ session, "MyTrackFile" }; - ScopedArtist artist{ session, "MyArtist" }; - ScopedClusterType clusterType{ session, "MyType" }; - ScopedCluster cluster1{ session, clusterType.lockAndGet(), "Cluster1" }; - ScopedCluster cluster2{ session, clusterType.lockAndGet(), "Cluster2" }; - ScopedCluster cluster3{ session, clusterType.lockAndGet(), "Cluster3" }; - { - auto transaction{ session.createWriteTransaction() }; - - auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) }; - cluster1.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0); - EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 2); - EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); - EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(track->getClusters().size(), 1); - EXPECT_EQ(track->getClusterIds().size(), 1); - } - - { - auto transaction{ session.createWriteTransaction() }; - - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster1.getId()})) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - - EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster2.getId() })).results.size(), 0); - EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster3.getId() })).results.size(), 0); - - cluster2.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster1.getId()})) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - - artists = Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster2.getId() })); - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - - artists = Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster1.getId() })); - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - - EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setClusters({ cluster3.getId() })).results.size(), 0); - } -} - -TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiRolesMultiClusters) -{ - ScopedTrack track{ session, "MyTrackFile" }; - ScopedArtist artist{ session, "MyArtist" }; - ScopedClusterType clusterType{ session, "MyType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - - { - auto transaction{ session.createWriteTransaction() }; - - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist); - cluster.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); - EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); - EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster.getId()})) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - } -} - -TEST_F(DatabaseFixture, MultiTracksSingleArtistMultiClusters) -{ - constexpr std::size_t nbTracks{ 10 }; - constexpr std::size_t nbClusters{ 5 }; - - std::list tracks; - std::list clusters; - ScopedArtist artist{ session, "MyArtist" }; - ScopedClusterType clusterType{ session, "MyType" }; - - for (std::size_t i{}; i < nbClusters; ++i) - clusters.emplace_back(session, clusterType.lockAndGet(), "MyCluster" + std::to_string(i)); - - for (std::size_t i{}; i < nbTracks; ++i) - { - tracks.emplace_back(session, "MyTrackFile" + std::to_string(i)); - - auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist); - - for (auto& cluster : clusters) - cluster.get().modify()->addTrack(tracks.back().get()); - } - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); - EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - - std::vector clusterIds; - std::transform(std::cbegin(clusters), std::cend(clusters), std::back_inserter(clusterIds), [](const ScopedCluster& cluster) { return cluster.getId(); }); - - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters(clusterIds)) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - } -} - - -TEST_F(DatabaseFixture, MultipleTracksSingleClusterSimilarity) -{ - std::list tracks; - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyClusterType" }; - - for (std::size_t i{}; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - { - auto transaction{ session.createWriteTransaction() }; - cluster.get().modify()->addTrack(tracks.back().get()); - } - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}) }; - EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1); - for (const TrackId similarTrackId : similarTracks.results) - { - EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks)); - } - } -} - -TEST_F(DatabaseFixture, MultipleTracksMultipleClustersSimilarity) -{ - std::list tracks; - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; - ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; - - for (std::size_t i{}; i < 5; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - { - auto transaction{ session.createWriteTransaction() }; - cluster1.get().modify()->addTrack(tracks.back().get()); - } - } - - for (std::size_t i{ 5 }; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - { - auto transaction{ session.createWriteTransaction() }; - cluster1.get().modify()->addTrack(tracks.back().get()); - cluster2.get().modify()->addTrack(tracks.back().get()); - } - } - - { - auto transaction{ session.createReadTransaction() }; - - { - auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.back().getId()}, Range {0, 4}) }; - EXPECT_EQ(similarTracks.results.size(), 4); - for (const TrackId similarTrackId : similarTracks.results) - EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 5), std::next(std::cend(tracks), -1), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks)); } { - auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}) }; + auto transaction{ session.createReadTransaction() }; + + const auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}) }; EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1); for (const TrackId similarTrackId : similarTracks.results) + { EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks)); - } - } -} - -TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedRelease release{ session, "MyRelease" }; - ScopedArtist artist{ session, "MyArtist" }; - ScopedClusterType clusterType{ session, "MyType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - - { - auto transaction{ session.createWriteTransaction() }; - - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - track.get().modify()->setRelease(release.get()); - cluster.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); - EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0); - EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); - EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster.getId()})) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist.getId()); - - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters({ cluster.getId() })); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - } -} - -TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistMultiClusters) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedRelease release{ session, "MyRelease" }; - ScopedArtist artist{ session, "MyArtist" }; - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; - ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; - - { - auto transaction{ session.createWriteTransaction() }; - - auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) }; - track.get().modify()->setRelease(release.get()); - cluster1.get().modify()->addTrack(track.get()); - cluster2.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters({ cluster1.getId(), cluster2.getId() })); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - } -} - -TEST_F(DatabaseFixture, SingleTrackListMultipleTrackSingleCluster) -{ - ScopedUser user{ session, "MyUser" }; - ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() }; - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - std::list tracks; - - for (std::size_t i{}; i < 20; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - auto transaction{ session.createWriteTransaction() }; - - if (i < 5) - session.create(tracks.back().get(), trackList.get()); - - if (i < 10) - cluster.get().modify()->addTrack(tracks.back().get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto similarTracks{ trackList->getSimilarTracks() }; - EXPECT_EQ(similarTracks.size(), 5); - - for (auto similarTrack : similarTracks) - EXPECT_TRUE(std::any_of(std::next(std::cbegin(tracks), 5), std::cend(tracks), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack->getId(); })); - } -} - -TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters) -{ - ScopedUser user{ session, "MyUser" }; - ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() }; - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; - ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; - std::list tracks; - - for (std::size_t i{}; i < 20; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - auto transaction{ session.createWriteTransaction() }; - - if (i < 5) - session.create(tracks.back().get(), trackList.get()); - - if (i < 10) - { - cluster1.get().modify()->addTrack(tracks.back().get()); - cluster2.get().modify()->addTrack(tracks.back().get()); - } - else if (i < 15) - { - cluster1.get().modify()->addTrack(tracks.back().get()); + } } } + TEST_F(DatabaseFixture, MultipleTracksMultipleClustersSimilarity) { - auto transaction{ session.createReadTransaction() }; + std::list tracks; + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; + ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; + + for (std::size_t i{}; i < 5; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + { + auto transaction{ session.createWriteTransaction() }; + cluster1.get().modify()->addTrack(tracks.back().get()); + } + } + + for (std::size_t i{ 5 }; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + { + auto transaction{ session.createWriteTransaction() }; + cluster1.get().modify()->addTrack(tracks.back().get()); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + } { - const auto similarTracks{ trackList->getSimilarTracks(0, 5) }; - ASSERT_EQ(similarTracks.size(), 5); + auto transaction{ session.createReadTransaction() }; + + { + auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.back().getId()}, Range {0, 4}) }; + EXPECT_EQ(similarTracks.results.size(), 4); + for (const TrackId similarTrackId : similarTracks.results) + EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 5), std::next(std::cend(tracks), -1), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks)); + } + + { + auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}) }; + EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1); + for (const TrackId similarTrackId : similarTracks.results) + EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks)); + } + } + } + + TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedRelease release{ session, "MyRelease" }; + ScopedArtist artist{ session, "MyArtist" }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + + { + auto transaction{ session.createWriteTransaction() }; + + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + track.get().modify()->setRelease(release.get()); + cluster.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0); + EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster.getId()})) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist.getId()); + + auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters({ cluster.getId() })); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + } + } + + TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistMultiClusters) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedRelease release{ session, "MyRelease" }; + ScopedArtist artist{ session, "MyArtist" }; + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; + ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; + + { + auto transaction{ session.createWriteTransaction() }; + + auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) }; + track.get().modify()->setRelease(release.get()); + cluster1.get().modify()->addTrack(track.get()); + cluster2.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setClusters({ cluster1.getId(), cluster2.getId() })); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + } + } + + TEST_F(DatabaseFixture, SingleTrackListMultipleTrackSingleCluster) + { + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + std::list tracks; + + for (std::size_t i{}; i < 20; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction{ session.createWriteTransaction() }; + + if (i < 5) + session.create(tracks.back().get(), trackList.get()); + + if (i < 10) + cluster.get().modify()->addTrack(tracks.back().get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto similarTracks{ trackList->getSimilarTracks() }; + EXPECT_EQ(similarTracks.size(), 5); for (auto similarTrack : similarTracks) - EXPECT_TRUE(std::any_of(std::next(std::cbegin(tracks), 5), std::next(std::cbegin(tracks), 10), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack->getId(); })); + EXPECT_TRUE(std::any_of(std::next(std::cbegin(tracks), 5), std::cend(tracks), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack->getId(); })); + } + } + + TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters) + { + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; + ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; + std::list tracks; + + for (std::size_t i{}; i < 20; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction{ session.createWriteTransaction() }; + + if (i < 5) + session.create(tracks.back().get(), trackList.get()); + + if (i < 10) + { + cluster1.get().modify()->addTrack(tracks.back().get()); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + else if (i < 15) + { + cluster1.get().modify()->addTrack(tracks.back().get()); + } } { - const auto similarTracks{ trackList->getSimilarTracks(5, 10) }; - ASSERT_EQ(similarTracks.size(), 5); + auto transaction{ session.createReadTransaction() }; - for (auto similarTrack : similarTracks) - EXPECT_TRUE(std::any_of(std::next(std::cbegin(tracks), 10), std::next(std::cbegin(tracks), 15), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack->getId(); })); + { + const auto similarTracks{ trackList->getSimilarTracks(0, 5) }; + ASSERT_EQ(similarTracks.size(), 5); + + for (auto similarTrack : similarTracks) + EXPECT_TRUE(std::any_of(std::next(std::cbegin(tracks), 5), std::next(std::cbegin(tracks), 10), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack->getId(); })); + } + + { + const auto similarTracks{ trackList->getSimilarTracks(5, 10) }; + ASSERT_EQ(similarTracks.size(), 5); + + for (auto similarTrack : similarTracks) + EXPECT_TRUE(std::any_of(std::next(std::cbegin(tracks), 10), std::next(std::cbegin(tracks), 15), [similarTrack](const ScopedTrack& track) { return track.getId() == similarTrack->getId(); })); + } + + EXPECT_EQ(trackList->getSimilarTracks(10, 10).size(), 0); + } + } + + TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters) + { + ScopedArtist artist1{ session, "MyArtist1" }; + ScopedArtist artist2{ session, "MyArtist2" }; + ScopedArtist artist3{ session, "MyArtist3" }; + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; + ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(artist1->findSimilarArtistIds().results.size(), 0); + EXPECT_EQ(artist2->findSimilarArtistIds().results.size(), 0); + EXPECT_EQ(artist3->findSimilarArtistIds().results.size(), 0); } - EXPECT_EQ(trackList->getSimilarTracks(10, 10).size(), 0); - } -} - -TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters) -{ - ScopedArtist artist1{ session, "MyArtist1" }; - ScopedArtist artist2{ session, "MyArtist2" }; - ScopedArtist artist3{ session, "MyArtist3" }; - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; - ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(artist1->findSimilarArtistIds().results.size(), 0); - EXPECT_EQ(artist2->findSimilarArtistIds().results.size(), 0); - EXPECT_EQ(artist3->findSimilarArtistIds().results.size(), 0); - } - - std::list tracks; - for (std::size_t i{}; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - auto transaction{ session.createWriteTransaction() }; - - if (i < 5) - TrackArtistLink::create(session, tracks.back().get(), artist1.get(), TrackArtistLinkType::Artist); - else + std::list tracks; + for (std::size_t i{}; i < 10; ++i) { - TrackArtistLink::create(session, tracks.back().get(), artist2.get(), TrackArtistLinkType::Artist); + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction{ session.createWriteTransaction() }; + + if (i < 5) + TrackArtistLink::create(session, tracks.back().get(), artist1.get(), TrackArtistLinkType::Artist); + else + { + TrackArtistLink::create(session, tracks.back().get(), artist2.get(), TrackArtistLinkType::Artist); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + + cluster1.get().modify()->addTrack(tracks.back().get()); + } + + tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size())); + { + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, tracks.back().get(), artist3.get(), TrackArtistLinkType::Artist); cluster2.get().modify()->addTrack(tracks.back().get()); } - cluster1.get().modify()->addTrack(tracks.back().get()); - } - - tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size())); - { - auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, tracks.back().get(), artist3.get(), TrackArtistLinkType::Artist); - cluster2.get().modify()->addTrack(tracks.back().get()); - } - - { - auto transaction{ session.createReadTransaction() }; - { - auto artists{ artist1->findSimilarArtistIds() }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist2.getId()); - } + auto transaction{ session.createReadTransaction() }; - { - auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::Artist}) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist2.getId()); - } + { + auto artists{ artist1->findSimilarArtistIds() }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist2.getId()); + } - { - auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::ReleaseArtist}) }; - EXPECT_EQ(artists.results.size(), 0); - } + { + auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::Artist}) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist2.getId()); + } - { - auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results.front(), artist2.getId()); - } + { + auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::ReleaseArtist}) }; + EXPECT_EQ(artists.results.size(), 0); + } - { - auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::Composer}) }; - EXPECT_EQ(artists.results.size(), 0); - } + { + auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results.front(), artist2.getId()); + } - { - auto artists{ artist2->findSimilarArtistIds() }; - ASSERT_EQ(artists.results.size(), 2); - EXPECT_EQ(artists.results[0], artist1.getId()); - EXPECT_EQ(artists.results[1], artist3.getId()); + { + auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::Composer}) }; + EXPECT_EQ(artists.results.size(), 0); + } + + { + auto artists{ artist2->findSimilarArtistIds() }; + ASSERT_EQ(artists.results.size(), 2); + EXPECT_EQ(artists.results[0], artist1.getId()); + EXPECT_EQ(artists.results[1], artist3.getId()); + } } } -} - -TEST_F(DatabaseFixture, MultipleTracksMultipleReleasesMultiClusters) -{ - ScopedRelease release1{ session, "MyRelease1" }; - ScopedRelease release2{ session, "MyRelease2" }; - ScopedRelease release3{ session, "MyRelease3" }; - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; - ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; + TEST_F(DatabaseFixture, MultipleTracksMultipleReleasesMultiClusters) { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(release1->getSimilarReleases().size(), 0); - EXPECT_EQ(release2->getSimilarReleases().size(), 0); - EXPECT_EQ(release3->getSimilarReleases().size(), 0); - } + ScopedRelease release1{ session, "MyRelease1" }; + ScopedRelease release2{ session, "MyRelease2" }; + ScopedRelease release3{ session, "MyRelease3" }; + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" }; + ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" }; - std::list tracks; - for (std::size_t i{}; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - auto transaction{ session.createWriteTransaction() }; - - if (i < 5) - tracks.back().get().modify()->setRelease(release1.get()); - else { - tracks.back().get().modify()->setRelease(release2.get()); + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release1->getSimilarReleases().size(), 0); + EXPECT_EQ(release2->getSimilarReleases().size(), 0); + EXPECT_EQ(release3->getSimilarReleases().size(), 0); + } + + std::list tracks; + for (std::size_t i{}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + + auto transaction{ session.createWriteTransaction() }; + + if (i < 5) + tracks.back().get().modify()->setRelease(release1.get()); + else + { + tracks.back().get().modify()->setRelease(release2.get()); + cluster2.get().modify()->addTrack(tracks.back().get()); + } + + cluster1.get().modify()->addTrack(tracks.back().get()); + } + + tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size())); + { + auto transaction{ session.createWriteTransaction() }; + tracks.back().get().modify()->setRelease(release3.get()); cluster2.get().modify()->addTrack(tracks.back().get()); } - cluster1.get().modify()->addTrack(tracks.back().get()); - } - - tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size())); - { - auto transaction{ session.createWriteTransaction() }; - tracks.back().get().modify()->setRelease(release3.get()); - cluster2.get().modify()->addTrack(tracks.back().get()); - } - - { - auto transaction{ session.createReadTransaction() }; - { - auto releases{ release1->getSimilarReleases() }; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release2.getId()); - } + auto transaction{ session.createReadTransaction() }; - { - auto releases{ release2->getSimilarReleases() }; - ASSERT_EQ(releases.size(), 2); - EXPECT_EQ(releases[0]->getId(), release1.getId()); - EXPECT_EQ(releases[1]->getId(), release3.getId()); + { + auto releases{ release1->getSimilarReleases() }; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front()->getId(), release2.getId()); + } + + { + auto releases{ release2->getSimilarReleases() }; + ASSERT_EQ(releases.size(), 2); + EXPECT_EQ(releases[0]->getId(), release1.getId()); + EXPECT_EQ(releases[1]->getId(), release3.getId()); + } } } -} +} \ No newline at end of file diff --git a/src/libs/database/test/Common.cpp b/src/libs/database/test/Common.cpp index 2015cacb..ac3089ab 100644 --- a/src/libs/database/test/Common.cpp +++ b/src/libs/database/test/Common.cpp @@ -36,199 +36,201 @@ #include "database/Types.hpp" #include "database/User.hpp" -TmpDatabase::TmpDatabase() - : _tmpFile{ std::tmpnam(nullptr) } - , _fileDeleter{ _tmpFile } - , _db{ _tmpFile } +namespace lms::db::tests { -} - -Database::Db& TmpDatabase::getDb() -{ - return _db; -} - -DatabaseFixture::~DatabaseFixture() -{ - testDatabaseEmpty(); -} - -void DatabaseFixture::SetUpTestCase() -{ - _tmpDb = std::make_unique(); + TmpDatabase::TmpDatabase() + : _tmpFile{ std::tmpnam(nullptr) } + , _fileDeleter{ _tmpFile } + , _db{ _tmpFile } { - Database::Session s{ _tmpDb->getDb() }; - s.prepareTables(); - s.analyze(); - } -} - -void DatabaseFixture::TearDownTestCase() -{ - _tmpDb.reset(); -} - -void DatabaseFixture::testDatabaseEmpty() -{ - using namespace Database; - - auto transaction{ session.createWriteTransaction() }; - - EXPECT_EQ(Artist::getCount(session), 0); - EXPECT_EQ(Cluster::getCount(session), 0); - EXPECT_EQ(ClusterType::getCount(session), 0); - EXPECT_EQ(Listen::getCount(session), 0); - EXPECT_EQ(MediaLibrary::getCount(session), 0); - EXPECT_EQ(Release::getCount(session), 0); - EXPECT_EQ(StarredArtist::getCount(session), 0); - EXPECT_EQ(StarredRelease::getCount(session), 0); - EXPECT_EQ(StarredTrack::getCount(session), 0); - EXPECT_EQ(Track::getCount(session), 0); - EXPECT_EQ(TrackBookmark::getCount(session), 0); - EXPECT_EQ(TrackList::getCount(session), 0); - EXPECT_EQ(User::getCount(session), 0); -} - -TEST_F(DatabaseFixture, Common_subRangeEmpty) -{ - using namespace Database; - - RangeResults results; - results.range = Range{ 0, 0 }; - results.results = {}; - results.moreResults = false; - - { - auto subRange{ results.getSubRange(Range {0, 0}) }; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 0); - EXPECT_EQ(subRange.range, Range{}); - } - { - auto subRange{ results.getSubRange(Range {0, 1}) }; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Common_subRangeForeach) -{ - using namespace Database; - - struct TestCase - { - Range range; - std::size_t subRangeSize; - std::vector 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 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) -{ - using namespace Database; - - { - const IdType id{}; - EXPECT_FALSE(id.isValid()); } + db::Db& TmpDatabase::getDb() { - const IdType id{ 0 }; - EXPECT_TRUE(id.isValid()); + return _db; } + DatabaseFixture::~DatabaseFixture() { - const IdType id1{ 0 }; - const IdType id2{ 0 }; - EXPECT_EQ(id1, id2); + testDatabaseEmpty(); } + void DatabaseFixture::SetUpTestCase() { - const IdType id1{ 0 }; - const IdType id2{ 1 }; - EXPECT_NE(id1, id2); - EXPECT_LT(id1, id2); - EXPECT_GT(id2, id1); - } -} - -TEST_F(DatabaseFixture, Common_subRange) -{ - using namespace Database; - - RangeResults results; - results.range = Range{ 0, 2 }; - results.results = { 5, 6 }; - results.moreResults = false; - - { - auto subRange{ results.getSubRange(Range {0, 1}) }; - EXPECT_TRUE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 1); - EXPECT_EQ(subRange.results.front(), 5); - } - { - auto subRange{ results.getSubRange(Range {1, 1}) }; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 1); - EXPECT_EQ(subRange.results.front(), 6); - } - { - auto subRange{ results.getSubRange(Range {0, 2}) }; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 2); - EXPECT_EQ(subRange.results.front(), 5); - EXPECT_EQ(subRange.results.back(), 6); - } - { - auto subRange{ results.getSubRange(Range {}) }; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 2); - EXPECT_EQ(subRange.results.front(), 5); - EXPECT_EQ(subRange.results.back(), 6); - EXPECT_EQ(subRange.range, results.range); + _tmpDb = std::make_unique(); + { + db::Session s{ _tmpDb->getDb() }; + s.prepareTables(); + s.analyze(); + } } + void DatabaseFixture::TearDownTestCase() { - auto subRange{ results.getSubRange(Range {1, 0}) }; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 1); - EXPECT_EQ(subRange.results.front(), 6); - const Range expectedRange{ 1, 1 }; - EXPECT_EQ(subRange.range, expectedRange); + _tmpDb.reset(); } - { - auto subRange{ results.getSubRange(Range {3, 2}) }; - EXPECT_FALSE(subRange.moreResults); - ASSERT_EQ(subRange.results.size(), 0); - const Range expectedRange{ 2, 0 }; - EXPECT_EQ(subRange.range, expectedRange); - } -} + void DatabaseFixture::testDatabaseEmpty() + { + using namespace db; + + auto transaction{ session.createWriteTransaction() }; + + EXPECT_EQ(Artist::getCount(session), 0); + EXPECT_EQ(Cluster::getCount(session), 0); + EXPECT_EQ(ClusterType::getCount(session), 0); + EXPECT_EQ(Listen::getCount(session), 0); + EXPECT_EQ(MediaLibrary::getCount(session), 0); + EXPECT_EQ(Release::getCount(session), 0); + EXPECT_EQ(StarredArtist::getCount(session), 0); + EXPECT_EQ(StarredRelease::getCount(session), 0); + EXPECT_EQ(StarredTrack::getCount(session), 0); + EXPECT_EQ(Track::getCount(session), 0); + EXPECT_EQ(TrackBookmark::getCount(session), 0); + EXPECT_EQ(TrackList::getCount(session), 0); + EXPECT_EQ(User::getCount(session), 0); + } + + TEST_F(DatabaseFixture, Common_subRangeEmpty) + { + using namespace db; + + RangeResults results; + results.range = Range{ 0, 0 }; + results.results = {}; + results.moreResults = false; + + { + auto subRange{ results.getSubRange(Range {0, 0}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 0); + EXPECT_EQ(subRange.range, Range{}); + } + { + auto subRange{ results.getSubRange(Range {0, 1}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 0); + } + } + + TEST_F(DatabaseFixture, Common_subRangeForeach) + { + using namespace db; + + struct TestCase + { + Range range; + std::size_t subRangeSize; + std::vector 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 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) + { + using namespace db; + + { + const IdType id{}; + EXPECT_FALSE(id.isValid()); + } + + { + const IdType id{ 0 }; + EXPECT_TRUE(id.isValid()); + } + + { + const IdType id1{ 0 }; + const IdType id2{ 0 }; + EXPECT_EQ(id1, id2); + } + + { + const IdType id1{ 0 }; + const IdType id2{ 1 }; + EXPECT_NE(id1, id2); + EXPECT_LT(id1, id2); + EXPECT_GT(id2, id1); + } + } + + TEST_F(DatabaseFixture, Common_subRange) + { + using namespace db; + + RangeResults results; + results.range = Range{ 0, 2 }; + results.results = { 5, 6 }; + results.moreResults = false; + + { + auto subRange{ results.getSubRange(Range {0, 1}) }; + EXPECT_TRUE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 1); + EXPECT_EQ(subRange.results.front(), 5); + } + { + auto subRange{ results.getSubRange(Range {1, 1}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 1); + EXPECT_EQ(subRange.results.front(), 6); + } + { + auto subRange{ results.getSubRange(Range {0, 2}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 2); + EXPECT_EQ(subRange.results.front(), 5); + EXPECT_EQ(subRange.results.back(), 6); + } + { + auto subRange{ results.getSubRange(Range {}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 2); + EXPECT_EQ(subRange.results.front(), 5); + EXPECT_EQ(subRange.results.back(), 6); + EXPECT_EQ(subRange.range, results.range); + } + + { + auto subRange{ results.getSubRange(Range {1, 0}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 1); + EXPECT_EQ(subRange.results.front(), 6); + const Range expectedRange{ 1, 1 }; + EXPECT_EQ(subRange.range, expectedRange); + } + { + auto subRange{ results.getSubRange(Range {3, 2}) }; + EXPECT_FALSE(subRange.moreResults); + ASSERT_EQ(subRange.results.size(), 0); + const Range expectedRange{ 2, 0 }; + EXPECT_EQ(subRange.range, expectedRange); + } + } +} \ No newline at end of file diff --git a/src/libs/database/test/Common.hpp b/src/libs/database/test/Common.hpp index d7bc32bd..0b9268cd 100644 --- a/src/libs/database/test/Common.hpp +++ b/src/libs/database/test/Common.hpp @@ -40,117 +40,119 @@ #include "database/Types.hpp" #include "database/User.hpp" -template -class [[nodiscard]] ScopedEntity +namespace lms::db::tests { -public: - using IdType = typename T::IdType; - - template - ScopedEntity(Database::Session& session, Args&& ...args) - : _session{ session } + template + class [[nodiscard]] ScopedEntity { - auto transaction{ _session.createWriteTransaction() }; + public: + using IdType = typename T::IdType; - auto entity{ _session.create(std::forward(args)...) }; - EXPECT_TRUE(entity); - _id = entity->getId(); - } + template + ScopedEntity(db::Session& session, Args&& ...args) + : _session{ session } + { + auto transaction{ _session.createWriteTransaction() }; - ~ScopedEntity() + auto entity{ _session.create(std::forward(args)...) }; + EXPECT_TRUE(entity); + _id = entity->getId(); + } + + ~ScopedEntity() + { + auto transaction{ _session.createWriteTransaction() }; + + auto entity{ T::find(_session, _id) }; + // could not be here due to "on delete cascade" constraints... + if (entity) + entity.remove(); + } + + ScopedEntity(const ScopedEntity&) = delete; + ScopedEntity(ScopedEntity&&) = delete; + ScopedEntity& operator=(const ScopedEntity&) = delete; + ScopedEntity& operator=(ScopedEntity&&) = delete; + + typename T::pointer lockAndGet() + { + auto transaction{ _session.createReadTransaction() }; + return get(); + } + + typename T::pointer get() + { + _session.checkReadTransaction(); + + auto entity{ T::find(_session, _id) }; + EXPECT_TRUE(entity); + return entity; + } + + typename T::pointer operator->() + { + return get(); + } + + IdType getId() const { return _id; } + + private: + db::Session& _session; + IdType _id{}; + }; + + using ScopedArtist = ScopedEntity; + using ScopedCluster = ScopedEntity; + using ScopedClusterType = ScopedEntity; + using ScopedMediaLibrary = ScopedEntity; + using ScopedRelease = ScopedEntity; + using ScopedTrack = ScopedEntity; + using ScopedTrackList = ScopedEntity; + using ScopedUser = ScopedEntity; + + class ScopedFileDeleter final { - auto transaction{ _session.createWriteTransaction() }; + public: + ScopedFileDeleter(const std::filesystem::path& path) : _path{ path } {} + ~ScopedFileDeleter() { std::filesystem::remove(_path); } - auto entity{ T::find(_session, _id) }; - // could not be here due to "on delete cascade" constraints... - if (entity) - entity.remove(); - } + private: + ScopedFileDeleter(const ScopedFileDeleter&) = delete; + ScopedFileDeleter(ScopedFileDeleter&&) = delete; + ScopedFileDeleter operator=(const ScopedFileDeleter&) = delete; + ScopedFileDeleter operator=(ScopedFileDeleter&&) = delete; - ScopedEntity(const ScopedEntity&) = delete; - ScopedEntity(ScopedEntity&&) = delete; - ScopedEntity& operator=(const ScopedEntity&) = delete; - ScopedEntity& operator=(ScopedEntity&&) = delete; + const std::filesystem::path _path; + }; - typename T::pointer lockAndGet() + class TmpDatabase final { - auto transaction{ _session.createReadTransaction() }; - return get(); - } + public: + TmpDatabase(); - typename T::pointer get() + db::Db& getDb(); + + private: + const std::filesystem::path _tmpFile; + ScopedFileDeleter _fileDeleter; + db::Db _db; + }; + + class DatabaseFixture : public ::testing::Test { - _session.checkReadTransaction(); + public: + ~DatabaseFixture(); - auto entity{ T::find(_session, _id) }; - EXPECT_TRUE(entity); - return entity; - } + public: + static void SetUpTestCase(); + static void TearDownTestCase(); - typename T::pointer operator->() - { - return get(); - } + private: + void testDatabaseEmpty(); - IdType getId() const { return _id; } - -private: - Database::Session& _session; - IdType _id{}; -}; - -using ScopedArtist = ScopedEntity; -using ScopedCluster = ScopedEntity; -using ScopedClusterType = ScopedEntity; -using ScopedMediaLibrary = ScopedEntity; -using ScopedRelease = ScopedEntity; -using ScopedTrack = ScopedEntity; -using ScopedTrackList = ScopedEntity; -using ScopedUser = ScopedEntity; - -class ScopedFileDeleter final -{ -public: - ScopedFileDeleter(const std::filesystem::path& path) : _path{ path } {} - ~ScopedFileDeleter() { std::filesystem::remove(_path); } - -private: - ScopedFileDeleter(const ScopedFileDeleter&) = delete; - ScopedFileDeleter(ScopedFileDeleter&&) = delete; - ScopedFileDeleter operator=(const ScopedFileDeleter&) = delete; - ScopedFileDeleter operator=(ScopedFileDeleter&&) = delete; - - const std::filesystem::path _path; -}; - -class TmpDatabase final -{ -public: - TmpDatabase(); - - Database::Db& getDb(); - -private: - const std::filesystem::path _tmpFile; - ScopedFileDeleter _fileDeleter; - Database::Db _db; -}; - -class DatabaseFixture : public ::testing::Test -{ -public: - ~DatabaseFixture(); - -public: - static void SetUpTestCase(); - static void TearDownTestCase(); - -private: - void testDatabaseEmpty(); - - static inline std::unique_ptr _tmpDb{}; - -public: - Database::Session session{ _tmpDb->getDb() }; -}; + static inline std::unique_ptr _tmpDb{}; + public: + db::Session session{ _tmpDb->getDb() }; + }; +} \ No newline at end of file diff --git a/src/libs/database/test/DatabaseTest.cpp b/src/libs/database/test/DatabaseTest.cpp index 8894c112..b46079b2 100644 --- a/src/libs/database/test/DatabaseTest.cpp +++ b/src/libs/database/test/DatabaseTest.cpp @@ -21,84 +21,85 @@ #include "Common.hpp" -using namespace Database; - -TEST_F(DatabaseFixture, MultiTracksSingleArtistSingleRelease) +namespace lms::db::tests { - constexpr std::size_t nbTracks{ 10 }; - std::list tracks; - ScopedArtist artist{ session, "MyArtst" }; - ScopedRelease release{ session, "MyRelease" }; - - for (std::size_t i{}; i < nbTracks; ++i) + TEST_F(DatabaseFixture, MultiTracksSingleArtistSingleRelease) { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); + constexpr std::size_t nbTracks{ 10 }; + std::list tracks; + ScopedArtist artist{ session, "MyArtst" }; + ScopedRelease release{ session, "MyRelease" }; - auto transaction{ session.createWriteTransaction() }; + for (std::size_t i{}; i < nbTracks; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist); - tracks.back().get().modify()->setRelease(release.get()); + auto transaction{ session.createWriteTransaction() }; + + TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist); + tracks.back().get().modify()->setRelease(release.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); + EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + + const auto releaseTracks{ Track::find(session, Track::FindParameters {}.setRelease(release.getId())) }; + EXPECT_EQ(releaseTracks.results.size(), nbTracks); + } } + TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtist) { - auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(Release::findOrphanIds(session).results.empty()); - EXPECT_TRUE(Artist::findOrphanIds(session).results.empty()); + ScopedTrack track{ session, "MyTrack" }; + ScopedRelease release{ session, "MyRelease" }; + ScopedArtist artist{ session, "MyArtist" }; + + { + auto transaction{ session.createWriteTransaction() }; + + auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) }; + track.get().modify()->setRelease(release.get()); + } + + { + auto transaction{ session.createWriteTransaction() }; + + auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + + auto artists{ release->getArtists() }; + ASSERT_EQ(artists.size(), 1); + ASSERT_EQ(artists.front()->getId(), artist.getId()); + } } + TEST_F(DatabaseFixture, SingleUser) { - auto transaction{ session.createReadTransaction() }; + { + auto transaction{ session.createReadTransaction() }; + EXPECT_TRUE(User::find(session, User::FindParameters{}).results.empty()); + EXPECT_EQ(User::getCount(session), 0); + } - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); + ScopedUser user{ session, "MyUser" }; - const auto releaseTracks{ Track::find(session, Track::FindParameters {}.setRelease(release.getId())) }; - EXPECT_EQ(releaseTracks.results.size(), nbTracks); - } -} + { + auto transaction{ session.createReadTransaction() }; -TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtist) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedRelease release{ session, "MyRelease" }; - ScopedArtist artist{ session, "MyArtist" }; - - { - auto transaction{ session.createWriteTransaction() }; - - auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) }; - track.get().modify()->setRelease(release.get()); - } - - { - auto transaction{ session.createWriteTransaction() }; - - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - - auto artists{ release->getArtists() }; - ASSERT_EQ(artists.size(), 1); - ASSERT_EQ(artists.front()->getId(), artist.getId()); - } -} - -TEST_F(DatabaseFixture, SingleUser) -{ - { - auto transaction{ session.createReadTransaction() }; - EXPECT_TRUE(User::find(session, User::FindParameters{}).results.empty()); - EXPECT_EQ(User::getCount(session), 0); - } - - ScopedUser user{ session, "MyUser" }; - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_EQ(User::find(session, User::FindParameters{}).results.size(), 1); - EXPECT_EQ(User::getCount(session), 1); + EXPECT_EQ(User::find(session, User::FindParameters{}).results.size(), 1); + EXPECT_EQ(User::getCount(session), 1); + } } } @@ -106,4 +107,4 @@ int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); -} +} \ No newline at end of file diff --git a/src/libs/database/test/Listen.cpp b/src/libs/database/test/Listen.cpp index 1615b908..b7461df6 100644 --- a/src/libs/database/test/Listen.cpp +++ b/src/libs/database/test/Listen.cpp @@ -20,194 +20,135 @@ #include "Common.hpp" #include "database/Listen.hpp" -using namespace Database; - -using ScopedListen = ScopedEntity; - -TEST_F(DatabaseFixture, Listen_getAll) +namespace lms::db::tests { - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; + using ScopedListen = ScopedEntity; + TEST_F(DatabaseFixture, Listen_getAll) { - auto transaction{ session.createReadTransaction() }; - - EXPECT_EQ(Listen::getCount(session), 0); - } - - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_EQ(Listen::getCount(session), 1); - } - - { - auto transaction{ session.createWriteTransaction() }; - ScopedListen listen2{ session, user.get(), track.get(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{13, 0, 1}} }; - - EXPECT_EQ(Listen::getCount(session), 2); - } - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_EQ(Listen::getCount(session), 1); - } -} - -TEST_F(DatabaseFixture, Listen_get) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; - - { - auto transaction{ session.createReadTransaction() }; - - auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::ListenBrainz)) }; - EXPECT_EQ(listens.results.size(), 0); - } - - { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; { - auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::Internal)) }; - EXPECT_EQ(listens.moreResults, false); - ASSERT_EQ(listens.results.size(), 1); - EXPECT_EQ(listens.results.front(), listen->getId()); + auto transaction{ session.createReadTransaction() }; + + EXPECT_EQ(Listen::getCount(session), 0); + } + + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; + + { + auto transaction{ session.createReadTransaction() }; + + EXPECT_EQ(Listen::getCount(session), 1); } { - auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::Internal).setSyncState(SyncState::PendingAdd)) }; - EXPECT_EQ(listens.results.size(), 1); + auto transaction{ session.createWriteTransaction() }; + ScopedListen listen2{ session, user.get(), track.get(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{13, 0, 1}} }; + + EXPECT_EQ(Listen::getCount(session), 2); } + { - auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::Internal).setSyncState(SyncState::Synchronized)) }; + auto transaction{ session.createReadTransaction() }; + + EXPECT_EQ(Listen::getCount(session), 1); + } + } + + TEST_F(DatabaseFixture, Listen_get) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; + + { + auto transaction{ session.createReadTransaction() }; + + auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::ListenBrainz)) }; EXPECT_EQ(listens.results.size(), 0); } - } -} - -TEST_F(DatabaseFixture, Listen_get_multi) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 3}} }; - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; - ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 2}} }; - - { - auto transaction{ session.createReadTransaction() }; - - auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::Internal)) }; - ASSERT_EQ(listens.results.size(), 3); - EXPECT_EQ(listens.results[0], listen1.getId()); - EXPECT_EQ(listens.results[1], listen2.getId()); - EXPECT_EQ(listens.results[2], listen3.getId()); - } -} - -TEST_F(DatabaseFixture, Listen_get_byDateTime) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime1{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - const Wt::WDateTime dateTime2{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 2} }; - ASSERT_GT(dateTime2, dateTime1); - - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; - ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 }; - - { - auto transaction{ session.createReadTransaction() }; { - Listen::pointer listen{ Listen::find(session, user.getId(), track.getId(), ScrobblingBackend::Internal, dateTime1) }; - ASSERT_TRUE(listen); - EXPECT_EQ(listen->getId(), listen1.getId()); + auto transaction{ session.createReadTransaction() }; + + { + auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::Internal)) }; + EXPECT_EQ(listens.moreResults, false); + ASSERT_EQ(listens.results.size(), 1); + EXPECT_EQ(listens.results.front(), listen->getId()); + } + + { + auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::Internal).setSyncState(SyncState::PendingAdd)) }; + EXPECT_EQ(listens.results.size(), 1); + } + { + auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::Internal).setSyncState(SyncState::Synchronized)) }; + EXPECT_EQ(listens.results.size(), 0); + } } + } + + TEST_F(DatabaseFixture, Listen_get_multi) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 3}} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; + ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 2}} }; { - Listen::pointer listen{ Listen::find(session, user.getId(), track.getId(), ScrobblingBackend::Internal, dateTime2) }; - ASSERT_TRUE(listen); - EXPECT_EQ(listen->getId(), listen2.getId()); + auto transaction{ session.createReadTransaction() }; + + auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::Internal)) }; + ASSERT_EQ(listens.results.size(), 3); + EXPECT_EQ(listens.results[0], listen1.getId()); + EXPECT_EQ(listens.results[1], listen2.getId()); + EXPECT_EQ(listens.results[2], listen3.getId()); } + } + + TEST_F(DatabaseFixture, Listen_get_byDateTime) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime1{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime2{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 2} }; + ASSERT_GT(dateTime2, dateTime1); + + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; + ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 }; { - Listen::pointer listen{ Listen::find(session, user.getId(), track.getId(), ScrobblingBackend::Internal, dateTime2.addSecs(56)) }; - EXPECT_FALSE(listen); + auto transaction{ session.createReadTransaction() }; + + { + Listen::pointer listen{ Listen::find(session, user.getId(), track.getId(), ScrobblingBackend::Internal, dateTime1) }; + ASSERT_TRUE(listen); + EXPECT_EQ(listen->getId(), listen1.getId()); + } + + { + Listen::pointer listen{ Listen::find(session, user.getId(), track.getId(), ScrobblingBackend::Internal, dateTime2) }; + ASSERT_TRUE(listen); + EXPECT_EQ(listen->getId(), listen2.getId()); + } + + { + Listen::pointer listen{ Listen::find(session, user.getId(), track.getId(), ScrobblingBackend::Internal, dateTime2.addSecs(56)) }; + EXPECT_FALSE(listen); + } } } -} - -TEST_F(DatabaseFixture, Listen_getTopArtists) -{ - ScopedTrack track1{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime1{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; + TEST_F(DatabaseFixture, Listen_getTopArtists) { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - const auto artists{ Listen::getTopArtists(session, params) }; - EXPECT_EQ(artists.results.size(), 0); - EXPECT_EQ(artists.moreResults, false); - } - - ScopedTrack track2{ session, "MyTrack2" }; - ScopedArtist artist1{ session, "MyArtist1" }; - ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime1.addSecs(1) }; - - { - auto transaction{ session.createWriteTransaction() }; - - TrackArtistLink::create(session, track2.get(), artist1.get(), TrackArtistLinkType::Artist); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto artists{ Listen::getTopArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist1->getId()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); - - auto artists{ Listen::getTopArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 0); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); - params.setLinkType(TrackArtistLinkType::Producer); - - auto artists{ Listen::getTopArtists(session, params) }; - EXPECT_EQ(artists.results.size(), 0); - } - - { - ScopedClusterType clusterType{ session, "MyType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + ScopedTrack track1{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime1{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; { auto transaction{ session.createReadTransaction() }; @@ -215,710 +156,718 @@ TEST_F(DatabaseFixture, Listen_getTopArtists) Listen::ArtistStatsFindParameters params; params.setUser(user->getId()); params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({cluster->getId()}); + + const auto artists{ Listen::getTopArtists(session, params) }; + EXPECT_EQ(artists.results.size(), 0); + EXPECT_EQ(artists.moreResults, false); + } + + ScopedTrack track2{ session, "MyTrack2" }; + ScopedArtist artist1{ session, "MyArtist1" }; + ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime1.addSecs(1) }; + + { + auto transaction{ session.createWriteTransaction() }; + + TrackArtistLink::create(session, track2.get(), artist1.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto artists{ Listen::getTopArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist1->getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); + + auto artists{ Listen::getTopArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 0); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); + params.setLinkType(TrackArtistLinkType::Producer); + + auto artists{ Listen::getTopArtists(session, params) }; + EXPECT_EQ(artists.results.size(), 0); + } + + { + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster->getId() }); + + auto artists{ Listen::getTopArtists(session, params) }; + EXPECT_EQ(artists.results.size(), 0); + } + } + } + + TEST_F(DatabaseFixture, Listen_getTopArtists_multi) + { + ScopedUser user{ session, "MyUser" }; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedArtist artist1{ session, "MyArtist1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedArtist artist2{ session, "MyArtist2" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + + { + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto artists{ Listen::getTopArtists(session, params) }; + EXPECT_EQ(artists.results.size(), 0); + } + + ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto artists{ Listen::getTopArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist1->getId()); + } + + ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(2) }; + ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(3) }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto artists{ Listen::getTopArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 2); + EXPECT_EQ(artists.results[0], artist2->getId()); + EXPECT_EQ(artists.results[1], artist1->getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setRange(Range{ 0, 1 }); + + auto artists{ Listen::getTopArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.moreResults, true); + EXPECT_EQ(artists.results[0], artist2->getId()); + } + } + + TEST_F(DatabaseFixture, Listen_getTopArtists_cluster) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedArtist artist{ session, "MyArtist" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + + { + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); + + auto artists{ Listen::getTopArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 0); + } + { + auto transaction{ session.createWriteTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); + + auto artists{ Listen::getTopArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist.getId()); + } + } + + TEST_F(DatabaseFixture, Listen_getTopArtists_mediaLibrary) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedArtist artist{ session, "MyArtist" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime1{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; + ScopedMediaLibrary library{ session }; + ScopedMediaLibrary otherLibrary{ session }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user.getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setMediaLibrary(library.getId()); + + const auto artists{ Listen::getTopArtists(session, params) }; + EXPECT_EQ(artists.results.size(), 0); + EXPECT_EQ(artists.moreResults, false); + } + + { + auto transaction{ session.createWriteTransaction() }; + + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + track.get().modify()->setMediaLibrary(library.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user.getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto artists{ Listen::getTopArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist->getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user.getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setMediaLibrary(library.getId()); + + auto artists{ Listen::getTopArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist->getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user.getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setMediaLibrary(otherLibrary.getId()); auto artists{ Listen::getTopArtists(session, params) }; EXPECT_EQ(artists.results.size(), 0); } } -} - -TEST_F(DatabaseFixture, Listen_getTopArtists_multi) -{ - ScopedUser user{ session, "MyUser" }; - ScopedTrack track1{ session, "MyTrack1" }; - ScopedArtist artist1{ session, "MyArtist1" }; - ScopedTrack track2{ session, "MyTrack2" }; - ScopedArtist artist2{ session, "MyArtist2" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + TEST_F(DatabaseFixture, Listen_getTopReleases) { - auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedRelease release{ session, "MyRelease" }; + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setRelease(release.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto releases{ Listen::getTopReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + EXPECT_EQ(releases.results.size(), 0); + } + + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto releases{ Listen::getTopReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); + + auto releases{ Listen::getTopReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + EXPECT_EQ(releases.results.size(), 0); + } } + TEST_F(DatabaseFixture, Listen_getTopReleases_multi) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track1{ session, "MyTrack" }; + ScopedTrack track2{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + ScopedRelease release1{ session, "MyRelease1" }; + ScopedRelease release2{ session, "MyRelease2" }; - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); + { + auto transaction{ session.createWriteTransaction() }; + track1.get().modify()->setRelease(release1.get()); + track2.get().modify()->setRelease(release2.get()); + } - auto artists{ Listen::getTopArtists(session, params) }; - EXPECT_EQ(artists.results.size(), 0); + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto releases{ Listen::getTopReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release1.getId()); + } + ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto releases{ Listen::getTopReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release2.getId()); + EXPECT_EQ(releases.results[1], release1.getId()); + } + ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + ScopedListen listen5{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto releases{ Listen::getTopReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release1.getId()); + EXPECT_EQ(releases.results[1], release2.getId()); + } } - ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto artists{ Listen::getTopArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist1->getId()); - } - - ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(2) }; - ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(3) }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto artists{ Listen::getTopArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 2); - EXPECT_EQ(artists.results[0], artist2->getId()); - EXPECT_EQ(artists.results[1], artist1->getId()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setRange(Range {0, 1}); - - auto artists{ Listen::getTopArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.moreResults, true); - EXPECT_EQ(artists.results[0], artist2->getId()); - } -} - -TEST_F(DatabaseFixture, Listen_getTopArtists_cluster) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedArtist artist{ session, "MyArtist" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - ScopedClusterType clusterType{ session, "MyType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - - { - auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); - - auto artists{ Listen::getTopArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 0); - } - { - auto transaction{ session.createWriteTransaction() }; - cluster.get().modify()->addTrack(track.get()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); - - auto artists{ Listen::getTopArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist.getId()); - } -} - -TEST_F(DatabaseFixture, Listen_getTopArtists_mediaLibrary) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedArtist artist{ session, "MyArtist" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime1{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; - ScopedMediaLibrary library{ session }; - ScopedMediaLibrary otherLibrary{ session }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user.getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setMediaLibrary(library.getId()); - - const auto artists{ Listen::getTopArtists(session, params) }; - EXPECT_EQ(artists.results.size(), 0); - EXPECT_EQ(artists.moreResults, false); - } - - { - auto transaction{ session.createWriteTransaction() }; - - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - track.get().modify()->setMediaLibrary(library.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user.getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto artists{ Listen::getTopArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist->getId()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user.getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setMediaLibrary(library.getId()); - - auto artists{ Listen::getTopArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist->getId()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user.getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setMediaLibrary(otherLibrary.getId()); - - auto artists{ Listen::getTopArtists(session, params) }; - EXPECT_EQ(artists.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Listen_getTopReleases) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedRelease release{ session, "MyRelease" }; - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setRelease(release.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto releases{ Listen::getTopReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - EXPECT_EQ(releases.results.size(), 0); - } - - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto releases{ Listen::getTopReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release.getId()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); - - auto releases{ Listen::getTopReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - EXPECT_EQ(releases.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Listen_getTopReleases_multi) -{ - ScopedTrack track1{ session, "MyTrack" }; - ScopedTrack track2{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - ScopedRelease release1{ session, "MyRelease1" }; - ScopedRelease release2{ session, "MyRelease2" }; - - { - auto transaction{ session.createWriteTransaction() }; - track1.get().modify()->setRelease(release1.get()); - track2.get().modify()->setRelease(release2.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto releases{ Listen::getTopReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release1.getId()); - } - ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto releases{ Listen::getTopReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release2.getId()); - EXPECT_EQ(releases.results[1], release1.getId()); - } - ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - ScopedListen listen5{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto releases{ Listen::getTopReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release1.getId()); - EXPECT_EQ(releases.results[1], release2.getId()); - } -} - -TEST_F(DatabaseFixture, Listen_getTopReleases_cluster) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - ScopedClusterType clusterType{ session, "MyType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - ScopedRelease release{ session, "MyRelease" }; - - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setRelease(release.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); - - auto releases{ Listen::getTopReleases(session, params) }; - EXPECT_EQ(releases.results.size(), 0); - } - { - auto transaction{ session.createWriteTransaction() }; - cluster.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); - - auto releases{ Listen::getTopReleases(session, params) }; - EXPECT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release.getId()); - } -} - -TEST_F(DatabaseFixture, Listen_getTopReleases_mediaLibrary) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedRelease release{ session, "MyRelease" }; - ScopedMediaLibrary library{ session }; - ScopedMediaLibrary otherLibrary{ session }; - - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setRelease(release.get()); - track.get().modify()->setMediaLibrary(library.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setMediaLibrary(library.getId()); - - auto releases{ Listen::getTopReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - EXPECT_EQ(releases.results.size(), 0); - } - - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setMediaLibrary(library.getId()); - - auto releases{ Listen::getTopReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release.getId()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setMediaLibrary(otherLibrary.getId()); - - auto releases{ Listen::getTopReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - EXPECT_EQ(releases.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Listen_getTopTracks) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 0); - } - - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track.getId()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - EXPECT_EQ(tracks.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Listen_getTopTracks_artist) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedArtist artist{ session, "MyArtist" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setArtist(artist->getId()); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 0); - } - - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setArtist(artist->getId()); - - auto tracks{ Listen::getTopTracks(session, params) }; - ASSERT_EQ(tracks.results.size(), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setArtist(artist->getId()); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track.getId()); - } -} - -TEST_F(DatabaseFixture, Listen_getTopTrack_multi) -{ - ScopedTrack track1{ session, "MyTrack1" }; - ScopedTrack track2{ session, "MyTrack2" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track1.getId()); - } - ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track2.getId()); - EXPECT_EQ(tracks.results[1], track1.getId()); - } - ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - ScopedListen listen5{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track1.getId()); - EXPECT_EQ(tracks.results[1], track2.getId()); - } -} - -TEST_F(DatabaseFixture, Listen_getTopTracks_cluster) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - ScopedClusterType clusterType{ session, "MyType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.results.size(), 0); - } - { - auto transaction{ session.createWriteTransaction() }; - cluster.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track.getId()); - } -} - -TEST_F(DatabaseFixture, Listen_getTopTracks_mediaLibrary) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedMediaLibrary library{ session }; - ScopedMediaLibrary otherLibrary{ session }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user.getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setMediaLibrary(library.getId()); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setMediaLibrary(library.get()); - } - - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setMediaLibrary(library.getId()); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track.getId()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setMediaLibrary(otherLibrary.getId()); - - auto tracks{ Listen::getTopTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - EXPECT_EQ(tracks.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Listen_getRecentArtists) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedArtist artist{ session, "MyArtist" }; - - { - auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto artists{ Listen::getRecentArtists(session, params) }; - EXPECT_EQ(artists.results.size(), 0); - EXPECT_EQ(artists.moreResults, false); - } - - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto artists{ Listen::getRecentArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist->getId()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); - - auto artists{ Listen::getRecentArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 0); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setLinkType(TrackArtistLinkType::Producer); - - auto artists{ Listen::getRecentArtists(session, params) }; - EXPECT_EQ(artists.results.size(), 0); - } + TEST_F(DatabaseFixture, Listen_getTopReleases_cluster) { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; ScopedClusterType clusterType{ session, "MyType" }; ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + ScopedRelease release{ session, "MyRelease" }; + + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setRelease(release.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); + + auto releases{ Listen::getTopReleases(session, params) }; + EXPECT_EQ(releases.results.size(), 0); + } + { + auto transaction{ session.createWriteTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); + + auto releases{ Listen::getTopReleases(session, params) }; + EXPECT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release.getId()); + } + } + + TEST_F(DatabaseFixture, Listen_getTopReleases_mediaLibrary) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedRelease release{ session, "MyRelease" }; + ScopedMediaLibrary library{ session }; + ScopedMediaLibrary otherLibrary{ session }; + + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setRelease(release.get()); + track.get().modify()->setMediaLibrary(library.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setMediaLibrary(library.getId()); + + auto releases{ Listen::getTopReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + EXPECT_EQ(releases.results.size(), 0); + } + + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setMediaLibrary(library.getId()); + + auto releases{ Listen::getTopReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setMediaLibrary(otherLibrary.getId()); + + auto releases{ Listen::getTopReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + EXPECT_EQ(releases.results.size(), 0); + } + } + + TEST_F(DatabaseFixture, Listen_getTopTracks) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 0); + } + + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + EXPECT_EQ(tracks.results.size(), 0); + } + } + + TEST_F(DatabaseFixture, Listen_getTopTracks_artist) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedArtist artist{ session, "MyArtist" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setArtist(artist->getId()); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 0); + } + + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setArtist(artist->getId()); + + auto tracks{ Listen::getTopTracks(session, params) }; + ASSERT_EQ(tracks.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setArtist(artist->getId()); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track.getId()); + } + } + + TEST_F(DatabaseFixture, Listen_getTopTrack_multi) + { + ScopedTrack track1{ session, "MyTrack1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track1.getId()); + } + ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track2.getId()); + EXPECT_EQ(tracks.results[1], track1.getId()); + } + ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + ScopedListen listen5{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track1.getId()); + EXPECT_EQ(tracks.results[1], track2.getId()); + } + } + + TEST_F(DatabaseFixture, Listen_getTopTracks_cluster) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.results.size(), 0); + } + { + auto transaction{ session.createWriteTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track.getId()); + } + } + + TEST_F(DatabaseFixture, Listen_getTopTracks_mediaLibrary) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedMediaLibrary library{ session }; + ScopedMediaLibrary otherLibrary{ session }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user.getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setMediaLibrary(library.getId()); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setMediaLibrary(library.get()); + } + + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setMediaLibrary(library.getId()); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setMediaLibrary(otherLibrary.getId()); + + auto tracks{ Listen::getTopTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + EXPECT_EQ(tracks.results.size(), 0); + } + } + + TEST_F(DatabaseFixture, Listen_getRecentArtists) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedArtist artist{ session, "MyArtist" }; + + { + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } { auto transaction{ session.createReadTransaction() }; @@ -926,758 +875,809 @@ TEST_F(DatabaseFixture, Listen_getRecentArtists) Listen::ArtistStatsFindParameters params; params.setUser(user->getId()); params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({cluster->getId()}); + + auto artists{ Listen::getRecentArtists(session, params) }; + EXPECT_EQ(artists.results.size(), 0); + EXPECT_EQ(artists.moreResults, false); + } + + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto artists{ Listen::getRecentArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist->getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); + + auto artists{ Listen::getRecentArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 0); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setLinkType(TrackArtistLinkType::Producer); + + auto artists{ Listen::getRecentArtists(session, params) }; + EXPECT_EQ(artists.results.size(), 0); + } + { + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster->getId() }); + + auto artists{ Listen::getRecentArtists(session, params) }; + EXPECT_EQ(artists.results.size(), 0); + } + } + } + + TEST_F(DatabaseFixture, Listen_getRecentArtists_multi) + { + ScopedUser user{ session, "MyUser" }; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedArtist artist1{ session, "MyArtist1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedArtist artist2{ session, "MyArtist2" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + + { + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto artists{ Listen::getRecentArtists(session, params) }; + EXPECT_EQ(artists.results.size(), 0); + } + + ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto artists{ Listen::getRecentArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist1->getId()); + } + ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(2) }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto artists{ Listen::getRecentArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 2); + EXPECT_EQ(artists.results[0], artist2->getId()); + EXPECT_EQ(artists.results[1], artist1->getId()); + } + ScopedListen listen3{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(-1) }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setRange(Range{ 0, 1 }); + + auto artists{ Listen::getRecentArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.moreResults, true); + EXPECT_EQ(artists.results[0], artist2->getId()); + } + } + + TEST_F(DatabaseFixture, Listen_getRecentArtists_cluster) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedArtist artist{ session, "MyArtist" }; + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + + { + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); + + auto artists{ Listen::getRecentArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 0); + } + { + auto transaction{ session.createWriteTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); + + auto artists{ Listen::getRecentArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist.getId()); + } + } + + + TEST_F(DatabaseFixture, Listen_getRecentArtists_mediaLibrary) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedArtist artist{ session, "MyArtist" }; + ScopedMediaLibrary library{ session }; + ScopedMediaLibrary otherLibrary{ session }; + + { + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + track.get().modify()->setMediaLibrary(library.get()); + } + + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setMediaLibrary(library.getId()); + + auto artists{ Listen::getRecentArtists(session, params) }; + ASSERT_EQ(artists.results.size(), 1); + EXPECT_EQ(artists.results[0], artist->getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::ArtistStatsFindParameters params; + params.setMediaLibrary(otherLibrary.getId()); auto artists{ Listen::getRecentArtists(session, params) }; EXPECT_EQ(artists.results.size(), 0); } } -} - -TEST_F(DatabaseFixture, Listen_getRecentArtists_multi) -{ - ScopedUser user{ session, "MyUser" }; - ScopedTrack track1{ session, "MyTrack1" }; - ScopedArtist artist1{ session, "MyArtist1" }; - ScopedTrack track2{ session, "MyTrack2" }; - ScopedArtist artist2{ session, "MyArtist2" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + TEST_F(DatabaseFixture, Listen_getRecentReleases) { - auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist); - TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist); + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedRelease release{ session, "MyRelease" }; + + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setRelease(release.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 0); + } + + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); + + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + EXPECT_EQ(releases.results.size(), 0); + } } + TEST_F(DatabaseFixture, Listen_getMostRecentRelease) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedRelease release{ session, "MyRelease" }; - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setRelease(release.get()); + } - auto artists{ Listen::getRecentArtists(session, params) }; - EXPECT_EQ(artists.results.size(), 0); + { + auto transaction{ session.createReadTransaction() }; + + const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) }; + EXPECT_FALSE(listen); + } + + const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; + + { + auto transaction{ session.createReadTransaction() }; + + const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime1); + } + + const Wt::WDateTime dateTime2{ Wt::WDate {1999, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 }; + + { + auto transaction{ session.createReadTransaction() }; + + const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime1); + } + + const Wt::WDateTime dateTime3{ Wt::WDate {2001, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime3 }; + + { + auto transaction{ session.createReadTransaction() }; + + const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime3); + } } - ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) }; + TEST_F(DatabaseFixture, Listen_getRecentReleases_multi) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedUser user{ session, "MyUser" }; + ScopedRelease release1{ session, "MyRelease1" }; + ScopedRelease release2{ session, "MyRelease2" }; - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); + { + auto transaction{ session.createWriteTransaction() }; + track1.get().modify()->setRelease(release1.get()); + track2.get().modify()->setRelease(release2.get()); + } - auto artists{ Listen::getRecentArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist1->getId()); - } - ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(2) }; - { - auto transaction{ session.createReadTransaction() }; + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); + { + auto transaction{ session.createReadTransaction() }; - auto artists{ Listen::getRecentArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 2); - EXPECT_EQ(artists.results[0], artist2->getId()); - EXPECT_EQ(artists.results[1], artist1->getId()); - } - ScopedListen listen3{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(-1) }; - { - auto transaction{ session.createReadTransaction() }; + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setRange(Range{ 0, 1 }); + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release2.getId()); + } - auto artists{ Listen::getRecentArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.moreResults, true); - EXPECT_EQ(artists.results[0], artist2->getId()); - } -} + ScopedListen listen2{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) }; + { + auto transaction{ session.createReadTransaction() }; -TEST_F(DatabaseFixture, Listen_getRecentArtists_cluster) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedArtist artist{ session, "MyArtist" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - ScopedClusterType clusterType{ session, "MyType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); - { - auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release1.getId()); + EXPECT_EQ(releases.results[1], release2.getId()); + } + + ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(2) }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release2.getId()); + EXPECT_EQ(releases.results[1], release1.getId()); + } + + ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(-1) }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release2.getId()); + EXPECT_EQ(releases.results[1], release1.getId()); + } } + TEST_F(DatabaseFixture, Listen_getRecentReleases_cluster) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + ScopedRelease release{ session, "MyRelease" }; - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setRelease(release.get()); + } + { + auto transaction{ session.createReadTransaction() }; - auto artists{ Listen::getRecentArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 0); - } - { - auto transaction{ session.createWriteTransaction() }; - cluster.get().modify()->addTrack(track.get()); - } - { - auto transaction{ session.createReadTransaction() }; + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); - Listen::ArtistStatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.results.size(), 0); + } - auto artists{ Listen::getRecentArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist.getId()); - } -} + const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + { + auto transaction{ session.createReadTransaction() }; -TEST_F(DatabaseFixture, Listen_getRecentArtists_mediaLibrary) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedArtist artist{ session, "MyArtist" }; - ScopedMediaLibrary library{ session }; - ScopedMediaLibrary otherLibrary{ session }; + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); - { - auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - track.get().modify()->setMediaLibrary(library.get()); + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); + + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release.getId()); + } } - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - + TEST_F(DatabaseFixture, Listen_getRecentReleases_mediaLibrary) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedRelease release{ session, "MyRelease" }; + ScopedMediaLibrary library{ session }; + ScopedMediaLibrary otherLibrary{ session }; - Listen::ArtistStatsFindParameters params; - params.setMediaLibrary(library.getId()); + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setRelease(release.get()); + track.get().modify()->setMediaLibrary(library.get()); + } - auto artists{ Listen::getRecentArtists(session, params) }; - ASSERT_EQ(artists.results.size(), 1); - EXPECT_EQ(artists.results[0], artist->getId()); - } - { - auto transaction{ session.createReadTransaction() }; + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - Listen::ArtistStatsFindParameters params; - params.setMediaLibrary(otherLibrary.getId()); + { + auto transaction{ session.createReadTransaction() }; - auto artists{ Listen::getRecentArtists(session, params) }; - EXPECT_EQ(artists.results.size(), 0); - } -} + Listen::StatsFindParameters params; + params.setMediaLibrary(library.getId()); -TEST_F(DatabaseFixture, Listen_getRecentReleases) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedRelease release{ session, "MyRelease" }; + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release.getId()); + } + { + auto transaction{ session.createReadTransaction() }; - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setRelease(release.get()); + Listen::StatsFindParameters params; + params.setMediaLibrary(otherLibrary.getId()); + + auto releases{ Listen::getRecentReleases(session, params) }; + EXPECT_EQ(releases.moreResults, false); + EXPECT_EQ(releases.results.size(), 0); + } } + TEST_F(DatabaseFixture, Listen_getRecentTracks) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); + { + auto transaction{ session.createReadTransaction() }; - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 0); + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto tracks{ Listen::getRecentTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 0); + } + + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto tracks{ Listen::getRecentTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track.getId()); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); + + auto tracks{ Listen::getRecentTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + EXPECT_EQ(tracks.results.size(), 0); + } } - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + TEST_F(DatabaseFixture, Listen_getRecentTracks_mediaLibrary) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedMediaLibrary library{ session }; + ScopedMediaLibrary otherLibrary{ session }; - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release.getId()); - } - { - auto transaction{ session.createReadTransaction() }; + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setMediaLibrary(library.get()); + } - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); + { + auto transaction{ session.createReadTransaction() }; - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - EXPECT_EQ(releases.results.size(), 0); - } -} + Listen::StatsFindParameters params; + params.setMediaLibrary(library.getId()); -TEST_F(DatabaseFixture, Listen_getMostRecentRelease) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedRelease release{ session, "MyRelease" }; + auto tracks{ Listen::getRecentTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track.getId()); + } - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setRelease(release.get()); + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setMediaLibrary(otherLibrary.getId()); + + auto tracks{ Listen::getRecentTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + EXPECT_EQ(tracks.results.size(), 0); + } } + TEST_F(DatabaseFixture, Listen_getCount_track) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; - const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) }; - EXPECT_FALSE(listen); + { + auto transaction{ session.createReadTransaction() }; + + const std::size_t count{ Listen::getCount(session, user->getId(), track.getId()) }; + EXPECT_EQ(count, 0); + } + + const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; + + { + auto transaction{ session.createReadTransaction() }; + + const std::size_t count{ Listen::getCount(session, user->getId(), track.getId()) }; + EXPECT_EQ(count, 1); + } + + { + auto transaction{ session.createWriteTransaction() }; + user.get().modify()->setScrobblingBackend(ScrobblingBackend::ListenBrainz); + } + + { + auto transaction{ session.createReadTransaction() }; + const std::size_t count{ Listen::getCount(session, user->getId(), track.getId()) }; + EXPECT_EQ(count, 0); + } } - const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; - + TEST_F(DatabaseFixture, Listen_getCount_release) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track1{ session, "MyTrack" }; + ScopedTrack track2{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedRelease release{ session, "MyRelease" }; - const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) }; - EXPECT_TRUE(listen); - EXPECT_EQ(listen->getDateTime(), dateTime1); + auto getReleaseListenCount{ [&] + { + auto transaction{ session.createReadTransaction() }; + return Listen::getCount(session, user->getId(), release.getId()); + } }; + + EXPECT_EQ(getReleaseListenCount(), 0); + + const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; + + EXPECT_EQ(getReleaseListenCount(), 0); + + { + auto transaction{ session.createWriteTransaction() }; + track1.get().modify()->setRelease(release.get()); + } + + EXPECT_EQ(getReleaseListenCount(), 1); + + { + auto transaction{ session.createWriteTransaction() }; + track2.get().modify()->setRelease(release.get()); + } + + EXPECT_EQ(getReleaseListenCount(), 0); + ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; + EXPECT_EQ(getReleaseListenCount(), 1); + ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; + EXPECT_EQ(getReleaseListenCount(), 1); + + ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; + EXPECT_EQ(getReleaseListenCount(), 2); + + { + auto transaction{ session.createWriteTransaction() }; + user.get().modify()->setScrobblingBackend(ScrobblingBackend::ListenBrainz); + } + EXPECT_EQ(getReleaseListenCount(), 0); } - const Wt::WDateTime dateTime2{ Wt::WDate {1999, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 }; - + TEST_F(DatabaseFixture, Listen_getMostRecentTrack) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; - const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) }; - EXPECT_TRUE(listen); - EXPECT_EQ(listen->getDateTime(), dateTime1); + { + auto transaction{ session.createReadTransaction() }; + + auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; + EXPECT_FALSE(listen); + } + + const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; + + { + auto transaction{ session.createReadTransaction() }; + + auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime1); + } + + const Wt::WDateTime dateTime2{ Wt::WDate {1999, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 }; + + { + auto transaction{ session.createReadTransaction() }; + + auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime1); + } + + const Wt::WDateTime dateTime3{ Wt::WDate {2001, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime3 }; + + { + auto transaction{ session.createReadTransaction() }; + + auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; + EXPECT_TRUE(listen); + EXPECT_EQ(listen->getDateTime(), dateTime3); + } } - const Wt::WDateTime dateTime3{ Wt::WDate {2001, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime3 }; - + TEST_F(DatabaseFixture, Listen_getRecentTracks_multi) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedUser user{ session, "MyUser" }; - const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) }; - EXPECT_TRUE(listen); - EXPECT_EQ(listen->getDateTime(), dateTime3); - } -} + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen1{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; -TEST_F(DatabaseFixture, Listen_getRecentReleases_multi) -{ - ScopedTrack track1{ session, "MyTrack1" }; - ScopedTrack track2{ session, "MyTrack2" }; - ScopedUser user{ session, "MyUser" }; - ScopedRelease release1{ session, "MyRelease1" }; - ScopedRelease release2{ session, "MyRelease2" }; + { + auto transaction{ session.createReadTransaction() }; - { - auto transaction{ session.createWriteTransaction() }; - track1.get().modify()->setRelease(release1.get()); - track2.get().modify()->setRelease(release2.get()); + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto tracks{ Listen::getRecentTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track2.getId()); + } + + ScopedListen listen2{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto tracks{ Listen::getRecentTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track1.getId()); + EXPECT_EQ(tracks.results[1], track2.getId()); + } + + ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(2) }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto tracks{ Listen::getRecentTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track2.getId()); + EXPECT_EQ(tracks.results[1], track1.getId()); + } + + ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(-1) }; + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + + auto tracks{ Listen::getRecentTracks(session, params) }; + EXPECT_EQ(tracks.moreResults, false); + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track2.getId()); + EXPECT_EQ(tracks.results[1], track1.getId()); + } } - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - + TEST_F(DatabaseFixture, Listen_getRecentTracks_cluster) { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; + ScopedClusterType clusterType{ session, "MyType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); + { + auto transaction{ session.createReadTransaction() }; - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release2.getId()); + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); + + auto tracks{ Listen::getRecentTracks(session, params) }; + EXPECT_EQ(tracks.results.size(), 0); + } + { + auto transaction{ session.createWriteTransaction() }; + cluster.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + Listen::StatsFindParameters params; + params.setUser(user->getId()); + params.setScrobblingBackend(ScrobblingBackend::Internal); + params.setClusters({ cluster.getId() }); + + auto tracks{ Listen::getRecentTracks(session, params) }; + EXPECT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results[0], track.getId()); + } } - - ScopedListen listen2{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release1.getId()); - EXPECT_EQ(releases.results[1], release2.getId()); - } - - ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(2) }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release2.getId()); - EXPECT_EQ(releases.results[1], release1.getId()); - } - - ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(-1) }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release2.getId()); - EXPECT_EQ(releases.results[1], release1.getId()); - } -} - -TEST_F(DatabaseFixture, Listen_getRecentReleases_cluster) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedClusterType clusterType{ session, "MyType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - ScopedRelease release{ session, "MyRelease" }; - - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setRelease(release.get()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); - - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.results.size(), 0); - } - - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); - - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.results.size(), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - cluster.get().modify()->addTrack(track.get()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); - - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release.getId()); - } -} - -TEST_F(DatabaseFixture, Listen_getRecentReleases_mediaLibrary) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedRelease release{ session, "MyRelease" }; - ScopedMediaLibrary library{ session }; - ScopedMediaLibrary otherLibrary{ session }; - - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setRelease(release.get()); - track.get().modify()->setMediaLibrary(library.get()); - } - - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setMediaLibrary(library.getId()); - - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release.getId()); - } - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setMediaLibrary(otherLibrary.getId()); - - auto releases{ Listen::getRecentReleases(session, params) }; - EXPECT_EQ(releases.moreResults, false); - EXPECT_EQ(releases.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Listen_getRecentTracks) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto tracks{ Listen::getRecentTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 0); - } - - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto tracks{ Listen::getRecentTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track.getId()); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::ListenBrainz); - - auto tracks{ Listen::getRecentTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - EXPECT_EQ(tracks.results.size(), 0); - } -} - - -TEST_F(DatabaseFixture, Listen_getRecentTracks_mediaLibrary) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedMediaLibrary library{ session }; - ScopedMediaLibrary otherLibrary{ session }; - - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setMediaLibrary(library.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setMediaLibrary(library.getId()); - - auto tracks{ Listen::getRecentTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track.getId()); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setMediaLibrary(otherLibrary.getId()); - - auto tracks{ Listen::getRecentTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - EXPECT_EQ(tracks.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Listen_getCount_track) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - - { - auto transaction{ session.createReadTransaction() }; - - const std::size_t count{ Listen::getCount(session, user->getId(), track.getId()) }; - EXPECT_EQ(count, 0); - } - - const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; - - { - auto transaction{ session.createReadTransaction() }; - - const std::size_t count{ Listen::getCount(session, user->getId(), track.getId()) }; - EXPECT_EQ(count, 1); - } - - { - auto transaction{ session.createWriteTransaction() }; - user.get().modify()->setScrobblingBackend(ScrobblingBackend::ListenBrainz); - } - - { - auto transaction{ session.createReadTransaction() }; - const std::size_t count{ Listen::getCount(session, user->getId(), track.getId()) }; - EXPECT_EQ(count, 0); - } -} - -TEST_F(DatabaseFixture, Listen_getCount_release) -{ - ScopedTrack track1{ session, "MyTrack" }; - ScopedTrack track2{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedRelease release{ session, "MyRelease" }; - - auto getReleaseListenCount{ [&] - { - auto transaction{ session.createReadTransaction() }; - return Listen::getCount(session, user->getId(), release.getId()); - } }; - - EXPECT_EQ(getReleaseListenCount(), 0); - - const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; - - EXPECT_EQ(getReleaseListenCount(), 0); - - { - auto transaction{ session.createWriteTransaction() }; - track1.get().modify()->setRelease(release.get()); - } - - EXPECT_EQ(getReleaseListenCount(), 1); - - { - auto transaction{ session.createWriteTransaction() }; - track2.get().modify()->setRelease(release.get()); - } - - EXPECT_EQ(getReleaseListenCount(), 0); - ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; - EXPECT_EQ(getReleaseListenCount(), 1); - ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; - EXPECT_EQ(getReleaseListenCount(), 1); - - ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; - EXPECT_EQ(getReleaseListenCount(), 2); - - { - auto transaction{ session.createWriteTransaction() }; - user.get().modify()->setScrobblingBackend(ScrobblingBackend::ListenBrainz); - } - EXPECT_EQ(getReleaseListenCount(), 0); -} - -TEST_F(DatabaseFixture, Listen_getMostRecentTrack) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - - { - auto transaction{ session.createReadTransaction() }; - - auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; - EXPECT_FALSE(listen); - } - - const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; - - { - auto transaction{ session.createReadTransaction() }; - - auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; - EXPECT_TRUE(listen); - EXPECT_EQ(listen->getDateTime(), dateTime1); - } - - const Wt::WDateTime dateTime2{ Wt::WDate {1999, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 }; - - { - auto transaction{ session.createReadTransaction() }; - - auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; - EXPECT_TRUE(listen); - EXPECT_EQ(listen->getDateTime(), dateTime1); - } - - const Wt::WDateTime dateTime3{ Wt::WDate {2001, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime3 }; - - { - auto transaction{ session.createReadTransaction() }; - - auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) }; - EXPECT_TRUE(listen); - EXPECT_EQ(listen->getDateTime(), dateTime3); - } -} - -TEST_F(DatabaseFixture, Listen_getRecentTracks_multi) -{ - ScopedTrack track1{ session, "MyTrack1" }; - ScopedTrack track2{ session, "MyTrack2" }; - ScopedUser user{ session, "MyUser" }; - - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen1{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto tracks{ Listen::getRecentTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track2.getId()); - } - - ScopedListen listen2{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto tracks{ Listen::getRecentTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track1.getId()); - EXPECT_EQ(tracks.results[1], track2.getId()); - } - - ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(2) }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto tracks{ Listen::getRecentTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track2.getId()); - EXPECT_EQ(tracks.results[1], track1.getId()); - } - - ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(-1) }; - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - - auto tracks{ Listen::getRecentTracks(session, params) }; - EXPECT_EQ(tracks.moreResults, false); - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track2.getId()); - EXPECT_EQ(tracks.results[1], track1.getId()); - } -} - -TEST_F(DatabaseFixture, Listen_getRecentTracks_cluster) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; - ScopedClusterType clusterType{ session, "MyType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); - - auto tracks{ Listen::getRecentTracks(session, params) }; - EXPECT_EQ(tracks.results.size(), 0); - } - { - auto transaction{ session.createWriteTransaction() }; - cluster.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - Listen::StatsFindParameters params; - params.setUser(user->getId()); - params.setScrobblingBackend(ScrobblingBackend::Internal); - params.setClusters({ cluster.getId() }); - - auto tracks{ Listen::getRecentTracks(session, params) }; - EXPECT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results[0], track.getId()); - } -} - +} \ No newline at end of file diff --git a/src/libs/database/test/Release.cpp b/src/libs/database/test/Release.cpp index 0096545b..483544bf 100644 --- a/src/libs/database/test/Release.cpp +++ b/src/libs/database/test/Release.cpp @@ -19,803 +19,805 @@ #include "Common.hpp" -using namespace Database; -using ScopedReleaseType = ScopedEntity; - -TEST_F(DatabaseFixture, Release) +namespace lms::db::tests { + using ScopedReleaseType = ScopedEntity; + + TEST_F(DatabaseFixture, Release) { - auto transaction{ session.createReadTransaction() }; + { + auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Release::getCount(session), 0); - EXPECT_EQ(Release::getCount(session, Release::FindParameters{}), 0); - EXPECT_FALSE(Release::exists(session, 0)); - EXPECT_FALSE(Release::exists(session, 1)); - } + EXPECT_EQ(Release::getCount(session), 0); + EXPECT_EQ(Release::getCount(session, Release::FindParameters{}), 0); + EXPECT_FALSE(Release::exists(session, 0)); + EXPECT_FALSE(Release::exists(session, 1)); + } - ScopedRelease release{ session, "MyRelease" }; - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_EQ(Release::getCount(session), 1); - EXPECT_EQ(Release::getCount(session, Release::FindParameters{}), 1); - EXPECT_TRUE(Release::exists(session, release.getId())); + ScopedRelease release{ session, "MyRelease" }; { - const auto releases{ Release::findOrphanIds(session) }; + auto transaction{ session.createReadTransaction() }; + + EXPECT_EQ(Release::getCount(session), 1); + EXPECT_EQ(Release::getCount(session, Release::FindParameters{}), 1); + EXPECT_TRUE(Release::exists(session, release.getId())); + + { + const auto releases{ Release::findOrphanIds(session) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + } + + { + const auto releases{ Release::findIds(session, Release::FindParameters {}) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + EXPECT_EQ(release->getDuration(), std::chrono::seconds{ 0 }); + } + + { + const auto releases{ Release::find(session, Release::FindParameters {}) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front()->getId(), release.getId()); + } + + { + bool visited{}; + Release::find(session, Release::FindParameters{}, [&](const Release::pointer& r) + { + visited = true; + EXPECT_EQ(r->getId(), release.getId()); + }); + EXPECT_TRUE(visited); + } + } + } + + TEST_F(DatabaseFixture, Release_singleTrack) + { + ScopedRelease release{ session, "MyRelease" }; + + { + ScopedTrack track{ session, "MyTrack" }; + { + auto transaction{ session.createWriteTransaction() }; + + track.get().modify()->setRelease(release.get()); + track.get().modify()->setName("MyTrackName"); + release.get().modify()->setName("MyReleaseName"); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); + + const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) }; + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track.getId()); + } + + { + auto transaction{ session.createWriteTransaction() }; + + ASSERT_TRUE(track->getRelease()); + EXPECT_EQ(track->getRelease()->getId(), release.getId()); + } + + { + auto transaction{ session.createWriteTransaction() }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setReleaseName("MyReleaseName")) }; + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track.getId()); + } + { + auto transaction{ session.createWriteTransaction() }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setReleaseName("MyReleaseFoo")) }; + EXPECT_EQ(tracks.results.size(), 0); + } + { + auto transaction{ session.createWriteTransaction() }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackFoo").setReleaseName("MyReleaseName")) }; + EXPECT_EQ(tracks.results.size(), 0); + } + } + + { + auto transaction{ session.createWriteTransaction() }; + + const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) }; + EXPECT_EQ(tracks.results.size(), 0); + + auto releases{ Release::findOrphanIds(session) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); } - - { - const auto releases{ Release::findIds(session, Release::FindParameters {}) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - EXPECT_EQ(release->getDuration(), std::chrono::seconds{ 0 }); - } - - { - const auto releases{ Release::find(session, Release::FindParameters {}) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front()->getId(), release.getId()); - } - - { - bool visited{}; - Release::find(session, Release::FindParameters{}, [&](const Release::pointer& r) - { - visited = true; - EXPECT_EQ(r->getId(), release.getId()); - }); - EXPECT_TRUE(visited); - } } -} - -TEST_F(DatabaseFixture, Release_singleTrack) -{ - ScopedRelease release{ session, "MyRelease" }; + TEST_F(DatabaseFixture, Release_singleTrack_mediaLibrary) { ScopedTrack track{ session, "MyTrack" }; + ScopedRelease release{ session, "MyRelease" }; + ScopedMediaLibrary library{ session }; + ScopedMediaLibrary otherLibrary{ session }; + { auto transaction{ session.createWriteTransaction() }; track.get().modify()->setRelease(release.get()); - track.get().modify()->setName("MyTrackName"); - release.get().modify()->setName("MyReleaseName"); + track.get().modify()->setMediaLibrary(library.get()); + } + { + auto transaction{ session.createReadTransaction() }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setMediaLibrary(library->getId())) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setMediaLibrary(otherLibrary->getId())) }; + EXPECT_EQ(releases.results.size(), 0); + } + } + + TEST_F(DatabaseFixture, Release_findByNameAndPath) + { + ScopedRelease release1{ session, "MyRelease" }; + ScopedRelease release2{ session, "MyRelease" }; + ScopedTrack track1{ session, "MyTrack" }; + ScopedTrack track2{ session, "MyTrack" }; + + { + auto transaction{ session.createWriteTransaction() }; + + track1.get().modify()->setRelease(release1.get()); + track1.get().modify()->setPath("/tmp/foo/foo.mp3"); + + track2.get().modify()->setRelease(release2.get()); + track2.get().modify()->setPath("/tmp/bar/bar.mp3"); } { auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); + { + const auto releases{ Release::find(session, "MyRelease", "/tmp/foo") }; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front()->getId(), release1.getId()); + } - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) }; - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front(), track.getId()); + { + const auto releases{ Release::find(session, "MyRelease", "/tmp/bar") }; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front()->getId(), release2.getId()); + } } + } + + TEST_F(DatabaseFixture, MulitpleReleaseSearchByName) + { + ScopedRelease release1{ session, "MyRelease" }; + ScopedRelease release2{ session, "MyRelease%" }; + ScopedRelease release3{ session, "%MyRelease" }; + ScopedRelease release4{ session, "MyRelease%Foo" }; + ScopedRelease release5{ session, "Foo%MyRelease" }; + ScopedRelease release6{ session, "_yRelease" }; + + // filters does not work on orphans + ScopedTrack track1{ session, "MyTrack" }; + ScopedTrack track2{ session, "MyTrack" }; + ScopedTrack track3{ session, "MyTrack" }; + ScopedTrack track4{ session, "MyTrack" }; + ScopedTrack track5{ session, "MyTrack" }; + ScopedTrack track6{ session, "MyTrack" }; { auto transaction{ session.createWriteTransaction() }; - ASSERT_TRUE(track->getRelease()); - EXPECT_EQ(track->getRelease()->getId(), release.getId()); + track1.get().modify()->setRelease(release1.get()); + track2.get().modify()->setRelease(release2.get()); + track3.get().modify()->setRelease(release3.get()); + track4.get().modify()->setRelease(release4.get()); + track5.get().modify()->setRelease(release5.get()); + track6.get().modify()->setRelease(release6.get()); } - { - auto transaction{ session.createWriteTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setReleaseName("MyReleaseName")) }; - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front(), track.getId()); - } - { - auto transaction{ session.createWriteTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setReleaseName("MyReleaseFoo")) }; - EXPECT_EQ(tracks.results.size(), 0); - } - { - auto transaction{ session.createWriteTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackFoo").setReleaseName("MyReleaseName")) }; - EXPECT_EQ(tracks.results.size(), 0); - } - } - - { - auto transaction{ session.createWriteTransaction() }; - - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) }; - EXPECT_EQ(tracks.results.size(), 0); - - auto releases{ Release::findOrphanIds(session) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - } -} - -TEST_F(DatabaseFixture, Release_singleTrack_mediaLibrary) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedRelease release{ session, "MyRelease" }; - ScopedMediaLibrary library{ session }; - ScopedMediaLibrary otherLibrary{ session }; - - { - auto transaction{ session.createWriteTransaction() }; - - track.get().modify()->setRelease(release.get()); - track.get().modify()->setMediaLibrary(library.get()); - } - { - auto transaction{ session.createReadTransaction() }; - auto releases{ Release::findIds(session, Release::FindParameters{}.setMediaLibrary(library->getId())) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - } - { - auto transaction{ session.createReadTransaction() }; - auto releases{ Release::findIds(session, Release::FindParameters{}.setMediaLibrary(otherLibrary->getId())) }; - EXPECT_EQ(releases.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Release_findByNameAndPath) -{ - ScopedRelease release1{ session, "MyRelease" }; - ScopedRelease release2{ session, "MyRelease" }; - ScopedTrack track1{ session, "MyTrack" }; - ScopedTrack track2{ session, "MyTrack" }; - - { - auto transaction{ session.createWriteTransaction() }; - - track1.get().modify()->setRelease(release1.get()); - track1.get().modify()->setPath("/tmp/foo/foo.mp3"); - - track2.get().modify()->setRelease(release2.get()); - track2.get().modify()->setPath("/tmp/bar/bar.mp3"); - } - - { - auto transaction{ session.createReadTransaction() }; - { - const auto releases{ Release::find(session, "MyRelease", "/tmp/foo") }; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release1.getId()); - } - - { - const auto releases{ Release::find(session, "MyRelease", "/tmp/bar") }; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release2.getId()); - } - } -} - -TEST_F(DatabaseFixture, MulitpleReleaseSearchByName) -{ - ScopedRelease release1{ session, "MyRelease" }; - ScopedRelease release2{ session, "MyRelease%" }; - ScopedRelease release3{ session, "%MyRelease" }; - ScopedRelease release4{ session, "MyRelease%Foo" }; - ScopedRelease release5{ session, "Foo%MyRelease" }; - ScopedRelease release6{ session, "_yRelease" }; - - // filters does not work on orphans - ScopedTrack track1{ session, "MyTrack" }; - ScopedTrack track2{ session, "MyTrack" }; - ScopedTrack track3{ session, "MyTrack" }; - ScopedTrack track4{ session, "MyTrack" }; - ScopedTrack track5{ session, "MyTrack" }; - ScopedTrack track6{ session, "MyTrack" }; - - { - auto transaction{ session.createWriteTransaction() }; - - track1.get().modify()->setRelease(release1.get()); - track2.get().modify()->setRelease(release2.get()); - track3.get().modify()->setRelease(release3.get()); - track4.get().modify()->setRelease(release4.get()); - track5.get().modify()->setRelease(release5.get()); - track6.get().modify()->setRelease(release6.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"Release"})) }; - EXPECT_EQ(releases.results.size(), 6); - } - - { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"MyRelease"})) }; - ASSERT_EQ(releases.results.size(), 5); - EXPECT_TRUE(std::none_of(std::cbegin(releases.results), std::cend(releases.results), [&](const ReleaseId releaseId) { return releaseId == release6.getId(); })); - } - { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"MyRelease%"})) }; - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release2.getId()); - EXPECT_EQ(releases.results[1], release4.getId()); - } - { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"%MyRelease"})) }; - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], release3.getId()); - EXPECT_EQ(releases.results[1], release5.getId()); - } - { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"Foo%MyRelease"})) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release5.getId()); - } - { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"MyRelease%Foo"})) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results[0], release4.getId()); - } - } -} - -TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack) -{ - ScopedRelease release1{ session, "MyRelease" }; - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_FALSE(release1->getTotalDisc()); - } - - ScopedTrack track1{ session, "MyTrack" }; - { - auto transaction{ session.createWriteTransaction() }; - - track1.get().modify()->setRelease(release1.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_FALSE(release1->getTotalDisc()); - } - - { - auto transaction{ session.createWriteTransaction() }; - - track1.get().modify()->setTotalTrack(36); - release1.get().modify()->setTotalDisc(6); - } - - { - auto transaction{ session.createReadTransaction() }; - - ASSERT_TRUE(track1->getTotalTrack()); - EXPECT_EQ(*track1->getTotalTrack(), 36); - ASSERT_TRUE(release1->getTotalDisc()); - EXPECT_EQ(*release1->getTotalDisc(), 6); - } - - ScopedTrack track2{ session, "MyTrack2" }; - { - auto transaction{ session.createWriteTransaction() }; - - track2.get().modify()->setRelease(release1.get()); - track2.get().modify()->setTotalTrack(37); - release1.get().modify()->setTotalDisc(67); - } - - { - auto transaction{ session.createReadTransaction() }; - - ASSERT_TRUE(track1->getTotalTrack()); - EXPECT_EQ(*track1->getTotalTrack(), 36); - ASSERT_TRUE(release1->getTotalDisc()); - EXPECT_EQ(*release1->getTotalDisc(), 67); - } - - ScopedRelease release2{ session, "MyRelease2" }; - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_FALSE(release2->getTotalDisc()); - } - - ScopedTrack track3{ session, "MyTrack3" }; - { - auto transaction{ session.createWriteTransaction() }; - - track3.get().modify()->setRelease(release2.get()); - track3.get().modify()->setTotalTrack(7); - release2.get().modify()->setTotalDisc(5); - } - { - auto transaction{ session.createReadTransaction() }; - - ASSERT_TRUE(track1->getTotalTrack()); - EXPECT_EQ(*track1->getTotalTrack(), 36); - ASSERT_TRUE(release1->getTotalDisc()); - EXPECT_EQ(*release2->getTotalDisc(), 5); - ASSERT_TRUE(track3->getTotalTrack()); - EXPECT_EQ(*track3->getTotalTrack(), 7); - ASSERT_TRUE(release2->getTotalDisc()); - EXPECT_EQ(*release2->getTotalDisc(), 5); - } -} - -TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack) -{ - ScopedRelease release1{ session, "MyRelease1" }; - ScopedRelease release2{ session, "MyRelease2" }; - - ScopedTrack track1A{ session, "MyTrack1A" }; - ScopedTrack track1B{ session, "MyTrack1B" }; - ScopedTrack track2A{ session, "MyTrack2A" }; - ScopedTrack track2B{ session, "MyTrack2B" }; - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_EQ(Track::findIds(session, Track::FindParameters{}.setRelease(release1.getId())).results.size(), 0); - EXPECT_EQ(Track::findIds(session, Track::FindParameters{}.setRelease(release2.getId())).results.size(), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - - track1A.get().modify()->setRelease(release1.get()); - track1B.get().modify()->setRelease(release1.get()); - track2A.get().modify()->setRelease(release2.get()); - track2B.get().modify()->setRelease(release2.get()); - - track1A.get().modify()->setTrackNumber(1); - track1B.get().modify()->setTrackNumber(2); - - track2A.get().modify()->setDiscNumber(2); - track2A.get().modify()->setTrackNumber(1); - track2B.get().modify()->setTrackNumber(2); - track2B.get().modify()->setDiscNumber(1); - } - - { - auto transaction{ session.createReadTransaction() }; - - { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release1.getId()).setSortMethod(TrackSortMethod::Release)) }; - EXPECT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results.front(), track1A.getId()); - } - - { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release2.getId()).setSortMethod(TrackSortMethod::Release)) }; - EXPECT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results.front(), track2B.getId()); - } - } -} - -TEST_F(DatabaseFixture, MultiTracksSingleReleaseDate) -{ - ScopedRelease release1{ session, "MyRelease1" }; - ScopedRelease release2{ session, "MyRelease2" }; - const Wt::WDate release1Date{ Wt::WDate {1994, 2, 3} }; - const Wt::WDate release1OriginalDate{ Wt::WDate {1993, 4, 5} }; - - ScopedTrack track1A{ session, "MyTrack1A" }; - ScopedTrack track1B{ session, "MyTrack1B" }; - ScopedTrack track2A{ session, "MyTrack2A" }; - ScopedTrack track2B{ session, "MyTrack2B" }; - - { - auto transaction{ session.createReadTransaction() }; - - const auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(0, 3000))) }; - EXPECT_EQ(releases.results.size(), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - - track1A.get().modify()->setRelease(release1.get()); - track1B.get().modify()->setRelease(release1.get()); - track2A.get().modify()->setRelease(release2.get()); - track2B.get().modify()->setRelease(release2.get()); - - track1A.get().modify()->setDate(release1Date); - track1B.get().modify()->setDate(release1Date); - track1A.get().modify()->setOriginalDate(release1OriginalDate); - track1B.get().modify()->setOriginalDate(release1OriginalDate); - - EXPECT_EQ(release1.get()->getDate(), release1Date); - EXPECT_EQ(release1.get()->getOriginalDate(), release1OriginalDate); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(1950, 2000))) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release1.getId()); - - releases = Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(1994, 1994))); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release1.getId()); - - releases = Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(1993, 1993))); - ASSERT_EQ(releases.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, MultiTracksSingleReleaseYear) -{ - ScopedRelease release1{ session, "MyRelease1" }; - ScopedRelease release2{ session, "MyRelease2" }; - const int release1Year{ 1994 }; - const int release1OriginalYear{ 1993 }; - - ScopedTrack track1A{ session, "MyTrack1A" }; - ScopedTrack track1B{ session, "MyTrack1B" }; - ScopedTrack track2A{ session, "MyTrack2A" }; - ScopedTrack track2B{ session, "MyTrack2B" }; - - { - auto transaction{ session.createReadTransaction() }; - - const auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(0, 3000))) }; - EXPECT_EQ(releases.results.size(), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - - track1A.get().modify()->setRelease(release1.get()); - track1B.get().modify()->setRelease(release1.get()); - track2A.get().modify()->setRelease(release2.get()); - track2B.get().modify()->setRelease(release2.get()); - - - track1A.get().modify()->setYear(release1Year); - track1B.get().modify()->setYear(release1Year); - track1A.get().modify()->setOriginalYear(release1OriginalYear); - track1B.get().modify()->setOriginalYear(release1OriginalYear); - - EXPECT_EQ(release1.get()->getYear(), release1Year); - EXPECT_EQ(release1.get()->getOriginalYear(), release1OriginalYear); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(1950, 2000))) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release1.getId()); - - releases = Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(1994, 1994))); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release1.getId()); - - releases = Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(1993, 1993))); - ASSERT_EQ(releases.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Release_writtenAfter) -{ - ScopedRelease release{ session, "MyRelease" }; - ScopedTrack track{ session, "MyTrack" }; - - const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 1}, Wt::WTime {12, 30, 20} }; - - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setLastWriteTime(dateTime); - track.get().modify()->setRelease(release.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}) }; - EXPECT_EQ(releases.results.size(), 1); - } - - { - auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setWrittenAfter(dateTime.addSecs(-1))) }; - EXPECT_EQ(releases.results.size(), 1); - } - - { - auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setWrittenAfter(dateTime.addSecs(+1))) }; - EXPECT_EQ(releases.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Release_artist) -{ - ScopedRelease release{ session, "MyRelease" }; - ScopedTrack track{ session, "MyTrack" }; - ScopedArtist artist{ session, "MyArtist" }; - ScopedArtist artist2{ session, "MyArtist2" }; - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setRelease(release.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Artist})) }; - EXPECT_EQ(releases.results.size(), 0); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist })); - EXPECT_EQ(releases.results.size(), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); - - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Artist})) }; - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer })); - EXPECT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist })); - EXPECT_EQ(releases.results.size(), 0); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId())); - EXPECT_EQ(releases.results.size(), 0); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist, TrackArtistLinkType::Artist })); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())); - ASSERT_EQ(releases.results.size(), 1); - EXPECT_EQ(releases.results.front(), release.getId()); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer })); - EXPECT_EQ(releases.results.size(), 0); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer, TrackArtistLinkType::Mixer })); - EXPECT_EQ(releases.results.size(), 0); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), {}, { TrackArtistLinkType::Artist })); - EXPECT_EQ(releases.results.size(), 0); - - releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), {}, { TrackArtistLinkType::Artist, TrackArtistLinkType::Composer })); - EXPECT_EQ(releases.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Release_getDiscCount) -{ - ScopedRelease release{ session, "MyRelease" }; - ScopedTrack track{ session, "MyTrack" }; - ScopedTrack track2{ session, "MyTrack2" }; - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(release.get()->getDiscCount(), 0); - } - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setRelease(release.get()); - } - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(release.get()->getDiscCount(), 0); - } - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setDiscNumber(5); - } - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(release.get()->getDiscCount(), 1); - } - { - auto transaction{ session.createWriteTransaction() }; - track2.get().modify()->setRelease(release.get()); - track2.get().modify()->setDiscNumber(5); - } - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(release.get()->getDiscCount(), 1); - } - { - auto transaction{ session.createWriteTransaction() }; - track2.get().modify()->setDiscNumber(6); - } - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(release.get()->getDiscCount(), 2); - } -} - -TEST_F(DatabaseFixture, ReleaseType) -{ - { - auto transaction{ session.createReadTransaction() }; - ReleaseType::pointer res{ ReleaseType::find(session, "album") }; - EXPECT_EQ(res, ReleaseType::pointer{}); - } - - ScopedReleaseType releaseType{ session, "album" }; - - { - auto transaction{ session.createReadTransaction() }; - ReleaseType::pointer res{ ReleaseType::find(session, "album") }; - EXPECT_EQ(res, releaseType.get()); - } -} - -TEST_F(DatabaseFixture, Release_releaseType) -{ - ScopedRelease release{ session, "MyRelease" }; - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(release.get()->getReleaseTypes().size(), 0); - } - - ScopedReleaseType releaseType{ session, "album" }; - - { - auto transaction{ session.createWriteTransaction() }; - release.get().modify()->addReleaseType(releaseType.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto releaseTypes{ release.get()->getReleaseTypes() }; - ASSERT_EQ(releaseTypes.size(), 1); - EXPECT_EQ(releaseTypes.front()->getId(), releaseType.getId()); - - const auto releaseTypeNames{ release.get()->getReleaseTypeNames() }; - ASSERT_EQ(releaseTypeNames.size(), 1); - EXPECT_EQ(releaseTypeNames.front(), "album"); - } -} - -TEST_F(DatabaseFixture, Release_find_releaseType) -{ - ScopedRelease release{ session, "MyRelease" }; - - { - auto transaction{ session.createReadTransaction() }; - auto releases{ Release::find(session, Release::FindParameters{}.setReleaseType("Foo")).results }; - EXPECT_EQ(releases.size(), 0); - } - - ScopedReleaseType releaseType{ session, "album" }; - - { - auto transaction{ session.createWriteTransaction() }; - release.get().modify()->addReleaseType(releaseType.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto releases{ Release::find(session, Release::FindParameters{}.setReleaseType("Foo")).results }; - EXPECT_EQ(releases.size(), 0); - - releases = Release::find(session, Release::FindParameters{}.setReleaseType("album")).results; - ASSERT_EQ(releases.size(), 1); - EXPECT_EQ(releases.front()->getId(), release.getId()); - } -} - -TEST_F(DatabaseFixture, Release_sortMethod) -{ - ScopedRelease release1{ session, "MyRelease1" }; - const Wt::WDate release1Date{ Wt::WDate {2000, 2, 3} }; - const Wt::WDate release1OriginalDate{ Wt::WDate {1993, 4, 5} }; - - ScopedRelease release2{ session, "MyRelease2" }; - const Wt::WDate release2Date{ Wt::WDate {1994, 2, 3} }; - - ScopedTrack track1{ session, "MyTrack1" }; - ScopedTrack track2{ session, "MyTrack2" }; - - ASSERT_LT(release2Date, release1Date); - ASSERT_GT(release2Date, release1OriginalDate); - - { - auto transaction{ session.createWriteTransaction() }; - - track1.get().modify()->setRelease(release1.get()); - track1.get().modify()->setOriginalDate(release1OriginalDate); - track1.get().modify()->setDate(release1Date); - - track2.get().modify()->setRelease(release2.get()); - track2.get().modify()->setDate(release2Date); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Name)) }; - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results.front(), release1.getId()); - EXPECT_EQ(releases.results.back(), release2.getId()); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Random)) }; - ASSERT_EQ(releases.results.size(), 2); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Date)) }; - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results.front(), release2.getId()); - EXPECT_EQ(releases.results.back(), release1.getId()); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::OriginalDate)) }; - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results.front(), release1.getId()); - EXPECT_EQ(releases.results.back(), release2.getId()); - } - { - auto transaction{ session.createReadTransaction() }; - - const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::OriginalDateDesc)) }; - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results.front(), release2.getId()); - EXPECT_EQ(releases.results.back(), release1.getId()); - } -} - -TEST_F(DatabaseFixture, Release_meanBitrate) -{ - ScopedRelease release1{ session, "MyRelease1" }; - ScopedTrack track1{ session, "MyTrack1" }; - ScopedTrack track2{ session, "MyTrack2" }; - ScopedTrack track3{ session, "MyTrack3" }; - - auto checkExpectedBitrate = [&](std::size_t bitrate) { auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(release1->getMeanBitrate(), bitrate); - }; - checkExpectedBitrate(0); + { + const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"Release"})) }; + EXPECT_EQ(releases.results.size(), 6); + } - { - auto transaction{ session.createWriteTransaction() }; - track1.get().modify()->setBitrate(128); - track1.get().modify()->setRelease(release1.get()); + { + const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"MyRelease"})) }; + ASSERT_EQ(releases.results.size(), 5); + EXPECT_TRUE(std::none_of(std::cbegin(releases.results), std::cend(releases.results), [&](const ReleaseId releaseId) { return releaseId == release6.getId(); })); + } + { + const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"MyRelease%"})) }; + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release2.getId()); + EXPECT_EQ(releases.results[1], release4.getId()); + } + { + const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"%MyRelease"})) }; + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], release3.getId()); + EXPECT_EQ(releases.results[1], release5.getId()); + } + { + const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"Foo%MyRelease"})) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release5.getId()); + } + { + const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"MyRelease%Foo"})) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results[0], release4.getId()); + } + } } - checkExpectedBitrate(128); - + TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack) { - auto transaction{ session.createWriteTransaction() }; - track2.get().modify()->setBitrate(256); - track2.get().modify()->setRelease(release1.get()); - } - checkExpectedBitrate(192); + ScopedRelease release1{ session, "MyRelease" }; - { - auto transaction{ session.createWriteTransaction() }; - track3.get().modify()->setBitrate(0); - track3.get().modify()->setRelease(release1.get()); + { + auto transaction{ session.createReadTransaction() }; + + EXPECT_FALSE(release1->getTotalDisc()); + } + + ScopedTrack track1{ session, "MyTrack" }; + { + auto transaction{ session.createWriteTransaction() }; + + track1.get().modify()->setRelease(release1.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + EXPECT_FALSE(release1->getTotalDisc()); + } + + { + auto transaction{ session.createWriteTransaction() }; + + track1.get().modify()->setTotalTrack(36); + release1.get().modify()->setTotalDisc(6); + } + + { + auto transaction{ session.createReadTransaction() }; + + ASSERT_TRUE(track1->getTotalTrack()); + EXPECT_EQ(*track1->getTotalTrack(), 36); + ASSERT_TRUE(release1->getTotalDisc()); + EXPECT_EQ(*release1->getTotalDisc(), 6); + } + + ScopedTrack track2{ session, "MyTrack2" }; + { + auto transaction{ session.createWriteTransaction() }; + + track2.get().modify()->setRelease(release1.get()); + track2.get().modify()->setTotalTrack(37); + release1.get().modify()->setTotalDisc(67); + } + + { + auto transaction{ session.createReadTransaction() }; + + ASSERT_TRUE(track1->getTotalTrack()); + EXPECT_EQ(*track1->getTotalTrack(), 36); + ASSERT_TRUE(release1->getTotalDisc()); + EXPECT_EQ(*release1->getTotalDisc(), 67); + } + + ScopedRelease release2{ session, "MyRelease2" }; + { + auto transaction{ session.createReadTransaction() }; + + EXPECT_FALSE(release2->getTotalDisc()); + } + + ScopedTrack track3{ session, "MyTrack3" }; + { + auto transaction{ session.createWriteTransaction() }; + + track3.get().modify()->setRelease(release2.get()); + track3.get().modify()->setTotalTrack(7); + release2.get().modify()->setTotalDisc(5); + } + { + auto transaction{ session.createReadTransaction() }; + + ASSERT_TRUE(track1->getTotalTrack()); + EXPECT_EQ(*track1->getTotalTrack(), 36); + ASSERT_TRUE(release1->getTotalDisc()); + EXPECT_EQ(*release2->getTotalDisc(), 5); + ASSERT_TRUE(track3->getTotalTrack()); + EXPECT_EQ(*track3->getTotalTrack(), 7); + ASSERT_TRUE(release2->getTotalDisc()); + EXPECT_EQ(*release2->getTotalDisc(), 5); + } + } + + TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack) + { + ScopedRelease release1{ session, "MyRelease1" }; + ScopedRelease release2{ session, "MyRelease2" }; + + ScopedTrack track1A{ session, "MyTrack1A" }; + ScopedTrack track1B{ session, "MyTrack1B" }; + ScopedTrack track2A{ session, "MyTrack2A" }; + ScopedTrack track2B{ session, "MyTrack2B" }; + + { + auto transaction{ session.createReadTransaction() }; + + EXPECT_EQ(Track::findIds(session, Track::FindParameters{}.setRelease(release1.getId())).results.size(), 0); + EXPECT_EQ(Track::findIds(session, Track::FindParameters{}.setRelease(release2.getId())).results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + + track1A.get().modify()->setRelease(release1.get()); + track1B.get().modify()->setRelease(release1.get()); + track2A.get().modify()->setRelease(release2.get()); + track2B.get().modify()->setRelease(release2.get()); + + track1A.get().modify()->setTrackNumber(1); + track1B.get().modify()->setTrackNumber(2); + + track2A.get().modify()->setDiscNumber(2); + track2A.get().modify()->setTrackNumber(1); + track2B.get().modify()->setTrackNumber(2); + track2B.get().modify()->setDiscNumber(1); + } + + { + auto transaction{ session.createReadTransaction() }; + + { + const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release1.getId()).setSortMethod(TrackSortMethod::Release)) }; + EXPECT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results.front(), track1A.getId()); + } + + { + const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release2.getId()).setSortMethod(TrackSortMethod::Release)) }; + EXPECT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results.front(), track2B.getId()); + } + } + } + + TEST_F(DatabaseFixture, MultiTracksSingleReleaseDate) + { + ScopedRelease release1{ session, "MyRelease1" }; + ScopedRelease release2{ session, "MyRelease2" }; + const Wt::WDate release1Date{ Wt::WDate {1994, 2, 3} }; + const Wt::WDate release1OriginalDate{ Wt::WDate {1993, 4, 5} }; + + ScopedTrack track1A{ session, "MyTrack1A" }; + ScopedTrack track1B{ session, "MyTrack1B" }; + ScopedTrack track2A{ session, "MyTrack2A" }; + ScopedTrack track2B{ session, "MyTrack2B" }; + + { + auto transaction{ session.createReadTransaction() }; + + const auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(0, 3000))) }; + EXPECT_EQ(releases.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + + track1A.get().modify()->setRelease(release1.get()); + track1B.get().modify()->setRelease(release1.get()); + track2A.get().modify()->setRelease(release2.get()); + track2B.get().modify()->setRelease(release2.get()); + + track1A.get().modify()->setDate(release1Date); + track1B.get().modify()->setDate(release1Date); + track1A.get().modify()->setOriginalDate(release1OriginalDate); + track1B.get().modify()->setOriginalDate(release1OriginalDate); + + EXPECT_EQ(release1.get()->getDate(), release1Date); + EXPECT_EQ(release1.get()->getOriginalDate(), release1OriginalDate); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(1950, 2000))) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release1.getId()); + + releases = Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(1994, 1994))); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release1.getId()); + + releases = Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(1993, 1993))); + ASSERT_EQ(releases.results.size(), 0); + } + } + + TEST_F(DatabaseFixture, MultiTracksSingleReleaseYear) + { + ScopedRelease release1{ session, "MyRelease1" }; + ScopedRelease release2{ session, "MyRelease2" }; + const int release1Year{ 1994 }; + const int release1OriginalYear{ 1993 }; + + ScopedTrack track1A{ session, "MyTrack1A" }; + ScopedTrack track1B{ session, "MyTrack1B" }; + ScopedTrack track2A{ session, "MyTrack2A" }; + ScopedTrack track2B{ session, "MyTrack2B" }; + + { + auto transaction{ session.createReadTransaction() }; + + const auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(0, 3000))) }; + EXPECT_EQ(releases.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + + track1A.get().modify()->setRelease(release1.get()); + track1B.get().modify()->setRelease(release1.get()); + track2A.get().modify()->setRelease(release2.get()); + track2B.get().modify()->setRelease(release2.get()); + + + track1A.get().modify()->setYear(release1Year); + track1B.get().modify()->setYear(release1Year); + track1A.get().modify()->setOriginalYear(release1OriginalYear); + track1B.get().modify()->setOriginalYear(release1OriginalYear); + + EXPECT_EQ(release1.get()->getYear(), release1Year); + EXPECT_EQ(release1.get()->getOriginalYear(), release1OriginalYear); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(1950, 2000))) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release1.getId()); + + releases = Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(1994, 1994))); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release1.getId()); + + releases = Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(1993, 1993))); + ASSERT_EQ(releases.results.size(), 0); + } + } + + TEST_F(DatabaseFixture, Release_writtenAfter) + { + ScopedRelease release{ session, "MyRelease" }; + ScopedTrack track{ session, "MyTrack" }; + + const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 1}, Wt::WTime {12, 30, 20} }; + + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setLastWriteTime(dateTime); + track.get().modify()->setRelease(release.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + const auto releases{ Release::findIds(session, Release::FindParameters {}) }; + EXPECT_EQ(releases.results.size(), 1); + } + + { + auto transaction{ session.createReadTransaction() }; + const auto releases{ Release::findIds(session, Release::FindParameters {}.setWrittenAfter(dateTime.addSecs(-1))) }; + EXPECT_EQ(releases.results.size(), 1); + } + + { + auto transaction{ session.createReadTransaction() }; + const auto releases{ Release::findIds(session, Release::FindParameters {}.setWrittenAfter(dateTime.addSecs(+1))) }; + EXPECT_EQ(releases.results.size(), 0); + } + } + + TEST_F(DatabaseFixture, Release_artist) + { + ScopedRelease release{ session, "MyRelease" }; + ScopedTrack track{ session, "MyTrack" }; + ScopedArtist artist{ session, "MyArtist" }; + ScopedArtist artist2{ session, "MyArtist2" }; + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setRelease(release.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Artist})) }; + EXPECT_EQ(releases.results.size(), 0); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist })); + EXPECT_EQ(releases.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + + auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Artist})) }; + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer })); + EXPECT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist })); + EXPECT_EQ(releases.results.size(), 0); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId())); + EXPECT_EQ(releases.results.size(), 0); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist, TrackArtistLinkType::Artist })); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())); + ASSERT_EQ(releases.results.size(), 1); + EXPECT_EQ(releases.results.front(), release.getId()); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer })); + EXPECT_EQ(releases.results.size(), 0); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer, TrackArtistLinkType::Mixer })); + EXPECT_EQ(releases.results.size(), 0); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), {}, { TrackArtistLinkType::Artist })); + EXPECT_EQ(releases.results.size(), 0); + + releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), {}, { TrackArtistLinkType::Artist, TrackArtistLinkType::Composer })); + EXPECT_EQ(releases.results.size(), 0); + } + } + + TEST_F(DatabaseFixture, Release_getDiscCount) + { + ScopedRelease release{ session, "MyRelease" }; + ScopedTrack track{ session, "MyTrack" }; + ScopedTrack track2{ session, "MyTrack2" }; + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release.get()->getDiscCount(), 0); + } + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setRelease(release.get()); + } + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release.get()->getDiscCount(), 0); + } + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setDiscNumber(5); + } + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release.get()->getDiscCount(), 1); + } + { + auto transaction{ session.createWriteTransaction() }; + track2.get().modify()->setRelease(release.get()); + track2.get().modify()->setDiscNumber(5); + } + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release.get()->getDiscCount(), 1); + } + { + auto transaction{ session.createWriteTransaction() }; + track2.get().modify()->setDiscNumber(6); + } + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release.get()->getDiscCount(), 2); + } + } + + TEST_F(DatabaseFixture, ReleaseType) + { + { + auto transaction{ session.createReadTransaction() }; + ReleaseType::pointer res{ ReleaseType::find(session, "album") }; + EXPECT_EQ(res, ReleaseType::pointer{}); + } + + ScopedReleaseType releaseType{ session, "album" }; + + { + auto transaction{ session.createReadTransaction() }; + ReleaseType::pointer res{ ReleaseType::find(session, "album") }; + EXPECT_EQ(res, releaseType.get()); + } + } + + TEST_F(DatabaseFixture, Release_releaseType) + { + ScopedRelease release{ session, "MyRelease" }; + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release.get()->getReleaseTypes().size(), 0); + } + + ScopedReleaseType releaseType{ session, "album" }; + + { + auto transaction{ session.createWriteTransaction() }; + release.get().modify()->addReleaseType(releaseType.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto releaseTypes{ release.get()->getReleaseTypes() }; + ASSERT_EQ(releaseTypes.size(), 1); + EXPECT_EQ(releaseTypes.front()->getId(), releaseType.getId()); + + const auto releaseTypeNames{ release.get()->getReleaseTypeNames() }; + ASSERT_EQ(releaseTypeNames.size(), 1); + EXPECT_EQ(releaseTypeNames.front(), "album"); + } + } + + TEST_F(DatabaseFixture, Release_find_releaseType) + { + ScopedRelease release{ session, "MyRelease" }; + + { + auto transaction{ session.createReadTransaction() }; + auto releases{ Release::find(session, Release::FindParameters{}.setReleaseType("Foo")).results }; + EXPECT_EQ(releases.size(), 0); + } + + ScopedReleaseType releaseType{ session, "album" }; + + { + auto transaction{ session.createWriteTransaction() }; + release.get().modify()->addReleaseType(releaseType.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto releases{ Release::find(session, Release::FindParameters{}.setReleaseType("Foo")).results }; + EXPECT_EQ(releases.size(), 0); + + releases = Release::find(session, Release::FindParameters{}.setReleaseType("album")).results; + ASSERT_EQ(releases.size(), 1); + EXPECT_EQ(releases.front()->getId(), release.getId()); + } + } + + TEST_F(DatabaseFixture, Release_sortMethod) + { + ScopedRelease release1{ session, "MyRelease1" }; + const Wt::WDate release1Date{ Wt::WDate {2000, 2, 3} }; + const Wt::WDate release1OriginalDate{ Wt::WDate {1993, 4, 5} }; + + ScopedRelease release2{ session, "MyRelease2" }; + const Wt::WDate release2Date{ Wt::WDate {1994, 2, 3} }; + + ScopedTrack track1{ session, "MyTrack1" }; + ScopedTrack track2{ session, "MyTrack2" }; + + ASSERT_LT(release2Date, release1Date); + ASSERT_GT(release2Date, release1OriginalDate); + + { + auto transaction{ session.createWriteTransaction() }; + + track1.get().modify()->setRelease(release1.get()); + track1.get().modify()->setOriginalDate(release1OriginalDate); + track1.get().modify()->setDate(release1Date); + + track2.get().modify()->setRelease(release2.get()); + track2.get().modify()->setDate(release2Date); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Name)) }; + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results.front(), release1.getId()); + EXPECT_EQ(releases.results.back(), release2.getId()); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Random)) }; + ASSERT_EQ(releases.results.size(), 2); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Date)) }; + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results.front(), release2.getId()); + EXPECT_EQ(releases.results.back(), release1.getId()); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::OriginalDate)) }; + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results.front(), release1.getId()); + EXPECT_EQ(releases.results.back(), release2.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + + const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::OriginalDateDesc)) }; + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results.front(), release2.getId()); + EXPECT_EQ(releases.results.back(), release1.getId()); + } + } + + TEST_F(DatabaseFixture, Release_meanBitrate) + { + ScopedRelease release1{ session, "MyRelease1" }; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedTrack track3{ session, "MyTrack3" }; + + auto checkExpectedBitrate = [&](std::size_t bitrate) + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release1->getMeanBitrate(), bitrate); + }; + + checkExpectedBitrate(0); + + { + auto transaction{ session.createWriteTransaction() }; + track1.get().modify()->setBitrate(128); + track1.get().modify()->setRelease(release1.get()); + } + + checkExpectedBitrate(128); + + { + auto transaction{ session.createWriteTransaction() }; + track2.get().modify()->setBitrate(256); + track2.get().modify()->setRelease(release1.get()); + } + checkExpectedBitrate(192); + + { + auto transaction{ session.createWriteTransaction() }; + track3.get().modify()->setBitrate(0); + track3.get().modify()->setRelease(release1.get()); + } + checkExpectedBitrate(192); // 0 should not be taken into account } - checkExpectedBitrate(192); // 0 should not be taken into account } \ No newline at end of file diff --git a/src/libs/database/test/StarredArtist.cpp b/src/libs/database/test/StarredArtist.cpp index a21c9a4b..ebd54357 100644 --- a/src/libs/database/test/StarredArtist.cpp +++ b/src/libs/database/test/StarredArtist.cpp @@ -20,129 +20,130 @@ #include "Common.hpp" #include "database/StarredArtist.hpp" -using namespace Database; - -using ScopedStarredArtist = ScopedEntity; - -TEST_F(DatabaseFixture, StarredArtist) +namespace lms::db::tests { - ScopedArtist artist{ session, "MyArtist" }; - ScopedUser user{ session, "MyUser" }; - ScopedUser user2{ session, "MyUser2" }; + using ScopedStarredArtist = ScopedEntity; + TEST_F(DatabaseFixture, StarredArtist) { - auto transaction{ session.createReadTransaction() }; + ScopedArtist artist{ session, "MyArtist" }; + ScopedUser user{ session, "MyUser" }; + ScopedUser user2{ session, "MyUser2" }; - auto starredArtist{ StarredArtist::find(session, artist->getId(), user->getId(), FeedbackBackend::Internal) }; - EXPECT_FALSE(starredArtist); - EXPECT_EQ(StarredArtist::getCount(session), 0); + { + auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters {}) }; - EXPECT_EQ(artists.results.size(), 1); + auto starredArtist{ StarredArtist::find(session, artist->getId(), user->getId(), FeedbackBackend::Internal) }; + EXPECT_FALSE(starredArtist); + EXPECT_EQ(StarredArtist::getCount(session), 0); + + auto artists{ Artist::findIds(session, Artist::FindParameters {}) }; + EXPECT_EQ(artists.results.size(), 1); + } + + ScopedStarredArtist starredArtist{ session, artist.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; + { + auto transaction{ session.createReadTransaction() }; + + auto gotArtist{ StarredArtist::find(session, artist->getId(), user->getId(), FeedbackBackend::Internal) }; + EXPECT_EQ(gotArtist->getId(), starredArtist->getId()); + EXPECT_EQ(StarredArtist::getCount(session), 1); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto artists{ Artist::findIds(session, Artist::FindParameters {}) }; + EXPECT_EQ(artists.results.size(), 1); + + artists = Artist::findIds(session, Artist::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); + EXPECT_EQ(artists.results.size(), 1); + + artists = Artist::findIds(session, Artist::FindParameters{}.setStarringUser(user2.getId(), FeedbackBackend::Internal)); + EXPECT_EQ(artists.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + user.get().modify()->setFeedbackBackend(FeedbackBackend::ListenBrainz); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto gotArtist{ StarredArtist::find(session, artist->getId(), user->getId()) }; + EXPECT_EQ(gotArtist, Artist::pointer{}); + } + + { + auto transaction{ session.createWriteTransaction() }; + user.get().modify()->setFeedbackBackend(FeedbackBackend::Internal); + } + + { + auto transaction{ session.createWriteTransaction() }; + auto gotArtist{ StarredArtist::find(session, artist->getId(), user->getId()) }; + EXPECT_EQ(gotArtist->getId(), starredArtist->getId()); + } } - ScopedStarredArtist starredArtist{ session, artist.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; + TEST_F(DatabaseFixture, StarredArtist_PendingDestroy) { - auto transaction{ session.createReadTransaction() }; + ScopedArtist artist{ session, "MyArtist" }; + ScopedUser user{ session, "MyUser" }; + ScopedStarredArtist starredArtist{ session, artist.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - auto gotArtist{ StarredArtist::find(session, artist->getId(), user->getId(), FeedbackBackend::Internal) }; - EXPECT_EQ(gotArtist->getId(), starredArtist->getId()); - EXPECT_EQ(StarredArtist::getCount(session), 1); + { + auto transaction{ session.createWriteTransaction() }; + + auto artists{ Artist::findIds(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + EXPECT_EQ(artists.results.size(), 1); + + starredArtist.get().modify()->setSyncState(SyncState::PendingRemove); + artists = Artist::findIds(session, Artist::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); + EXPECT_EQ(artists.results.size(), 0); + } } + TEST_F(DatabaseFixture, StarredArtist_dateTime) { - auto transaction{ session.createReadTransaction() }; + ScopedArtist artist1{ session, "MyArtist1" }; + ScopedArtist artist2{ session, "MyArtist2" }; + ScopedUser user{ session, "MyUser" }; - auto artists{ Artist::findIds(session, Artist::FindParameters {}) }; - EXPECT_EQ(artists.results.size(), 1); + ScopedStarredArtist starredArtist1{ session, artist1.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; + ScopedStarredArtist starredArtist2{ session, artist2.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - artists = Artist::findIds(session, Artist::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); - EXPECT_EQ(artists.results.size(), 1); + const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} }; - artists = Artist::findIds(session, Artist::FindParameters{}.setStarringUser(user2.getId(), FeedbackBackend::Internal)); - EXPECT_EQ(artists.results.size(), 0); + { + auto transaction{ session.createReadTransaction() }; + + auto artists{ Artist::find(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + EXPECT_EQ(artists.results.size(), 2); + } + + { + auto transaction{ session.createWriteTransaction() }; + + starredArtist1.get().modify()->setDateTime(dateTime); + starredArtist2.get().modify()->setDateTime(dateTime.addSecs(-1)); + + auto artists{ Artist::findIds(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ArtistSortMethod::StarredDateDesc)) }; + ASSERT_EQ(artists.results.size(), 2); + EXPECT_EQ(artists.results[0], starredArtist1->getArtist()->getId()); + EXPECT_EQ(artists.results[1], starredArtist2->getArtist()->getId()); + } + { + auto transaction{ session.createWriteTransaction() }; + + starredArtist1.get().modify()->setDateTime(dateTime); + starredArtist2.get().modify()->setDateTime(dateTime.addSecs(1)); + + auto artists{ Artist::findIds(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ArtistSortMethod::StarredDateDesc)) }; + ASSERT_EQ(artists.results.size(), 2); + EXPECT_EQ(artists.results[0], starredArtist2->getArtist()->getId()); + EXPECT_EQ(artists.results[1], starredArtist1->getArtist()->getId()); + } } - - { - auto transaction{ session.createWriteTransaction() }; - user.get().modify()->setFeedbackBackend(FeedbackBackend::ListenBrainz); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto gotArtist{ StarredArtist::find(session, artist->getId(), user->getId()) }; - EXPECT_EQ(gotArtist, Artist::pointer{}); - } - - { - auto transaction{ session.createWriteTransaction() }; - user.get().modify()->setFeedbackBackend(FeedbackBackend::Internal); - } - - { - auto transaction{ session.createWriteTransaction() }; - auto gotArtist{ StarredArtist::find(session, artist->getId(), user->getId()) }; - EXPECT_EQ(gotArtist->getId(), starredArtist->getId()); - } -} - -TEST_F(DatabaseFixture, StarredArtist_PendingDestroy) -{ - ScopedArtist artist{ session, "MyArtist" }; - ScopedUser user{ session, "MyUser" }; - ScopedStarredArtist starredArtist{ session, artist.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - - { - auto transaction{ session.createWriteTransaction() }; - - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; - EXPECT_EQ(artists.results.size(), 1); - - starredArtist.get().modify()->setSyncState(SyncState::PendingRemove); - artists = Artist::findIds(session, Artist::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); - EXPECT_EQ(artists.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, StarredArtist_dateTime) -{ - ScopedArtist artist1{ session, "MyArtist1" }; - ScopedArtist artist2{ session, "MyArtist2" }; - ScopedUser user{ session, "MyUser" }; - - ScopedStarredArtist starredArtist1{ session, artist1.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - ScopedStarredArtist starredArtist2{ session, artist2.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - - const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} }; - - { - auto transaction{ session.createReadTransaction() }; - - auto artists{ Artist::find(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; - EXPECT_EQ(artists.results.size(), 2); - } - - { - auto transaction{ session.createWriteTransaction() }; - - starredArtist1.get().modify()->setDateTime(dateTime); - starredArtist2.get().modify()->setDateTime(dateTime.addSecs(-1)); - - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ArtistSortMethod::StarredDateDesc)) }; - ASSERT_EQ(artists.results.size(), 2); - EXPECT_EQ(artists.results[0], starredArtist1->getArtist()->getId()); - EXPECT_EQ(artists.results[1], starredArtist2->getArtist()->getId()); - } - { - auto transaction{ session.createWriteTransaction() }; - - starredArtist1.get().modify()->setDateTime(dateTime); - starredArtist2.get().modify()->setDateTime(dateTime.addSecs(1)); - - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ArtistSortMethod::StarredDateDesc)) }; - ASSERT_EQ(artists.results.size(), 2); - EXPECT_EQ(artists.results[0], starredArtist2->getArtist()->getId()); - EXPECT_EQ(artists.results[1], starredArtist1->getArtist()->getId()); - } -} +} \ No newline at end of file diff --git a/src/libs/database/test/StarredRelease.cpp b/src/libs/database/test/StarredRelease.cpp index b940b4be..bc88509a 100644 --- a/src/libs/database/test/StarredRelease.cpp +++ b/src/libs/database/test/StarredRelease.cpp @@ -20,118 +20,119 @@ #include "Common.hpp" #include "database/StarredRelease.hpp" -using namespace Database; - -using ScopedStarredRelease = ScopedEntity; - -TEST_F(DatabaseFixture, StarredRelease) +namespace lms::db::tests { - ScopedRelease release{ session, "MyRelease" }; - ScopedUser user{ session, "MyUser" }; - ScopedUser user2{ session, "MyUser2" }; + using ScopedStarredRelease = ScopedEntity; + TEST_F(DatabaseFixture, StarredRelease) { - auto transaction{ session.createReadTransaction() }; + ScopedRelease release{ session, "MyRelease" }; + ScopedUser user{ session, "MyUser" }; + ScopedUser user2{ session, "MyUser2" }; - auto starredRelease{ StarredRelease::find(session, release->getId(), user->getId(), FeedbackBackend::Internal) }; - EXPECT_FALSE(starredRelease); - EXPECT_EQ(StarredRelease::getCount(session), 0); + { + auto transaction{ session.createReadTransaction() }; - auto releases{ Release::find(session, Release::FindParameters {}) }; - EXPECT_EQ(releases.results.size(), 1); + auto starredRelease{ StarredRelease::find(session, release->getId(), user->getId(), FeedbackBackend::Internal) }; + EXPECT_FALSE(starredRelease); + EXPECT_EQ(StarredRelease::getCount(session), 0); + + auto releases{ Release::find(session, Release::FindParameters {}) }; + EXPECT_EQ(releases.results.size(), 1); + } + + ScopedStarredRelease starredRelease{ session, release.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; + { + auto transaction{ session.createReadTransaction() }; + + auto gotRelease{ StarredRelease::find(session, release->getId(), user->getId(), FeedbackBackend::Internal) }; + EXPECT_EQ(gotRelease->getId(), starredRelease->getId()); + EXPECT_EQ(StarredRelease::getCount(session), 1); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto releases{ Release::find(session, Release::FindParameters {}) }; + EXPECT_EQ(releases.results.size(), 1); + + releases = Release::find(session, Release::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); + EXPECT_EQ(releases.results.size(), 1); + + releases = Release::find(session, Release::FindParameters{}.setStarringUser(user2.getId(), FeedbackBackend::Internal)); + EXPECT_EQ(releases.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + user.get().modify()->setFeedbackBackend(FeedbackBackend::ListenBrainz); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto gotRelease{ StarredRelease::find(session, release->getId(), user->getId()) }; + EXPECT_EQ(gotRelease, StarredRelease::pointer{}); + } } - ScopedStarredRelease starredRelease{ session, release.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; + TEST_F(DatabaseFixture, Starredrelease_PendingDestroy) { - auto transaction{ session.createReadTransaction() }; + ScopedRelease release{ session, "MyRelease" }; + ScopedUser user{ session, "MyUser" }; + ScopedStarredRelease starredRelease{ session, release.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - auto gotRelease{ StarredRelease::find(session, release->getId(), user->getId(), FeedbackBackend::Internal) }; - EXPECT_EQ(gotRelease->getId(), starredRelease->getId()); - EXPECT_EQ(StarredRelease::getCount(session), 1); + { + auto transaction{ session.createWriteTransaction() }; + + auto releases{ Release::find(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + EXPECT_EQ(releases.results.size(), 1); + + starredRelease.get().modify()->setSyncState(SyncState::PendingRemove); + releases = Release::find(session, Release::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); + EXPECT_EQ(releases.results.size(), 0); + } } + TEST_F(DatabaseFixture, StarredRelease_dateTime) { - auto transaction{ session.createReadTransaction() }; + ScopedRelease release1{ session, "MyRelease1" }; + ScopedRelease release2{ session, "MyRelease2" }; + ScopedUser user{ session, "MyUser" }; - auto releases{ Release::find(session, Release::FindParameters {}) }; - EXPECT_EQ(releases.results.size(), 1); + ScopedStarredRelease starredRelease1{ session, release1.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; + ScopedStarredRelease starredRelease2{ session, release2.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - releases = Release::find(session, Release::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); - EXPECT_EQ(releases.results.size(), 1); + const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} }; - releases = Release::find(session, Release::FindParameters{}.setStarringUser(user2.getId(), FeedbackBackend::Internal)); - EXPECT_EQ(releases.results.size(), 0); + { + auto transaction{ session.createReadTransaction() }; + + auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + EXPECT_EQ(releases.results.size(), 2); + } + + { + auto transaction{ session.createWriteTransaction() }; + + starredRelease1.get().modify()->setDateTime(dateTime); + starredRelease2.get().modify()->setDateTime(dateTime.addSecs(-1)); + + auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) }; + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], starredRelease1->getRelease()->getId()); + EXPECT_EQ(releases.results[1], starredRelease2->getRelease()->getId()); + } + { + auto transaction{ session.createWriteTransaction() }; + + starredRelease1.get().modify()->setDateTime(dateTime); + starredRelease2.get().modify()->setDateTime(dateTime.addSecs(1)); + + auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) }; + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results[0], starredRelease2->getRelease()->getId()); + EXPECT_EQ(releases.results[1], starredRelease1->getRelease()->getId()); + } } - - { - auto transaction{ session.createWriteTransaction() }; - user.get().modify()->setFeedbackBackend(FeedbackBackend::ListenBrainz); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto gotRelease{ StarredRelease::find(session, release->getId(), user->getId()) }; - EXPECT_EQ(gotRelease, StarredRelease::pointer{}); - } -} - -TEST_F(DatabaseFixture, Starredrelease_PendingDestroy) -{ - ScopedRelease release{ session, "MyRelease" }; - ScopedUser user{ session, "MyUser" }; - ScopedStarredRelease starredRelease{ session, release.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - - { - auto transaction{ session.createWriteTransaction() }; - - auto releases{ Release::find(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; - EXPECT_EQ(releases.results.size(), 1); - - starredRelease.get().modify()->setSyncState(SyncState::PendingRemove); - releases = Release::find(session, Release::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); - EXPECT_EQ(releases.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, StarredRelease_dateTime) -{ - ScopedRelease release1{ session, "MyRelease1" }; - ScopedRelease release2{ session, "MyRelease2" }; - ScopedUser user{ session, "MyUser" }; - - ScopedStarredRelease starredRelease1{ session, release1.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - ScopedStarredRelease starredRelease2{ session, release2.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - - const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} }; - - { - auto transaction{ session.createReadTransaction() }; - - auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; - EXPECT_EQ(releases.results.size(), 2); - } - - { - auto transaction{ session.createWriteTransaction() }; - - starredRelease1.get().modify()->setDateTime(dateTime); - starredRelease2.get().modify()->setDateTime(dateTime.addSecs(-1)); - - auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) }; - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], starredRelease1->getRelease()->getId()); - EXPECT_EQ(releases.results[1], starredRelease2->getRelease()->getId()); - } - { - auto transaction{ session.createWriteTransaction() }; - - starredRelease1.get().modify()->setDateTime(dateTime); - starredRelease2.get().modify()->setDateTime(dateTime.addSecs(1)); - - auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) }; - ASSERT_EQ(releases.results.size(), 2); - EXPECT_EQ(releases.results[0], starredRelease2->getRelease()->getId()); - EXPECT_EQ(releases.results[1], starredRelease1->getRelease()->getId()); - } -} +} \ No newline at end of file diff --git a/src/libs/database/test/StarredTrack.cpp b/src/libs/database/test/StarredTrack.cpp index cba2f026..ac21eeba 100644 --- a/src/libs/database/test/StarredTrack.cpp +++ b/src/libs/database/test/StarredTrack.cpp @@ -20,118 +20,119 @@ #include "Common.hpp" #include "database/StarredTrack.hpp" -using namespace Database; - -using ScopedStarredTrack = ScopedEntity; - -TEST_F(DatabaseFixture, StarredTrack) +namespace lms::db::tests { - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedUser user2{ session, "MyUser2" }; + using ScopedStarredTrack = ScopedEntity; + TEST_F(DatabaseFixture, StarredTrack) { - auto transaction {session.createReadTransaction()}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedUser user2{ session, "MyUser2" }; - auto starredTrack{ StarredTrack::find(session, track->getId(), user->getId(), FeedbackBackend::Internal) }; - EXPECT_FALSE(starredTrack); - EXPECT_EQ(StarredTrack::getCount(session), 0); + { + auto transaction{ session.createReadTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters {}) }; - EXPECT_EQ(tracks.results.size(), 1); + auto starredTrack{ StarredTrack::find(session, track->getId(), user->getId(), FeedbackBackend::Internal) }; + EXPECT_FALSE(starredTrack); + EXPECT_EQ(StarredTrack::getCount(session), 0); + + auto tracks{ Track::findIds(session, Track::FindParameters {}) }; + EXPECT_EQ(tracks.results.size(), 1); + } + + ScopedStarredTrack starredTrack{ session, track.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; + { + auto transaction{ session.createReadTransaction() }; + + auto gotTrack{ StarredTrack::find(session, track->getId(), user->getId(), FeedbackBackend::Internal) }; + EXPECT_EQ(gotTrack->getId(), starredTrack->getId()); + EXPECT_EQ(StarredTrack::getCount(session), 1); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto tracks{ Track::findIds(session, Track::FindParameters {}) }; + EXPECT_EQ(tracks.results.size(), 1); + + tracks = Track::findIds(session, Track::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); + EXPECT_EQ(tracks.results.size(), 1); + + tracks = Track::findIds(session, Track::FindParameters{}.setStarringUser(user2.getId(), FeedbackBackend::Internal)); + EXPECT_EQ(tracks.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + user.get().modify()->setFeedbackBackend(FeedbackBackend::ListenBrainz); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto gotRelease{ StarredTrack::find(session, track->getId(), user->getId()) }; + EXPECT_EQ(gotRelease, StarredTrack::pointer{}); + } } - ScopedStarredTrack starredTrack{ session, track.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; + TEST_F(DatabaseFixture, Starredtrack_PendingDestroy) { - auto transaction {session.createReadTransaction()}; + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; + ScopedStarredTrack starredTrack{ session, track.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - auto gotTrack{ StarredTrack::find(session, track->getId(), user->getId(), FeedbackBackend::Internal) }; - EXPECT_EQ(gotTrack->getId(), starredTrack->getId()); - EXPECT_EQ(StarredTrack::getCount(session), 1); + { + auto transaction{ session.createWriteTransaction() }; + + auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + EXPECT_EQ(tracks.results.size(), 1); + + starredTrack.get().modify()->setSyncState(SyncState::PendingRemove); + tracks = Track::findIds(session, Track::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); + EXPECT_EQ(tracks.results.size(), 0); + } } + TEST_F(DatabaseFixture, StarredTrack_dateTime) { - auto transaction {session.createReadTransaction()}; + ScopedTrack track1{ session, "MyTrack1" }; + ScopedTrack track2{ session, "MyTrack2" }; + ScopedUser user{ session, "MyUser" }; - auto tracks{ Track::findIds(session, Track::FindParameters {}) }; - EXPECT_EQ(tracks.results.size(), 1); + ScopedStarredTrack starredTrack1{ session, track1.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; + ScopedStarredTrack starredTrack2{ session, track2.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - tracks = Track::findIds(session, Track::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); - EXPECT_EQ(tracks.results.size(), 1); + const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} }; - tracks = Track::findIds(session, Track::FindParameters{}.setStarringUser(user2.getId(), FeedbackBackend::Internal)); - EXPECT_EQ(tracks.results.size(), 0); + { + auto transaction{ session.createReadTransaction() }; + + auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + EXPECT_EQ(tracks.results.size(), 2); + } + + { + auto transaction{ session.createWriteTransaction() }; + + starredTrack1.get().modify()->setDateTime(dateTime); + starredTrack2.get().modify()->setDateTime(dateTime.addSecs(-1)); + + auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(TrackSortMethod::StarredDateDesc)) }; + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], starredTrack1->getTrack()->getId()); + EXPECT_EQ(tracks.results[1], starredTrack2->getTrack()->getId()); + } + { + auto transaction{ session.createWriteTransaction() }; + + starredTrack1.get().modify()->setDateTime(dateTime); + starredTrack2.get().modify()->setDateTime(dateTime.addSecs(1)); + + auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(TrackSortMethod::StarredDateDesc)) }; + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], starredTrack2->getTrack()->getId()); + EXPECT_EQ(tracks.results[1], starredTrack1->getTrack()->getId()); + } } - - { - auto transaction{ session.createWriteTransaction() }; - user.get().modify()->setFeedbackBackend(FeedbackBackend::ListenBrainz); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto gotRelease{ StarredTrack::find(session, track->getId(), user->getId()) }; - EXPECT_EQ(gotRelease, StarredTrack::pointer{}); - } -} - -TEST_F(DatabaseFixture, Starredtrack_PendingDestroy) -{ - ScopedTrack track{ session, "MyTrack" }; - ScopedUser user{ session, "MyUser" }; - ScopedStarredTrack starredTrack{ session, track.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - - { - auto transaction {session.createWriteTransaction()}; - - auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; - EXPECT_EQ(tracks.results.size(), 1); - - starredTrack.get().modify()->setSyncState(SyncState::PendingRemove); - tracks = Track::findIds(session, Track::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); - EXPECT_EQ(tracks.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, StarredTrack_dateTime) -{ - ScopedTrack track1{ session, "MyTrack1" }; - ScopedTrack track2{ session, "MyTrack2" }; - ScopedUser user{ session, "MyUser" }; - - ScopedStarredTrack starredTrack1{ session, track1.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - ScopedStarredTrack starredTrack2{ session, track2.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - - const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} }; - - { - auto transaction {session.createReadTransaction()}; - - auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; - EXPECT_EQ(tracks.results.size(), 2); - } - - { - auto transaction {session.createWriteTransaction()}; - - starredTrack1.get().modify()->setDateTime(dateTime); - starredTrack2.get().modify()->setDateTime(dateTime.addSecs(-1)); - - auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(TrackSortMethod::StarredDateDesc)) }; - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], starredTrack1->getTrack()->getId()); - EXPECT_EQ(tracks.results[1], starredTrack2->getTrack()->getId()); - } - { - auto transaction {session.createWriteTransaction()}; - - starredTrack1.get().modify()->setDateTime(dateTime); - starredTrack2.get().modify()->setDateTime(dateTime.addSecs(1)); - - auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(TrackSortMethod::StarredDateDesc)) }; - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], starredTrack2->getTrack()->getId()); - EXPECT_EQ(tracks.results[1], starredTrack1->getTrack()->getId()); - } -} +} \ No newline at end of file diff --git a/src/libs/database/test/Track.cpp b/src/libs/database/test/Track.cpp index fcbeefec..4ba8883d 100644 --- a/src/libs/database/test/Track.cpp +++ b/src/libs/database/test/Track.cpp @@ -21,224 +21,224 @@ #include -using namespace Database; - -TEST_F(DatabaseFixture, Track) +namespace lms::db::tests { + TEST_F(DatabaseFixture, Track) + { + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Track::find(session, Track::FindParameters{}).results.size(), 0); + EXPECT_EQ(Track::findIds(session, Track::FindParameters{}).results.size(), 0); + EXPECT_EQ(Track::getCount(session), 0); + EXPECT_FALSE(Track::exists(session, 0)); + + { + bool visited{}; + Track::find(session, Track::FindParameters{}, [&](const Track::pointer&) {visited = true;}); + EXPECT_FALSE(visited); + } + } + + ScopedTrack track{ session, "MyTrackFile" }; + + { + auto transaction{ session.createReadTransaction() }; + + EXPECT_EQ(Track::find(session, Track::FindParameters{}).results.size(), 1); + EXPECT_EQ(Track::getCount(session), 1); + EXPECT_TRUE(Track::exists(session, track.getId())); + auto myTrack{ Track::find(session, track.getId()) }; + ASSERT_TRUE(myTrack); + EXPECT_EQ(myTrack->getId(), track.getId()); + + { + bool visited{}; + Track::find(session, Track::FindParameters{}, [&](const Track::pointer& t) + { + visited = true; + EXPECT_EQ(t->getId(), track.getId()); + }); + EXPECT_TRUE(visited); + } + } + } + + TEST_F(DatabaseFixture, Track_MediaLibrary) + { + ScopedTrack track{ session, "MyTrackFile" }; + ScopedMediaLibrary library{ session }; + ScopedMediaLibrary otherLibrary{ session }; + + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setMediaLibrary(library.get()); + } + + { + auto transaction{ session.createWriteTransaction() }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setMediaLibrary(library->getId())) }; + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track.getId()); + } + { + auto transaction{ session.createWriteTransaction() }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setMediaLibrary(otherLibrary->getId())) }; + EXPECT_EQ(tracks.results.size(), 0); + } + } + + TEST_F(DatabaseFixture, Track_noMediaLibrary) + { + ScopedTrack track{ session, "MyTrackFile" }; + { + auto transaction{ session.createReadTransaction() }; + MediaLibrary::pointer mediaLibrary{ track->getMediaLibrary() }; + EXPECT_EQ(mediaLibrary, MediaLibrary::pointer{}); + EXPECT_FALSE(mediaLibrary); + EXPECT_TRUE(!mediaLibrary); + } + } + + TEST_F(DatabaseFixture, TrackNotExists) { auto transaction{ session.createReadTransaction() }; + + EXPECT_FALSE(Track::exists(session, TrackId{ 42 })); + EXPECT_EQ(Track::find(session, TrackId{ 42 }), Track::pointer{}); + EXPECT_FALSE(Track::find(session, TrackId{ 42 })); EXPECT_EQ(Track::find(session, Track::FindParameters{}).results.size(), 0); - EXPECT_EQ(Track::findIds(session, Track::FindParameters{}).results.size(), 0); - EXPECT_EQ(Track::getCount(session), 0); - EXPECT_FALSE(Track::exists(session, 0)); - { - bool visited{}; - Track::find(session, Track::FindParameters{}, [&](const Track::pointer&) {visited = true;}); - EXPECT_FALSE(visited); + auto track{ Track::find(session, TrackId{ 42 }) }; + EXPECT_TRUE(!track); + EXPECT_FALSE(track); } } - ScopedTrack track{ session, "MyTrackFile" }; - + TEST_F(DatabaseFixture, MultipleTracks) { - auto transaction{ session.createReadTransaction() }; - - EXPECT_EQ(Track::find(session, Track::FindParameters{}).results.size(), 1); - EXPECT_EQ(Track::getCount(session), 1); - EXPECT_TRUE(Track::exists(session, track.getId())); - auto myTrack{ Track::find(session, track.getId()) }; - ASSERT_TRUE(myTrack); - EXPECT_EQ(myTrack->getId(), track.getId()); + ScopedTrack track1{ session, "MyTrackFile1" }; + ScopedTrack track2{ session, "MyTrackFile2" }; { - bool visited{}; - Track::find(session, Track::FindParameters{}, [&](const Track::pointer& t) - { - visited = true; - EXPECT_EQ(t->getId(), track.getId()); - }); - EXPECT_TRUE(visited); + auto transaction{ session.createReadTransaction() }; + + EXPECT_TRUE(track1.getId() != track2.getId()); + EXPECT_TRUE(track1.get() != track2.get()); + EXPECT_FALSE(track1.get() == track2.get()); } } -} - -TEST_F(DatabaseFixture, Track_MediaLibrary) -{ - ScopedTrack track{ session, "MyTrackFile" }; - ScopedMediaLibrary library{ session }; - ScopedMediaLibrary otherLibrary{ session }; + TEST_F(DatabaseFixture, MultipleTracksSearchByFilter) { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setMediaLibrary(library.get()); - } - - { - auto transaction{ session.createWriteTransaction() }; - const auto tracks{ Track::findIds(session, Track::FindParameters{}.setMediaLibrary(library->getId()))}; - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front(), track.getId()); - } - { - auto transaction{ session.createWriteTransaction() }; - const auto tracks{ Track::findIds(session, Track::FindParameters{}.setMediaLibrary(otherLibrary->getId()))}; - EXPECT_EQ(tracks.results.size(), 0); - } -} - -TEST_F(DatabaseFixture, Track_noMediaLibrary) -{ - ScopedTrack track{ session, "MyTrackFile" }; - { - auto transaction{ session.createReadTransaction() }; - MediaLibrary::pointer mediaLibrary{ track->getMediaLibrary() }; - EXPECT_EQ(mediaLibrary, MediaLibrary::pointer{}); - EXPECT_FALSE(mediaLibrary); - EXPECT_TRUE(!mediaLibrary); - } -} - -TEST_F(DatabaseFixture, TrackNotExists) -{ - auto transaction{ session.createReadTransaction() }; - - EXPECT_FALSE(Track::exists(session, TrackId{ 42 })); - EXPECT_EQ(Track::find(session, TrackId{ 42 }), Track::pointer{}); - EXPECT_FALSE(Track::find(session, TrackId{ 42 })); - EXPECT_EQ(Track::find(session, Track::FindParameters{}).results.size(), 0); - { - auto track{ Track::find(session, TrackId{ 42 }) }; - EXPECT_TRUE(!track); - EXPECT_FALSE(track); - } -} - -TEST_F(DatabaseFixture, MultipleTracks) -{ - ScopedTrack track1{ session, "MyTrackFile1" }; - ScopedTrack track2{ session, "MyTrackFile2" }; - - { - auto transaction{ session.createReadTransaction() }; - - EXPECT_TRUE(track1.getId() != track2.getId()); - EXPECT_TRUE(track1.get() != track2.get()); - EXPECT_FALSE(track1.get() == track2.get()); - } -} - -TEST_F(DatabaseFixture, MultipleTracksSearchByFilter) -{ - ScopedTrack track1{ session, "" }; - ScopedTrack track2{ session, "" }; - ScopedTrack track3{ session, "" }; - ScopedTrack track4{ session, "" }; - ScopedTrack track5{ session, "" }; - ScopedTrack track6{ session, "" }; - - { - auto transaction{ session.createWriteTransaction() }; - track1.get().modify()->setName("MyTrack"); - track2.get().modify()->setName("MyTrack%"); - track3.get().modify()->setName("MyTrack%Foo"); - track4.get().modify()->setName("%MyTrack"); - track5.get().modify()->setName("Foo%MyTrack"); - track6.get().modify()->setName("M_Track"); - } - - { - auto transaction{ session.createReadTransaction() }; + ScopedTrack track1{ session, "" }; + ScopedTrack track2{ session, "" }; + ScopedTrack track3{ session, "" }; + ScopedTrack track4{ session, "" }; + ScopedTrack track5{ session, "" }; + ScopedTrack track6{ session, "" }; { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"Track"})) }; - EXPECT_EQ(tracks.results.size(), 6); + auto transaction{ session.createWriteTransaction() }; + track1.get().modify()->setName("MyTrack"); + track2.get().modify()->setName("MyTrack%"); + track3.get().modify()->setName("MyTrack%Foo"); + track4.get().modify()->setName("%MyTrack"); + track5.get().modify()->setName("Foo%MyTrack"); + track6.get().modify()->setName("M_Track"); } + { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"MyTrack"})) }; - EXPECT_EQ(tracks.results.size(), 5); - EXPECT_TRUE(std::none_of(std::cbegin(tracks.results), std::cend(tracks.results), [&](const TrackId trackId) { return trackId == track6.getId(); })); + auto transaction{ session.createReadTransaction() }; + + { + const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"Track"})) }; + EXPECT_EQ(tracks.results.size(), 6); + } + { + const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"MyTrack"})) }; + EXPECT_EQ(tracks.results.size(), 5); + EXPECT_TRUE(std::none_of(std::cbegin(tracks.results), std::cend(tracks.results), [&](const TrackId trackId) { return trackId == track6.getId(); })); + } + { + const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"MyTrack%"})) }; + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track2.getId()); + EXPECT_EQ(tracks.results[1], track3.getId()); + } + { + const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"%MyTrack"})) }; + ASSERT_EQ(tracks.results.size(), 2); + EXPECT_EQ(tracks.results[0], track4.getId()); + EXPECT_EQ(tracks.results[1], track5.getId()); + } } + } + + TEST_F(DatabaseFixture, Track_date) + { + ScopedTrack track{ session, "MyTrack" }; + const Wt::WDate date{ 1995, 5, 5 }; + const Wt::WDate originalDate{ 1994, 2, 2 }; { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"MyTrack%"})) }; - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track2.getId()); - EXPECT_EQ(tracks.results[1], track3.getId()); + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(track->getYear(), std::nullopt); + EXPECT_EQ(track->getOriginalYear(), std::nullopt); } + { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"%MyTrack"})) }; - ASSERT_EQ(tracks.results.size(), 2); - EXPECT_EQ(tracks.results[0], track4.getId()); - EXPECT_EQ(tracks.results[1], track5.getId()); + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setDate(date); + track.get().modify()->setOriginalDate(originalDate); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(track->getYear(), std::nullopt); + EXPECT_EQ(track->getOriginalYear(), std::nullopt); + EXPECT_EQ(track->getDate(), date); + EXPECT_EQ(track->getOriginalDate(), originalDate); + } + + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setYear(date.year()); + track.get().modify()->setOriginalYear(originalDate.year()); + } + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(track->getYear(), date.year()); + EXPECT_EQ(track->getOriginalYear(), originalDate.year()); } } -} -TEST_F(DatabaseFixture, Track_date) -{ - ScopedTrack track{ session, "MyTrack" }; - const Wt::WDate date{ 1995, 5, 5 }; - const Wt::WDate originalDate{ 1994, 2, 2 }; + TEST_F(DatabaseFixture, Track_writtenAfter) { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(track->getYear(), std::nullopt); - EXPECT_EQ(track->getOriginalYear(), std::nullopt); + ScopedTrack track{ session, "MyTrack" }; + + const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 1}, Wt::WTime {12, 30, 20} }; + + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setLastWriteTime(dateTime); + } + + { + auto transaction{ session.createReadTransaction() }; + const auto tracks{ Track::findIds(session, Track::FindParameters {}) }; + EXPECT_EQ(tracks.results.size(), 1); + } + + { + auto transaction{ session.createReadTransaction() }; + const auto tracks{ Track::findIds(session, Track::FindParameters {}.setWrittenAfter(dateTime.addSecs(-1))) }; + EXPECT_EQ(tracks.results.size(), 1); + } + + { + auto transaction{ session.createReadTransaction() }; + const auto tracks{ Track::findIds(session, Track::FindParameters {}.setWrittenAfter(dateTime.addSecs(+1))) }; + EXPECT_EQ(tracks.results.size(), 0); + } } - - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setDate(date); - track.get().modify()->setOriginalDate(originalDate); - } - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(track->getYear(), std::nullopt); - EXPECT_EQ(track->getOriginalYear(), std::nullopt); - EXPECT_EQ(track->getDate(), date); - EXPECT_EQ(track->getOriginalDate(), originalDate); - } - - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setYear(date.year()); - track.get().modify()->setOriginalYear(originalDate.year()); - } - - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(track->getYear(), date.year()); - EXPECT_EQ(track->getOriginalYear(), originalDate.year()); - } -} - -TEST_F(DatabaseFixture, Track_writtenAfter) -{ - ScopedTrack track{ session, "MyTrack" }; - - const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 1}, Wt::WTime {12, 30, 20} }; - - { - auto transaction{ session.createWriteTransaction() }; - track.get().modify()->setLastWriteTime(dateTime); - } - - { - auto transaction{ session.createReadTransaction() }; - const auto tracks{ Track::findIds(session, Track::FindParameters {}) }; - EXPECT_EQ(tracks.results.size(), 1); - } - - { - auto transaction{ session.createReadTransaction() }; - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setWrittenAfter(dateTime.addSecs(-1))) }; - EXPECT_EQ(tracks.results.size(), 1); - } - - { - auto transaction{ session.createReadTransaction() }; - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setWrittenAfter(dateTime.addSecs(+1))) }; - EXPECT_EQ(tracks.results.size(), 0); - } -} - +} \ No newline at end of file diff --git a/src/libs/database/test/TrackBookmark.cpp b/src/libs/database/test/TrackBookmark.cpp index ddcae560..5571c92c 100644 --- a/src/libs/database/test/TrackBookmark.cpp +++ b/src/libs/database/test/TrackBookmark.cpp @@ -21,46 +21,47 @@ #include "database/TrackBookmark.hpp" -using ScopedTrackBookmark = ScopedEntity; - -using namespace Database; - -TEST_F(DatabaseFixture, TrackBookmark) +namespace lms::db::tests { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; + using ScopedTrackBookmark = ScopedEntity; - { - auto transaction {session.createReadTransaction()}; - EXPECT_EQ(TrackBookmark::getCount(session), 0); - } + TEST_F(DatabaseFixture, TrackBookmark) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; - ScopedTrackBookmark bookmark {session, user.lockAndGet(), track.lockAndGet()}; + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(TrackBookmark::getCount(session), 0); + } - { - auto transaction {session.createWriteTransaction()}; + ScopedTrackBookmark bookmark{ session, user.lockAndGet(), track.lockAndGet() }; - bookmark.get().modify()->setComment("MyComment"); - bookmark.get().modify()->setOffset(std::chrono::milliseconds {5}); - } + { + auto transaction{ session.createWriteTransaction() }; - { - auto transaction {session.createReadTransaction()}; + bookmark.get().modify()->setComment("MyComment"); + bookmark.get().modify()->setOffset(std::chrono::milliseconds{ 5 }); + } - EXPECT_EQ(TrackBookmark::getCount(session), 1); + { + auto transaction{ session.createReadTransaction() }; - const auto bookmarks {TrackBookmark::find(session, user.getId())}; - ASSERT_EQ(bookmarks.results.size(), 1); - EXPECT_EQ(bookmarks.results.front(), bookmark.getId()); - } - { - auto transaction {session.createReadTransaction()}; + EXPECT_EQ(TrackBookmark::getCount(session), 1); - auto userBookmark {TrackBookmark::find(session, user.getId(), track.getId())}; - ASSERT_TRUE(userBookmark); - EXPECT_EQ(userBookmark, bookmark.get()); + const auto bookmarks{ TrackBookmark::find(session, user.getId()) }; + ASSERT_EQ(bookmarks.results.size(), 1); + EXPECT_EQ(bookmarks.results.front(), bookmark.getId()); + } + { + auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(userBookmark->getOffset(), std::chrono::milliseconds {5}); - EXPECT_EQ(userBookmark->getComment(), "MyComment"); - } -} + auto userBookmark{ TrackBookmark::find(session, user.getId(), track.getId()) }; + ASSERT_TRUE(userBookmark); + EXPECT_EQ(userBookmark, bookmark.get()); + + EXPECT_EQ(userBookmark->getOffset(), std::chrono::milliseconds{ 5 }); + EXPECT_EQ(userBookmark->getComment(), "MyComment"); + } + } +} \ No newline at end of file diff --git a/src/libs/database/test/TrackFeatures.cpp b/src/libs/database/test/TrackFeatures.cpp index 6dce37f0..d5b74553 100644 --- a/src/libs/database/test/TrackFeatures.cpp +++ b/src/libs/database/test/TrackFeatures.cpp @@ -21,28 +21,29 @@ #include "database/TrackFeatures.hpp" -using ScopedTrackFeatures = ScopedEntity; - -using namespace Database; - -TEST_F(DatabaseFixture, TrackFeatures) +namespace lms::db::tests { - ScopedTrack track {session, "MyTrack"}; - ScopedUser user {session, "MyUser"}; + using ScopedTrackFeatures = ScopedEntity; - { - auto transaction {session.createReadTransaction()}; - EXPECT_EQ(TrackFeatures::getCount(session), 0); - } + TEST_F(DatabaseFixture, TrackFeatures) + { + ScopedTrack track{ session, "MyTrack" }; + ScopedUser user{ session, "MyUser" }; - ScopedTrackFeatures trackFeatures {session, track.lockAndGet(), ""}; + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(TrackFeatures::getCount(session), 0); + } - { - auto transaction {session.createWriteTransaction()}; - EXPECT_EQ(TrackFeatures::getCount(session), 1); + ScopedTrackFeatures trackFeatures{ session, track.lockAndGet(), "" }; - auto allTrackFeatures {TrackFeatures::find(session)}; - ASSERT_EQ(allTrackFeatures.results.size(), 1); - EXPECT_EQ(allTrackFeatures.results.front(), trackFeatures.getId()); - } -} + { + auto transaction{ session.createWriteTransaction() }; + EXPECT_EQ(TrackFeatures::getCount(session), 1); + + auto allTrackFeatures{ TrackFeatures::find(session) }; + ASSERT_EQ(allTrackFeatures.results.size(), 1); + EXPECT_EQ(allTrackFeatures.results.front(), trackFeatures.getId()); + } + } +} \ No newline at end of file diff --git a/src/libs/database/test/TrackList.cpp b/src/libs/database/test/TrackList.cpp index a1d0c979..d69fda7f 100644 --- a/src/libs/database/test/TrackList.cpp +++ b/src/libs/database/test/TrackList.cpp @@ -21,204 +21,205 @@ #include "Common.hpp" -using namespace Database; - -TEST_F(DatabaseFixture, SingleTrackList) +namespace lms::db::tests { - ScopedUser user{ session, "MyUser" }; + TEST_F(DatabaseFixture, SingleTrackList) { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(TrackList::getCount(session), 0); + ScopedUser user{ session, "MyUser" }; + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(TrackList::getCount(session), 0); + } + + ScopedTrackList trackList{ session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet() }; + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(TrackList::getCount(session), 1); + } } - ScopedTrackList trackList{ session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet() }; - + TEST_F(DatabaseFixture, SingleTrackListSingleTrack) { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(TrackList::getCount(session), 1); - } -} + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrack track{ session, "MyTrack" }; -TEST_F(DatabaseFixture, SingleTrackListSingleTrack) -{ - ScopedUser user{ session, "MyUser" }; - ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() }; - ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() }; - ScopedTrack track{ session, "MyTrack" }; + { + auto transaction{ session.createReadTransaction() }; - { - auto transaction{ session.createReadTransaction() }; + auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) }; + EXPECT_EQ(tracks.results.size(), 0); - auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) }; - EXPECT_EQ(tracks.results.size(), 0); + tracks = Track::findIds(session, Track::FindParameters{}.setTrackList(trackList2.getId())); + EXPECT_EQ(tracks.results.size(), 0); + } - tracks = Track::findIds(session, Track::FindParameters{}.setTrackList(trackList2.getId())); - EXPECT_EQ(tracks.results.size(), 0); + { + auto transaction{ session.createWriteTransaction() }; + + session.create(track.get(), trackList1.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) }; + ASSERT_EQ(tracks.results.size(), 1); + EXPECT_EQ(tracks.results.front(), track.getId()); + + tracks = Track::findIds(session, Track::FindParameters{}.setTrackList(trackList2.getId())); + EXPECT_EQ(tracks.results.size(), 0); + } } + TEST_F(DatabaseFixture, TrackList_SortMethod) { - auto transaction{ session.createWriteTransaction() }; + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrack track{ session, "MyTrack" }; - session.create(track.get(), trackList1.get()); + { + auto transaction{ session.createReadTransaction() }; + + const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::Name)) }; + ASSERT_EQ(trackLists.results.size(), 2); + EXPECT_EQ(trackLists.results[0], trackList1.getId()); + EXPECT_EQ(trackLists.results[1], trackList2.getId()); + } + + { + auto transaction{ session.createWriteTransaction() }; + + trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,1} }); + trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,2} }); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) }; + ASSERT_EQ(trackLists.results.size(), 2); + EXPECT_EQ(trackLists.results[0], trackList2.getId()); + EXPECT_EQ(trackLists.results[1], trackList1.getId()); + } + + { + auto transaction{ session.createWriteTransaction() }; + + trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,2} }); + trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,1} }); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) }; + ASSERT_EQ(trackLists.results.size(), 2); + EXPECT_EQ(trackLists.results[0], trackList1.getId()); + EXPECT_EQ(trackLists.results[1], trackList2.getId()); + } } + TEST_F(DatabaseFixture, SingleTrackListMultipleTrack) { - auto transaction{ session.createReadTransaction() }; + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList{ session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet() }; + std::list tracks; - auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) }; - ASSERT_EQ(tracks.results.size(), 1); - EXPECT_EQ(tracks.results.front(), track.getId()); + for (std::size_t i{}; i < 10; ++i) + { + tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - tracks = Track::findIds(session, Track::FindParameters{}.setTrackList(trackList2.getId())); - EXPECT_EQ(tracks.results.size(), 0); - } -} + auto transaction{ session.createWriteTransaction() }; + session.create(tracks.back().get(), trackList.get()); + } -TEST_F(DatabaseFixture, TrackList_SortMethod) -{ - ScopedUser user{ session, "MyUser" }; - ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() }; - ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() }; - ScopedTrack track{ session, "MyTrack" }; + { + auto transaction{ session.createReadTransaction() }; - { - auto transaction{ session.createReadTransaction() }; + ASSERT_EQ(trackList->getCount(), tracks.size()); + const auto trackIds{ trackList->getTrackIds() }; + ASSERT_EQ(trackIds.size(), tracks.size()); - const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::Name)) }; - ASSERT_EQ(trackLists.results.size(), 2); - EXPECT_EQ(trackLists.results[0], trackList1.getId()); - EXPECT_EQ(trackLists.results[1], trackList2.getId()); + // Same order + std::size_t i{}; + for (const ScopedTrack& track : tracks) + EXPECT_EQ(track.getId(), trackIds[i++]); + } } + TEST_F(DatabaseFixture, SingleTrackListSingleTrackWithCluster) { - auto transaction{ session.createWriteTransaction() }; + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedClusterType clusterType{ session, "MyClusterType" }; + ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; + ScopedTrack track{ session, "MyTrack" }; - trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,1} }); - trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,2} }); + { + auto transaction{ session.createReadTransaction() }; + + auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) }; + EXPECT_EQ(trackLists.results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + + session.create(track.get(), trackList1.get()); + cluster.get().modify()->addTrack(track.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) }; + ASSERT_EQ(trackLists.results.size(), 1); + EXPECT_EQ(trackLists.results.front(), trackList1.getId()); + } } + TEST_F(DatabaseFixture, SingleTrackList_getEntries) { - auto transaction{ session.createReadTransaction() }; + ScopedUser user{ session, "MyUser" }; + ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() }; + ScopedTrack track1{ session, "MyTrack" }; + ScopedTrack track2{ session, "MyTrack" }; - const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) }; - ASSERT_EQ(trackLists.results.size(), 2); - EXPECT_EQ(trackLists.results[0], trackList2.getId()); - EXPECT_EQ(trackLists.results[1], trackList1.getId()); + { + auto transaction{ session.createWriteTransaction() }; + session.create(track1.get(), trackList.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + auto entries{ trackList.get()->getEntries() }; + ASSERT_EQ(entries.size(), 1); + EXPECT_EQ(entries.front()->getTrack()->getId(), track1.getId()); + } + + { + auto transaction{ session.createWriteTransaction() }; + session.create(track2.get(), trackList.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + auto entries{ trackList.get()->getEntries() }; + ASSERT_EQ(entries.size(), 2); + EXPECT_EQ(entries[0]->getTrack()->getId(), track1.getId()); + EXPECT_EQ(entries[1]->getTrack()->getId(), track2.getId()); + } + + { + auto transaction{ session.createReadTransaction() }; + auto entries{ trackList.get()->getEntries(Range {1, 1}) }; + ASSERT_EQ(entries.size(), 1); + EXPECT_EQ(entries[0]->getTrack()->getId(), track2.getId()); + } } - - { - auto transaction{ session.createWriteTransaction() }; - - trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,2} }); - trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,1} }); - } - - { - auto transaction{ session.createReadTransaction() }; - - const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) }; - ASSERT_EQ(trackLists.results.size(), 2); - EXPECT_EQ(trackLists.results[0], trackList1.getId()); - EXPECT_EQ(trackLists.results[1], trackList2.getId()); - } -} - -TEST_F(DatabaseFixture, SingleTrackListMultipleTrack) -{ - ScopedUser user{ session, "MyUser" }; - ScopedTrackList trackList{ session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet() }; - std::list tracks; - - for (std::size_t i{}; i < 10; ++i) - { - tracks.emplace_back(session, "MyTrack" + std::to_string(i)); - - auto transaction{ session.createWriteTransaction() }; - session.create(tracks.back().get(), trackList.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - ASSERT_EQ(trackList->getCount(), tracks.size()); - const auto trackIds{ trackList->getTrackIds() }; - ASSERT_EQ(trackIds.size(), tracks.size()); - - // Same order - std::size_t i{}; - for (const ScopedTrack& track : tracks) - EXPECT_EQ(track.getId(), trackIds[i++]); - } -} - -TEST_F(DatabaseFixture, SingleTrackListSingleTrackWithCluster) -{ - ScopedUser user{ session, "MyUser" }; - ScopedTrackList trackList1{ session, "MyTrackList1", TrackListType::Playlist, false, user.lockAndGet() }; - ScopedTrackList trackList2{ session, "MyTrackList2", TrackListType::Playlist, false, user.lockAndGet() }; - ScopedClusterType clusterType{ session, "MyClusterType" }; - ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; - ScopedTrack track{ session, "MyTrack" }; - - { - auto transaction{ session.createReadTransaction() }; - - auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) }; - EXPECT_EQ(trackLists.results.size(), 0); - } - - { - auto transaction{ session.createWriteTransaction() }; - - session.create(track.get(), trackList1.get()); - cluster.get().modify()->addTrack(track.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - - auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) }; - ASSERT_EQ(trackLists.results.size(), 1); - EXPECT_EQ(trackLists.results.front(), trackList1.getId()); - } -} - -TEST_F(DatabaseFixture, SingleTrackList_getEntries) -{ - ScopedUser user{ session, "MyUser" }; - ScopedTrackList trackList{ session, "MyTrackList", TrackListType::Playlist, false, user.lockAndGet() }; - ScopedTrack track1{ session, "MyTrack" }; - ScopedTrack track2{ session, "MyTrack" }; - - { - auto transaction{ session.createWriteTransaction() }; - session.create(track1.get(), trackList.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - auto entries{ trackList.get()->getEntries() }; - ASSERT_EQ(entries.size(), 1); - EXPECT_EQ(entries.front()->getTrack()->getId(), track1.getId()); - } - - { - auto transaction{ session.createWriteTransaction() }; - session.create(track2.get(), trackList.get()); - } - - { - auto transaction{ session.createReadTransaction() }; - auto entries{ trackList.get()->getEntries() }; - ASSERT_EQ(entries.size(), 2); - EXPECT_EQ(entries[0]->getTrack()->getId(), track1.getId()); - EXPECT_EQ(entries[1]->getTrack()->getId(), track2.getId()); - } - - { - auto transaction{ session.createReadTransaction() }; - auto entries{ trackList.get()->getEntries(Range {1, 1}) }; - ASSERT_EQ(entries.size(), 1); - EXPECT_EQ(entries[0]->getTrack()->getId(), track2.getId()); - } -} +} \ No newline at end of file diff --git a/src/libs/image/CMakeLists.txt b/src/libs/image/CMakeLists.txt index cb855563..83ae0f50 100644 --- a/src/libs/image/CMakeLists.txt +++ b/src/libs/image/CMakeLists.txt @@ -12,7 +12,7 @@ target_include_directories(lmsimage PRIVATE ) target_link_libraries(lmsimage PUBLIC - lmsutils + lmscore std::filesystem ) diff --git a/src/libs/image/impl/graphicsmagick/JPEGImage.cpp b/src/libs/image/impl/graphicsmagick/JPEGImage.cpp index f2600f2c..8465a0c2 100644 --- a/src/libs/image/impl/graphicsmagick/JPEGImage.cpp +++ b/src/libs/image/impl/graphicsmagick/JPEGImage.cpp @@ -21,9 +21,9 @@ #include "RawImage.hpp" #include "image/Exception.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" -namespace Image::GraphicsMagick +namespace lms::image::GraphicsMagick { JPEGImage::JPEGImage(const RawImage& rawImage, unsigned quality) { @@ -37,7 +37,7 @@ namespace Image::GraphicsMagick catch (Magick::Exception& e) { LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what()); - throw ImageException {std::string {"Magick read error: "} + e.what()}; + throw Exception {std::string {"Magick read error: "} + e.what()}; } } diff --git a/src/libs/image/impl/graphicsmagick/JPEGImage.hpp b/src/libs/image/impl/graphicsmagick/JPEGImage.hpp index 9ead9f3c..cc2e6b37 100644 --- a/src/libs/image/impl/graphicsmagick/JPEGImage.hpp +++ b/src/libs/image/impl/graphicsmagick/JPEGImage.hpp @@ -27,7 +27,7 @@ #include "image/IEncodedImage.hpp" -namespace Image::GraphicsMagick +namespace lms::image::GraphicsMagick { class RawImage; class JPEGImage : public IEncodedImage diff --git a/src/libs/image/impl/graphicsmagick/RawImage.cpp b/src/libs/image/impl/graphicsmagick/RawImage.cpp index 0217c5db..e0f74ebd 100644 --- a/src/libs/image/impl/graphicsmagick/RawImage.cpp +++ b/src/libs/image/impl/graphicsmagick/RawImage.cpp @@ -23,9 +23,9 @@ #include "JPEGImage.hpp" #include "image/Exception.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" -namespace Image +namespace lms::image { std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize) { @@ -56,7 +56,7 @@ namespace Image } } -namespace Image::GraphicsMagick +namespace lms::image::GraphicsMagick { RawImage::RawImage(const std::byte* encodedData, std::size_t encodedDataSize) @@ -73,12 +73,12 @@ RawImage::RawImage(const std::byte* encodedData, std::size_t encodedDataSize) catch (Magick::Warning& e) { LMS_LOG(COVER, WARNING, "Caught Magick warning: " << e.what()); - throw ImageException {std::string {"Magick read warning: "} + e.what()}; + throw Exception {std::string {"Magick read warning: "} + e.what()}; } catch (Magick::Exception& e) { LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what()); - throw ImageException {std::string {"Magick read error: "} + e.what()}; + throw Exception {std::string {"Magick read error: "} + e.what()}; } } @@ -95,12 +95,12 @@ RawImage::RawImage(const std::filesystem::path& p) catch (Magick::Warning& e) { LMS_LOG(COVER, WARNING, "Caught Magick warning: " << e.what()); - throw ImageException {std::string {"Magick read warning: "} + e.what()}; + throw Exception {std::string {"Magick read warning: "} + e.what()}; } catch (Magick::Exception& e) { LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what()); - throw ImageException {std::string {"Magick read error: "} + e.what()}; + throw Exception {std::string {"Magick read error: "} + e.what()}; } } @@ -114,7 +114,7 @@ RawImage::resize(ImageSize width) catch (Magick::Exception& e) { LMS_LOG(COVER, ERROR, "Caught Magick exception while resizing: " << e.what()); - throw ImageException {std::string {"Magick resize error: "} + e.what()}; + throw Exception {std::string {"Magick resize error: "} + e.what()}; } } @@ -130,5 +130,5 @@ RawImage::getMagickImage() const return _image; } -} // namespace Image::GraphicsMagick +} // namespace lms::image::GraphicsMagick diff --git a/src/libs/image/impl/graphicsmagick/RawImage.hpp b/src/libs/image/impl/graphicsmagick/RawImage.hpp index d1c180ed..2055f285 100644 --- a/src/libs/image/impl/graphicsmagick/RawImage.hpp +++ b/src/libs/image/impl/graphicsmagick/RawImage.hpp @@ -31,7 +31,7 @@ #include "image/IEncodedImage.hpp" #include "image/IRawImage.hpp" -namespace Image::GraphicsMagick +namespace lms::image::GraphicsMagick { class RawImage : public IRawImage { diff --git a/src/libs/image/impl/stb/JPEGImage.cpp b/src/libs/image/impl/stb/JPEGImage.cpp index 280fcd6e..5ecad160 100644 --- a/src/libs/image/impl/stb/JPEGImage.cpp +++ b/src/libs/image/impl/stb/JPEGImage.cpp @@ -25,7 +25,7 @@ #include "image/Exception.hpp" #include "RawImage.hpp" -namespace Image::STB +namespace lms::image::STB { JPEGImage::JPEGImage(const RawImage& rawImage, unsigned quality) { @@ -40,7 +40,7 @@ namespace Image::STB if (stbi_write_jpg_to_func(writeCb, &_data, rawImage.getWidth(), rawImage.getHeight(), 3, rawImage.getData(), quality) == 0) { _data.clear(); - throw ImageException {"Failed to export in jpeg format!"}; + throw Exception {"Failed to export in jpeg format!"}; } } diff --git a/src/libs/image/impl/stb/JPEGImage.hpp b/src/libs/image/impl/stb/JPEGImage.hpp index ddce80f2..34394d17 100644 --- a/src/libs/image/impl/stb/JPEGImage.hpp +++ b/src/libs/image/impl/stb/JPEGImage.hpp @@ -23,7 +23,7 @@ #include "image/IEncodedImage.hpp" -namespace Image::STB +namespace lms::image::STB { class RawImage; class JPEGImage : public IEncodedImage diff --git a/src/libs/image/impl/stb/RawImage.cpp b/src/libs/image/impl/stb/RawImage.cpp index 1f8a5e68..d4344056 100644 --- a/src/libs/image/impl/stb/RawImage.cpp +++ b/src/libs/image/impl/stb/RawImage.cpp @@ -34,7 +34,7 @@ #include "image/Exception.hpp" -namespace Image +namespace lms::image { std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize) { @@ -51,14 +51,14 @@ namespace Image } } -namespace Image::STB +namespace lms::image::STB { RawImage::RawImage(const std::byte* encodedData, std::size_t encodedDataSize) { int n; _data = UniquePtrFree{ ::stbi_load_from_memory(reinterpret_cast(encodedData), encodedDataSize, &_width, &_height, &n, 3), std::free }; if (!_data) - throw ImageException{ "Cannot load image from memory: " + std::string{ ::stbi_failure_reason() } }; + throw Exception{ "Cannot load image from memory: " + std::string{ ::stbi_failure_reason() } }; } RawImage::RawImage(const std::filesystem::path& p) @@ -66,7 +66,7 @@ namespace Image::STB int n; _data = UniquePtrFree{ stbi_load(p.string().c_str(), &_width, &_height, &n, 3), std::free }; if (!_data) - throw ImageException{ "Cannot load image from file: " + std::string{ ::stbi_failure_reason() } }; + throw Exception{ "Cannot load image from file: " + std::string{ ::stbi_failure_reason() } }; } void RawImage::resize(ImageSize width) @@ -88,13 +88,13 @@ namespace Image::STB UniquePtrFree resizedData{ reinterpret_cast(malloc(width * height * 3)), std::free }; if (!resizedData) - throw ImageException{ "Cannot allocate memory for resized image!" }; + throw Exception{ "Cannot allocate memory for resized image!" }; if (::stbir_resize_uint8_srgb(reinterpret_cast(_data.get()), _width, _height, 0, reinterpret_cast(resizedData.get()), width, height, 0, 3, STBIR_ALPHA_CHANNEL_NONE, 0) == 0) { - throw ImageException{ "Failed to resize image:" + std::string{ ::stbi_failure_reason() } }; + throw Exception{ "Failed to resize image:" + std::string{ ::stbi_failure_reason() } }; } _data = std::move(resizedData); diff --git a/src/libs/image/impl/stb/RawImage.hpp b/src/libs/image/impl/stb/RawImage.hpp index 60452529..4bef16d5 100644 --- a/src/libs/image/impl/stb/RawImage.hpp +++ b/src/libs/image/impl/stb/RawImage.hpp @@ -29,7 +29,7 @@ #include "image/IEncodedImage.hpp" #include "image/IRawImage.hpp" -namespace Image::STB +namespace lms::image::STB { class RawImage : public IRawImage { diff --git a/src/libs/image/include/image/Exception.hpp b/src/libs/image/include/image/Exception.hpp index 53428934..1991be18 100644 --- a/src/libs/image/include/image/Exception.hpp +++ b/src/libs/image/include/image/Exception.hpp @@ -19,16 +19,15 @@ #pragma once -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -namespace Image +namespace lms::image { - // internal use only - class ImageException : public LmsException + class Exception : public core::LmsException { public: using LmsException::LmsException; }; -} // namespace Cover +} // namespace lms::cover diff --git a/src/libs/image/include/image/IEncodedImage.hpp b/src/libs/image/include/image/IEncodedImage.hpp index 96fc03a5..0613f214 100644 --- a/src/libs/image/include/image/IEncodedImage.hpp +++ b/src/libs/image/include/image/IEncodedImage.hpp @@ -22,7 +22,7 @@ #include #include -namespace Image +namespace lms::image { using ImageSize = std::size_t; @@ -35,6 +35,5 @@ namespace Image virtual std::size_t getDataSize() const = 0; virtual std::string_view getMimeType() const = 0; }; - -} // namespace Cover +} diff --git a/src/libs/image/include/image/IRawImage.hpp b/src/libs/image/include/image/IRawImage.hpp index 79d8587f..e0949b2c 100644 --- a/src/libs/image/include/image/IRawImage.hpp +++ b/src/libs/image/include/image/IRawImage.hpp @@ -24,7 +24,7 @@ #include "image/IEncodedImage.hpp" -namespace Image +namespace lms::image { class IRawImage { diff --git a/src/libs/metadata/CMakeLists.txt b/src/libs/metadata/CMakeLists.txt index 8a11a7cf..8afa4f57 100644 --- a/src/libs/metadata/CMakeLists.txt +++ b/src/libs/metadata/CMakeLists.txt @@ -24,7 +24,7 @@ target_link_libraries(lmsmetadata PRIVATE ) target_link_libraries(lmsmetadata PUBLIC - lmsutils + lmscore std::filesystem ) diff --git a/src/libs/metadata/impl/AvFormatTagReader.cpp b/src/libs/metadata/impl/AvFormatTagReader.cpp index 0a81fec3..8d487c94 100644 --- a/src/libs/metadata/impl/AvFormatTagReader.cpp +++ b/src/libs/metadata/impl/AvFormatTagReader.cpp @@ -24,15 +24,14 @@ #include "av/IAudioFile.hpp" #include "metadata/Exception.hpp" -#include "utils/ILogger.hpp" -#include "utils/String.hpp" +#include "core/ILogger.hpp" +#include "core/String.hpp" #include "Utils.hpp" -namespace MetaData +namespace lms::metadata { namespace { - // Mapping to internal avformat names and/or common alternative custom names static const std::unordered_map> tagMapping { @@ -144,19 +143,19 @@ namespace MetaData { try { - const auto audioFile{ Av::parseAudioFile(p) }; + const auto audioFile{ av::parseAudioFile(p) }; _containerInfo = audioFile->getContainerInfo(); _metaDataMap = audioFile->getMetaData(); _hasEmbeddedCover = audioFile->hasAttachedPictures(); - if (debug && Service::get()->isSeverityActive(Severity::DEBUG)) + if (debug && core::Service::get()->isSeverityActive(core::logging::Severity::DEBUG)) { for (const auto& [key, value] : _metaDataMap) LMS_LOG(METADATA, DEBUG, "Key = '" << key << "', value = '" << value << "'"); } } - catch (Av::Exception& e) + catch (av::Exception& e) { throw ParseException{}; } @@ -199,4 +198,4 @@ namespace MetaData visitor("", value); }); } -} // namespace MetaData +} // namespace lms::metadata diff --git a/src/libs/metadata/impl/AvFormatTagReader.hpp b/src/libs/metadata/impl/AvFormatTagReader.hpp index 7172a5a6..db7bddde 100644 --- a/src/libs/metadata/impl/AvFormatTagReader.hpp +++ b/src/libs/metadata/impl/AvFormatTagReader.hpp @@ -25,7 +25,7 @@ #include "metadata/IParser.hpp" #include "ITagReader.hpp" -namespace MetaData +namespace lms::metadata { class AvFormatTagReader : public ITagReader { @@ -46,9 +46,9 @@ namespace MetaData std::size_t getBitsPerSample() const override { return 0; } std::size_t getSampleRate() const override { return 0; } - Av::IAudioFile::MetadataMap _metaDataMap; - Av::ContainerInfo _containerInfo; + av::IAudioFile::MetadataMap _metaDataMap; + av::ContainerInfo _containerInfo; bool _hasEmbeddedCover{}; }; -} // namespace MetaData +} // namespace lms::metadata diff --git a/src/libs/metadata/impl/ITagReader.hpp b/src/libs/metadata/impl/ITagReader.hpp index 91b8e7b6..e426b691 100644 --- a/src/libs/metadata/impl/ITagReader.hpp +++ b/src/libs/metadata/impl/ITagReader.hpp @@ -22,7 +22,7 @@ #include #include -namespace MetaData +namespace lms::metadata { // using picard internal names // see https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html @@ -159,4 +159,4 @@ namespace MetaData virtual std::size_t getBitsPerSample() const = 0; virtual std::size_t getSampleRate() const = 0; }; -} // namespace MetaData +} // namespace lms::metadata diff --git a/src/libs/metadata/impl/Parser.cpp b/src/libs/metadata/impl/Parser.cpp index f9744434..6a0a30e3 100644 --- a/src/libs/metadata/impl/Parser.cpp +++ b/src/libs/metadata/impl/Parser.cpp @@ -22,14 +22,14 @@ #include #include "metadata/Exception.hpp" -#include "utils/ILogger.hpp" -#include "utils/String.hpp" +#include "core/ILogger.hpp" +#include "core/String.hpp" #include "AvFormatTagReader.hpp" #include "TagLibTagReader.hpp" #include "Utils.hpp" -namespace MetaData +namespace lms::metadata { namespace { @@ -39,7 +39,7 @@ namespace MetaData { auto visitTagIfNonEmpty{ [&](std::string_view tag) { - tag = StringUtils::stringTrim(tag); + tag = core::stringUtils::stringTrim(tag); if (!tag.empty()) visitor(tag); } }; @@ -48,7 +48,7 @@ namespace MetaData { if (value.find(tagDelimiter) != std::string_view::npos) { - for (std::string_view splitTag : StringUtils::splitString(value, tagDelimiter)) + for (std::string_view splitTag : core::stringUtils::splitString(value, tagDelimiter)) visitTagIfNonEmpty(splitTag); return; @@ -69,10 +69,10 @@ namespace MetaData { auto addTagIfNonEmpty{ [&res](std::string_view tag) { - tag = StringUtils::stringTrim(tag); + tag = core::stringUtils::stringTrim(tag); if (!tag.empty()) { - std::optional val{ StringUtils::readAs(tag) }; + std::optional val{ core::stringUtils::readAs(tag) }; if (val) res.emplace_back(std::move(*val)); } @@ -84,7 +84,7 @@ namespace MetaData { if (value.find(tagDelimiter) != std::string_view::npos) { - for (std::string_view splitTag : StringUtils::splitString(value, tagDelimiter)) + for (std::string_view splitTag : core::stringUtils::splitString(value, tagDelimiter)) addTagIfNonEmpty(splitTag); return; @@ -137,7 +137,7 @@ namespace MetaData return {}; std::vector artistSortNames{ getTagValuesFirstMatchAs(tagReader, artistSortTagNames, artistTagDelimiters) }; - std::vector artistMBIDs{ getTagValuesFirstMatchAs(tagReader, artistMBIDTagNames, artistTagDelimiters) }; + std::vector artistMBIDs{ getTagValuesFirstMatchAs(tagReader, artistMBIDTagNames, artistTagDelimiters) }; std::vector artists; artists.reserve(artistNames.size()); @@ -166,15 +166,15 @@ namespace MetaData if (role.empty()) { // "PERFORMER" "artist (role)" - Utils::PerformerArtist performer{ Utils::extractPerformerAndRole(name) }; - StringUtils::capitalize(performer.role); + utils::PerformerArtist performer{ utils::extractPerformerAndRole(name) }; + core::stringUtils::capitalize(performer.role); performers[performer.role].push_back(std::move(performer.artist)); } else { // "PERFORMER:role", "artist" (MP3) - std::string roleCapitalized{ StringUtils::stringToLower(role) }; - StringUtils::capitalize(roleCapitalized); + std::string roleCapitalized{ core::stringUtils::stringToLower(role) }; + core::stringUtils::capitalize(roleCapitalized); performers[roleCapitalized].push_back(Artist{ name }); } }); @@ -195,7 +195,7 @@ namespace MetaData switch (_parserBackend) { case ParserBackend::TagLib: - LMS_LOG(METADATA, INFO, "Using TagLib parser with read style = " << Utils::readStyleToString(readStyle)); + LMS_LOG(METADATA, INFO, "Using TagLib parser with read style = " << utils::readStyleToString(readStyle)); break; case ParserBackend::AvFormat: @@ -252,37 +252,37 @@ namespace MetaData track.hasCover = tagReader.hasEmbeddedCover(); track.title = getTagValueAs(tagReader, TagType::TrackTitle).value_or(""); - track.mbid = getTagValueAs(tagReader, TagType::MusicBrainzTrackID); - track.recordingMBID = getTagValueAs(tagReader, TagType::MusicBrainzRecordingID); - track.acoustID = getTagValueAs(tagReader, TagType::AcoustID); + track.mbid = getTagValueAs(tagReader, TagType::MusicBrainzTrackID); + track.recordingMBID = getTagValueAs(tagReader, TagType::MusicBrainzRecordingID); + track.acoustID = getTagValueAs(tagReader, TagType::AcoustID); track.position = getTagValueAs(tagReader, TagType::TrackNumber); // May parse 'Number/Total', that's fine if (auto dateStr = getTagValueAs(tagReader, TagType::Date)) { - if (const Wt::WDate date{ Utils::parseDate(*dateStr) }; date.isValid()) + if (const Wt::WDate date{ utils::parseDate(*dateStr) }; date.isValid()) { track.date = date; track.year = date.year(); } else { - track.year = Utils::parseYear(*dateStr); + track.year = utils::parseYear(*dateStr); } } if (auto dateStr = getTagValueAs(tagReader, TagType::OriginalReleaseDate)) { - if (const Wt::WDate date{ Utils::parseDate(*dateStr) }; date.isValid()) + if (const Wt::WDate date{ utils::parseDate(*dateStr) }; date.isValid()) { track.originalDate = date; track.originalYear = date.year(); } else { - track.originalYear = Utils::parseYear(*dateStr); + track.originalYear = utils::parseYear(*dateStr); } } if (auto dateStr = getTagValueAs(tagReader, TagType::OriginalReleaseYear)) { - track.originalYear = Utils::parseYear(*dateStr); + track.originalYear = utils::parseYear(*dateStr); } track.copyright = getTagValueAs(tagReader, TagType::Copyright).value_or(""); @@ -294,7 +294,7 @@ namespace MetaData { visitTagValues(tagReader, userExtraTag, _defaultTagDelimiters, [&](std::string_view value) { - value = StringUtils::stringTrim(value); + value = core::stringUtils::stringTrim(value); if (!value.empty()) track.userExtraTags[userExtraTag].push_back(std::string{ value }); }); @@ -342,9 +342,9 @@ namespace MetaData if (const auto value{ getTagValueAs(tagReader, TagType::TrackNumber) }) { // Expecting 'Number/Total' - const std::vector strings{ StringUtils::splitString(*value, '/') }; + const std::vector strings{ core::stringUtils::splitString(*value, '/') }; if (strings.size() == 2) - medium->trackCount = StringUtils::readAs(strings[1]); + medium->trackCount = core::stringUtils::readAs(strings[1]); } } // Expecting 'Number[/Total]' @@ -370,8 +370,8 @@ namespace MetaData release->name = std::move(*releaseName); release->sortName = getTagValueAs(tagReader, TagType::AlbumSortOrder).value_or(""); release->artistDisplayName = getTagValueAs(tagReader, TagType::AlbumArtist).value_or(""); // TODO try to join albumartists if present - release->mbid = getTagValueAs(tagReader, TagType::MusicBrainzReleaseID); - release->groupMBID = getTagValueAs(tagReader, TagType::MusicBrainzReleaseGroupID); + release->mbid = getTagValueAs(tagReader, TagType::MusicBrainzReleaseID); + release->groupMBID = getTagValueAs(tagReader, TagType::MusicBrainzReleaseGroupID); release->artists = getArtists(tagReader, { TagType::AlbumArtists, TagType::AlbumArtist }, { TagType::AlbumArtistsSortOrder, TagType::AlbumArtistSortOrder }, { TagType::MusicBrainzReleaseArtistID }, _artistTagDelimiters); release->mediumCount = getTagValueAs(tagReader, TagType::TotalDiscs); if (!release->mediumCount) @@ -380,9 +380,9 @@ namespace MetaData if (const auto value{ getTagValueAs(tagReader, TagType::DiscNumber) }) { // Expecting 'Number/Total' - const std::vector strings{ StringUtils::splitString(*value, '/') }; + const std::vector strings{ core::stringUtils::splitString(*value, '/') }; if (strings.size() == 2) - release->mediumCount = StringUtils::readAs(strings[1]); + release->mediumCount = core::stringUtils::readAs(strings[1]); } } @@ -390,4 +390,4 @@ namespace MetaData return release; } -} // namespace MetaData \ No newline at end of file +} // namespace lms::metadata \ No newline at end of file diff --git a/src/libs/metadata/impl/Parser.hpp b/src/libs/metadata/impl/Parser.hpp index 6979beba..ffbd89c1 100644 --- a/src/libs/metadata/impl/Parser.hpp +++ b/src/libs/metadata/impl/Parser.hpp @@ -22,7 +22,7 @@ #include "metadata/IParser.hpp" #include "ITagReader.hpp" -namespace MetaData +namespace lms::metadata { class Parser : public IParser { @@ -50,5 +50,5 @@ namespace MetaData std::vector _artistTagDelimiters; std::vector _defaultTagDelimiters; }; -} // namespace MetaData +} // namespace lms::metadata diff --git a/src/libs/metadata/impl/TagLibTagReader.cpp b/src/libs/metadata/impl/TagLibTagReader.cpp index f03e3321..7f26f601 100644 --- a/src/libs/metadata/impl/TagLibTagReader.cpp +++ b/src/libs/metadata/impl/TagLibTagReader.cpp @@ -34,11 +34,11 @@ #include #include "metadata/Exception.hpp" -#include "utils/ILogger.hpp" -#include "utils/ITraceLogger.hpp" -#include "utils/String.hpp" +#include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" +#include "core/String.hpp" -namespace MetaData +namespace lms::metadata { namespace { @@ -159,7 +159,7 @@ namespace MetaData case ParserReadStyle::Accurate: return TagLib::AudioProperties::ReadStyle::Accurate; } - throw LmsException{ "Cannot convert read style" }; + throw core::LmsException{ "Cannot convert read style" }; } void mergeTagMaps(TagLib::PropertyMap& dst, TagLib::PropertyMap&& src) @@ -223,7 +223,7 @@ namespace MetaData if (attributeList.isEmpty()) continue; - std::string strName{ StringUtils::stringToUpper(name.to8Bit(true)) }; + std::string strName{ core::stringUtils::stringToUpper(name.to8Bit(true)) }; if (strName.find("WM/") == 0 || _propertyMap.find(strName) != std::cend(_propertyMap)) continue; @@ -290,7 +290,7 @@ namespace MetaData _hasEmbeddedCover = true; } - if (debug && Service::get()->isSeverityActive(Severity::DEBUG)) + if (debug && core::Service::get()->isSeverityActive(core::logging::Severity::DEBUG)) { for (const auto& [key, values] : _propertyMap) { @@ -380,4 +380,4 @@ namespace MetaData { return static_cast(_file.audioProperties()->sampleRate()); } -} // namespace MetaData +} // namespace lms::metadata diff --git a/src/libs/metadata/impl/TagLibTagReader.hpp b/src/libs/metadata/impl/TagLibTagReader.hpp index a6db24e1..6aaa3476 100644 --- a/src/libs/metadata/impl/TagLibTagReader.hpp +++ b/src/libs/metadata/impl/TagLibTagReader.hpp @@ -27,7 +27,7 @@ #include "metadata/IParser.hpp" #include "ITagReader.hpp" -namespace MetaData +namespace lms::metadata { class TagLibTagReader : public ITagReader { @@ -52,4 +52,4 @@ namespace MetaData TagLib::PropertyMap _propertyMap; // case-insensitive keys bool _hasEmbeddedCover{}; }; -} // namespace MetaData +} // namespace lms::metadata diff --git a/src/libs/metadata/impl/Utils.cpp b/src/libs/metadata/impl/Utils.cpp index b8a0877b..1d9c5c70 100644 --- a/src/libs/metadata/impl/Utils.cpp +++ b/src/libs/metadata/impl/Utils.cpp @@ -23,9 +23,9 @@ #include #include -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -namespace MetaData::Utils +namespace lms::metadata::utils { Wt::WDate parseDate(std::string_view dateStr) { @@ -105,7 +105,7 @@ namespace MetaData::Utils case ParserReadStyle::Accurate: return "accurate"; } - throw LmsException{ "Unknown read style" }; + throw core::LmsException{ "Unknown read style" }; } PerformerArtist extractPerformerAndRole(std::string_view entry) @@ -138,8 +138,8 @@ namespace MetaData::Utils if (--count == 0) { roleBegin = currentIndex + 1; - role = StringUtils::stringTrim(entry.substr(roleBegin, roleEnd - roleBegin)); - artistName = StringUtils::stringTrim(entry.substr(0, currentIndex)); + role = core::stringUtils::stringTrim(entry.substr(roleBegin, roleEnd - roleBegin)); + artistName = core::stringUtils::stringTrim(entry.substr(0, currentIndex)); break; } } @@ -148,7 +148,7 @@ namespace MetaData::Utils } if (!roleEnd || !roleBegin) - artistName = StringUtils::stringTrim(entry); + artistName = core::stringUtils::stringTrim(entry); return PerformerArtist{ Artist {artistName}, std::string {role} }; } diff --git a/src/libs/metadata/impl/Utils.hpp b/src/libs/metadata/impl/Utils.hpp index d566cf7d..3e844da2 100644 --- a/src/libs/metadata/impl/Utils.hpp +++ b/src/libs/metadata/impl/Utils.hpp @@ -26,7 +26,7 @@ #include "metadata/IParser.hpp" -namespace MetaData::Utils +namespace lms::metadata::utils { Wt::WDate parseDate(std::string_view dateStr); std::optional parseYear(std::string_view yearStr); diff --git a/src/libs/metadata/include/metadata/Exception.hpp b/src/libs/metadata/include/metadata/Exception.hpp index 5559b18f..7f0c6ac0 100644 --- a/src/libs/metadata/include/metadata/Exception.hpp +++ b/src/libs/metadata/include/metadata/Exception.hpp @@ -19,11 +19,11 @@ #pragma once -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -namespace MetaData +namespace lms::metadata { - class Exception : public LmsException + class Exception : public core::LmsException { public: using LmsException::LmsException; diff --git a/src/libs/metadata/include/metadata/IParser.hpp b/src/libs/metadata/include/metadata/IParser.hpp index a5bcc29c..189ebda2 100644 --- a/src/libs/metadata/include/metadata/IParser.hpp +++ b/src/libs/metadata/include/metadata/IParser.hpp @@ -29,9 +29,9 @@ #include #include -#include "utils/UUID.hpp" +#include "core/UUID.hpp" -namespace MetaData +namespace lms::metadata { using Tags = std::map /* values */>; @@ -39,12 +39,12 @@ namespace MetaData struct Artist { - std::optional mbid; + std::optional mbid; std::string name; std::optional sortName; Artist(std::string_view _name) : name{ _name } {} - Artist(std::optional _mbid, std::string_view _name, std::optional _sortName) : mbid{ std::move(_mbid) }, name{ _name }, sortName{ std::move(_sortName) } {} + Artist(std::optional _mbid, std::string_view _name, std::optional _sortName) : mbid{ std::move(_mbid) }, name{ _name }, sortName{ std::move(_sortName) } {} bool operator<=>(const Artist&) const = default; }; @@ -53,8 +53,8 @@ namespace MetaData struct Release { - std::optional mbid; - std::optional groupMBID; + std::optional mbid; + std::optional groupMBID; std::string name; std::string sortName; std::string artistDisplayName; @@ -85,8 +85,8 @@ namespace MetaData struct Track { - std::optional mbid; - std::optional recordingMBID; + std::optional mbid; + std::optional recordingMBID; std::string title; std::optional medium; std::optional position; // in medium @@ -103,7 +103,7 @@ namespace MetaData std::optional originalYear{}; Wt::WDate originalDate; bool hasCover{}; - std::optional acoustID; + std::optional acoustID; std::string copyright; std::string copyrightURL; std::optional replayGain; @@ -143,4 +143,4 @@ namespace MetaData Accurate, }; std::unique_ptr createParser(ParserBackend parserBackend, ParserReadStyle parserReadStyle); -} // namespace MetaData +} diff --git a/src/libs/metadata/test/Parser.cpp b/src/libs/metadata/test/Parser.cpp index 919d9d59..45ddd48e 100644 --- a/src/libs/metadata/test/Parser.cpp +++ b/src/libs/metadata/test/Parser.cpp @@ -24,7 +24,7 @@ #include "TestTagReader.hpp" #include "Parser.hpp" -namespace MetaData +namespace lms::metadata { TEST(Parser, generalTest) { @@ -88,15 +88,15 @@ namespace MetaData std::unique_ptr track{ parser.parse(testTags) }; - EXPECT_EQ(track->acoustID, UUID::fromString("e987a441-e134-4960-8019-274eddacc418")); + EXPECT_EQ(track->acoustID, core::UUID::fromString("e987a441-e134-4960-8019-274eddacc418")); EXPECT_EQ(track->artistDisplayName, "MyArtist1 & MyArtist2"); ASSERT_EQ(track->artists.size(), 2); EXPECT_EQ(track->artists[0].name, "MyArtist1"); EXPECT_EQ(track->artists[0].sortName, "MyArtist1SortName"); - EXPECT_EQ(track->artists[0].mbid, UUID::fromString("9d2e0c8c-8c5e-4372-a061-590955eaeaae")); + EXPECT_EQ(track->artists[0].mbid, core::UUID::fromString("9d2e0c8c-8c5e-4372-a061-590955eaeaae")); EXPECT_EQ(track->artists[1].name, "MyArtist2"); EXPECT_EQ(track->artists[1].sortName, "MyArtist2SortName"); - EXPECT_EQ(track->artists[1].mbid, UUID::fromString("5e2cf87f-c8d7-4504-8a86-954dc0840229")); + EXPECT_EQ(track->artists[1].mbid, core::UUID::fromString("5e2cf87f-c8d7-4504-8a86-954dc0840229")); EXPECT_EQ(track->bitrate, TestTagReader::trackBitrate); ASSERT_EQ(track->composerArtists.size(), 2); EXPECT_EQ(track->composerArtists[0].name, "MyComposer1"); @@ -130,7 +130,7 @@ namespace MetaData EXPECT_EQ(track->lyricistArtists[0].name, "MyLyricist1"); EXPECT_EQ(track->lyricistArtists[1].name, "MyLyricist2"); ASSERT_TRUE(track->mbid.has_value()); - EXPECT_EQ(track->mbid.value(), UUID::fromString("0afb190a-6735-46df-a16d-199f48206e4a")); + EXPECT_EQ(track->mbid.value(), core::UUID::fromString("0afb190a-6735-46df-a16d-199f48206e4a")); ASSERT_EQ(track->mixerArtists.size(), 2); EXPECT_EQ(track->mixerArtists[0].name, "MyMixer1"); EXPECT_EQ(track->mixerArtists[1].name, "MyMixer2"); @@ -156,7 +156,7 @@ namespace MetaData EXPECT_EQ(track->producerArtists[0].name, "MyProducer1"); EXPECT_EQ(track->producerArtists[1].name, "MyProducer2"); ASSERT_TRUE(track->recordingMBID.has_value()); - EXPECT_EQ(track->recordingMBID.value(), UUID::fromString("bd3fc666-89de-4ac8-93f6-2dbf028ad8d5")); + EXPECT_EQ(track->recordingMBID.value(), core::UUID::fromString("bd3fc666-89de-4ac8-93f6-2dbf028ad8d5")); ASSERT_TRUE(track->replayGain.has_value()); EXPECT_FLOAT_EQ(track->replayGain.value(), -0.33); ASSERT_EQ(track->remixerArtists.size(), 2); @@ -189,13 +189,13 @@ namespace MetaData ASSERT_EQ(track->medium->release->artists.size(), 2); EXPECT_EQ(track->medium->release->artists[0].name, "MyAlbumArtist1"); EXPECT_EQ(track->medium->release->artists[0].sortName, "MyAlbumArtist1SortName"); - EXPECT_EQ(track->medium->release->artists[0].mbid, UUID::fromString("6fbf097c-1487-43e8-874b-50dd074398a7")); + EXPECT_EQ(track->medium->release->artists[0].mbid, core::UUID::fromString("6fbf097c-1487-43e8-874b-50dd074398a7")); EXPECT_EQ(track->medium->release->artists[1].name, "MyAlbumArtist2"); EXPECT_EQ(track->medium->release->artists[1].sortName, "MyAlbumArtist2SortName"); - EXPECT_EQ(track->medium->release->artists[1].mbid, UUID::fromString("5ed3d6b3-2aed-4a03-828c-3c4d4f7406e1")); + EXPECT_EQ(track->medium->release->artists[1].mbid, core::UUID::fromString("5ed3d6b3-2aed-4a03-828c-3c4d4f7406e1")); ASSERT_TRUE(track->medium->release->mbid.has_value()); - EXPECT_EQ(track->medium->release->mbid.value(), UUID::fromString("3fa39992-b786-4585-a70e-85d5cc15ef69")); - EXPECT_EQ(track->medium->release->groupMBID.value(), UUID::fromString("5b1a5a44-8420-4426-9b86-d25dc8d04838")); + EXPECT_EQ(track->medium->release->mbid.value(), core::UUID::fromString("3fa39992-b786-4585-a70e-85d5cc15ef69")); + EXPECT_EQ(track->medium->release->groupMBID.value(), core::UUID::fromString("5b1a5a44-8420-4426-9b86-d25dc8d04838")); EXPECT_EQ(track->medium->release->mediumCount, 3); EXPECT_EQ(track->medium->release->name, "MyAlbum"); EXPECT_EQ(track->medium->release->sortName, "MyAlbumSortName"); diff --git a/src/libs/metadata/test/TestTagReader.hpp b/src/libs/metadata/test/TestTagReader.hpp index 68686176..b7aca082 100644 --- a/src/libs/metadata/test/TestTagReader.hpp +++ b/src/libs/metadata/test/TestTagReader.hpp @@ -23,7 +23,7 @@ #include "Parser.hpp" -namespace MetaData +namespace lms::metadata { class TestTagReader : public ITagReader { diff --git a/src/libs/metadata/test/Utils.cpp b/src/libs/metadata/test/Utils.cpp index 71e4aa44..4d766c70 100644 --- a/src/libs/metadata/test/Utils.cpp +++ b/src/libs/metadata/test/Utils.cpp @@ -22,137 +22,134 @@ #include "Utils.hpp" -TEST(MetaData, parseDate) +namespace lms::metadata::utils::tests { - using namespace MetaData::Utils; - - struct TestCase + TEST(MetaData, parseDate) { - std::string str; - Wt::WDate result; - } testCases[] - { - { "1995-05-09", Wt::WDate {1995, 5, 9} }, - { "1995-01-01", Wt::WDate {1995, 1, 1} }, - { "1900-01-01", Wt::WDate {1900, 1, 1} }, - { "1899-01-01", Wt::WDate {1899, 1, 1} }, - { "1899-12-31", Wt::WDate {1899, 12, 31} }, - { "1899-11-30", Wt::WDate {1899, 11, 30} }, - { "1500-11-30", Wt::WDate {1500, 11, 30} }, - { "1000-11-30", Wt::WDate {1000, 11, 30} }, - { "1899-11-31", Wt::WDate {} }, // invalid day - { "1899-11-00", Wt::WDate {} }, // invalid day - { "1899-13-01", Wt::WDate {} }, // invalid month - { "1899-00-01", Wt::WDate {} }, // invalid month - { "1899-11", Wt::WDate {} }, // missing day - { "1899", Wt::WDate {} }, // missing month and days - { "1600", Wt::WDate {} }, // missing month and days - { "1995/05/09", Wt::WDate {1995, 5, 9} }, - { "1995/01/01", Wt::WDate {1995, 1, 1} }, - { "1900/01/01", Wt::WDate {1900, 1, 1} }, - { "1899/01/01", Wt::WDate {1899, 1, 1} }, - { "1899/12/31", Wt::WDate {1899, 12, 31} }, - { "1899/11/30", Wt::WDate {1899, 11, 30} }, - { "1500/11/30", Wt::WDate {1500, 11, 30} }, - { "1000/11/30", Wt::WDate {1000, 11, 30} }, - { "1899/11/31", Wt::WDate {} }, // invalid day - { "1899/11/00", Wt::WDate {} }, // invalid day - { "1899/13/01", Wt::WDate {} }, // invalid month - { "1899/00/01", Wt::WDate {} }, // invalid month - { "1899/11", Wt::WDate {} }, // missing day - { "1899", Wt::WDate {} }, // missing month and days - { "1600", Wt::WDate {} }, // missing month and days - { "1995/05-09", Wt::WDate {} }, // invalid mixup separators - { "1995-05/09", Wt::WDate {} }, // invalid mixup separators - }; + struct TestCase + { + std::string str; + Wt::WDate result; + } testCases[] + { + { "1995-05-09", Wt::WDate {1995, 5, 9} }, + { "1995-01-01", Wt::WDate {1995, 1, 1} }, + { "1900-01-01", Wt::WDate {1900, 1, 1} }, + { "1899-01-01", Wt::WDate {1899, 1, 1} }, + { "1899-12-31", Wt::WDate {1899, 12, 31} }, + { "1899-11-30", Wt::WDate {1899, 11, 30} }, + { "1500-11-30", Wt::WDate {1500, 11, 30} }, + { "1000-11-30", Wt::WDate {1000, 11, 30} }, + { "1899-11-31", Wt::WDate {} }, // invalid day + { "1899-11-00", Wt::WDate {} }, // invalid day + { "1899-13-01", Wt::WDate {} }, // invalid month + { "1899-00-01", Wt::WDate {} }, // invalid month + { "1899-11", Wt::WDate {} }, // missing day + { "1899", Wt::WDate {} }, // missing month and days + { "1600", Wt::WDate {} }, // missing month and days + { "1995/05/09", Wt::WDate {1995, 5, 9} }, + { "1995/01/01", Wt::WDate {1995, 1, 1} }, + { "1900/01/01", Wt::WDate {1900, 1, 1} }, + { "1899/01/01", Wt::WDate {1899, 1, 1} }, + { "1899/12/31", Wt::WDate {1899, 12, 31} }, + { "1899/11/30", Wt::WDate {1899, 11, 30} }, + { "1500/11/30", Wt::WDate {1500, 11, 30} }, + { "1000/11/30", Wt::WDate {1000, 11, 30} }, + { "1899/11/31", Wt::WDate {} }, // invalid day + { "1899/11/00", Wt::WDate {} }, // invalid day + { "1899/13/01", Wt::WDate {} }, // invalid month + { "1899/00/01", Wt::WDate {} }, // invalid month + { "1899/11", Wt::WDate {} }, // missing day + { "1899", Wt::WDate {} }, // missing month and days + { "1600", Wt::WDate {} }, // missing month and days + { "1995/05-09", Wt::WDate {} }, // invalid mixup separators + { "1995-05/09", Wt::WDate {} }, // invalid mixup separators + }; - for (const TestCase& testCase : testCases) - { - const Wt::WDate parsed{ parseDate(testCase.str) }; + for (const TestCase& testCase : testCases) + { + const Wt::WDate parsed{ parseDate(testCase.str) }; - EXPECT_EQ(parsed.year(), testCase.result.year()) << " str was '" << testCase.str << "'"; - EXPECT_EQ(parsed.month(), testCase.result.month()) << " str was '" << testCase.str << "'"; - EXPECT_EQ(parsed.day(), testCase.result.day()) << " str was '" << testCase.str << "'"; + EXPECT_EQ(parsed.year(), testCase.result.year()) << " str was '" << testCase.str << "'"; + EXPECT_EQ(parsed.month(), testCase.result.month()) << " str was '" << testCase.str << "'"; + EXPECT_EQ(parsed.day(), testCase.result.day()) << " str was '" << testCase.str << "'"; + } } -} -TEST(MetaData, parseYear) -{ - using namespace MetaData::Utils; + TEST(MetaData, parseYear) + { + struct TestCase + { + std::string str; + std::optional result; + } testCases[] + { + { "1995-05-09", 1995 }, + { "1995", 1995 }, + { "-0", 0 }, + { "0", 0 }, + { "00", 0 }, + { "05", 5 }, + { "050", 50 }, + { "00005", 0 }, + { "-50", -50 }, + { "-", std::nullopt }, + { "", std::nullopt }, + { "a", std::nullopt }, + { "1a", 1 }, + { "12a", 12 }, + { "123a", 123 }, + { "1234a", 1234 }, + { "19951123", 1995 }, + { "199511", 1995 }, + }; - struct TestCase - { - std::string str; - std::optional result; - } testCases[] - { - { "1995-05-09", 1995 }, - { "1995", 1995 }, - { "-0", 0 }, - { "0", 0 }, - { "00", 0 }, - { "05", 5 }, - { "050", 50 }, - { "00005", 0 }, - { "-50", -50 }, - { "-", std::nullopt }, - { "", std::nullopt }, - { "a", std::nullopt }, - { "1a", 1 }, - { "12a", 12 }, - { "123a", 123 }, - { "1234a", 1234 }, - { "19951123", 1995 }, - { "199511", 1995 }, - }; - - for (const TestCase& testCase : testCases) - { - const std::optional parsed{ parseYear(testCase.str) }; - EXPECT_EQ(parsed, testCase.result) << " str was '" << testCase.str << "'"; + for (const TestCase& testCase : testCases) + { + const std::optional parsed{ parseYear(testCase.str) }; + EXPECT_EQ(parsed, testCase.result) << " str was '" << testCase.str << "'"; + } } -} -TEST(MetaData, extractPerformerAndRole) -{ - using namespace MetaData::Utils; - - struct TestCase + TEST(MetaData, extractPerformerAndRole) { - std::string str; - std::string expectedArtistName; - std::string expectedRole; - } testCases[] - { - { "", "", "" }, - { "(myrole)", "", "myrole" }, - { "(my role)", "", "my role" }, - { " ( my role ) ", "", "my role" }, - { " (()) ", "", "()" }, - { ")", ")", "" }, - { "(", "(", "" }, - { "artist name (my role)", "artist name", "my role" }, - { "artist name ()", "artist name", "" }, - { "artist name ( )", "artist name", "" }, - { "artist (subname) name", "artist (subname) name", "" }, - { " artist name ( my role )", "artist name", "my role" }, - { "artist name (artist subname) (my role)", "artist name (artist subname)", "my role" }, - { "artist name", "artist name", "" }, - { " artist name ", "artist name", "" }, - { "artist name (", "artist name (", "" }, - { "artist name )", "artist name )", "" }, - { "artist name (()", "artist name (", "" }, - { "artist name (())", "artist name", "()" }, - { "artist name ( () )", "artist name", "()" }, - { "artist name (drums (drum set))", "artist name", "drums (drum set)" }, - { "artist name ( drums (drum set) )", "artist name", "drums (drum set)" }, - }; + struct TestCase + { + std::string str; + std::string expectedArtistName; + std::string expectedRole; + } testCases[] + { + { "", "", "" }, + { "(myrole)", "", "myrole" }, + { "(my role)", "", "my role" }, + { " ( my role ) ", "", "my role" }, + { " (()) ", "", "()" }, + { ")", ")", "" }, + { "(", "(", "" }, + { "artist name (my role)", "artist name", "my role" }, + { "artist name ()", "artist name", "" }, + { "artist name ( )", "artist name", "" }, + { "artist (subname) name", "artist (subname) name", "" }, + { " artist name ( my role )", "artist name", "my role" }, + { "artist name (artist subname) (my role)", "artist name (artist subname)", "my role" }, + { "artist name", "artist name", "" }, + { " artist name ", "artist name", "" }, + { "artist name (", "artist name (", "" }, + { "artist name )", "artist name )", "" }, + { "artist name (()", "artist name (", "" }, + { "artist name (())", "artist name", "()" }, + { "artist name ( () )", "artist name", "()" }, + { "artist name (drums (drum set))", "artist name", "drums (drum set)" }, + { "artist name ( drums (drum set) )", "artist name", "drums (drum set)" }, + }; - for (const TestCase& testCase : testCases) - { - PerformerArtist performer{ extractPerformerAndRole(testCase.str) }; + for (const TestCase& testCase : testCases) + { + PerformerArtist performer{ extractPerformerAndRole(testCase.str) }; - EXPECT_EQ(performer.artist.name, testCase.expectedArtistName) << " str was '" << testCase.str << "'"; - EXPECT_EQ(performer.role, testCase.expectedRole) << " str was '" << testCase.str << "'"; + EXPECT_EQ(performer.artist.name, testCase.expectedArtistName) << " str was '" << testCase.str << "'"; + EXPECT_EQ(performer.role, testCase.expectedRole) << " str was '" << testCase.str << "'"; + } } -} +} \ No newline at end of file diff --git a/src/libs/services/auth/CMakeLists.txt b/src/libs/services/auth/CMakeLists.txt index 0a2eb6a5..3ebcdb22 100644 --- a/src/libs/services/auth/CMakeLists.txt +++ b/src/libs/services/auth/CMakeLists.txt @@ -19,7 +19,7 @@ target_include_directories(lmsauth PRIVATE ) target_link_libraries(lmsauth PRIVATE - lmsutils + lmscore lmsdatabase ) diff --git a/src/libs/services/auth/impl/AuthServiceBase.cpp b/src/libs/services/auth/impl/AuthServiceBase.cpp index 7119d4ac..9d72e2dd 100644 --- a/src/libs/services/auth/impl/AuthServiceBase.cpp +++ b/src/libs/services/auth/impl/AuthServiceBase.cpp @@ -23,11 +23,11 @@ #include "database/Db.hpp" #include "database/Session.hpp" #include "database/User.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" -namespace Auth +namespace lms::auth { - using namespace Database; + using namespace db; AuthServiceBase::AuthServiceBase(Db& db) : _db{ db } diff --git a/src/libs/services/auth/impl/AuthServiceBase.hpp b/src/libs/services/auth/impl/AuthServiceBase.hpp index 4c0d2ae6..918b4317 100644 --- a/src/libs/services/auth/impl/AuthServiceBase.hpp +++ b/src/libs/services/auth/impl/AuthServiceBase.hpp @@ -22,25 +22,25 @@ #include #include "database/UserId.hpp" -namespace Database +namespace lms::db { class Db; class Session; } -namespace Auth +namespace lms::auth { class AuthServiceBase { protected: - AuthServiceBase(Database::Db& db); + AuthServiceBase(db::Db& db); - Database::UserId getOrCreateUser(std::string_view loginName); - void onUserAuthenticated(Database::UserId userId); + db::UserId getOrCreateUser(std::string_view loginName); + void onUserAuthenticated(db::UserId userId); - Database::Session& getDbSession(); + db::Session& getDbSession(); private: - Database::Db& _db; + db::Db& _db; }; } diff --git a/src/libs/services/auth/impl/AuthTokenService.cpp b/src/libs/services/auth/impl/AuthTokenService.cpp index 86575837..38c5fce0 100644 --- a/src/libs/services/auth/impl/AuthTokenService.cpp +++ b/src/libs/services/auth/impl/AuthTokenService.cpp @@ -27,45 +27,45 @@ #include "database/AuthToken.hpp" #include "database/Session.hpp" #include "database/User.hpp" -#include "utils/Exception.hpp" -#include "utils/ILogger.hpp" +#include "core/Exception.hpp" +#include "core/ILogger.hpp" -namespace Auth +namespace lms::auth { - std::unique_ptr createAuthTokenService(Database::Db& db, std::size_t maxThrottlerEntries) + std::unique_ptr createAuthTokenService(db::Db& db, std::size_t maxThrottlerEntries) { return std::make_unique(db, maxThrottlerEntries); } static const Wt::Auth::SHA1HashFunction sha1Function; - AuthTokenService::AuthTokenService(Database::Db& db, std::size_t maxThrottlerEntries) + AuthTokenService::AuthTokenService(db::Db& db, std::size_t maxThrottlerEntries) : AuthServiceBase {db} , _loginThrottler {maxThrottlerEntries} { } std::string - AuthTokenService::createAuthToken(Database::UserId userId, const Wt::WDateTime& expiry) + AuthTokenService::createAuthToken(db::UserId userId, const Wt::WDateTime& expiry) { const std::string secret {Wt::WRandom::generateId(32)}; const std::string secretHash {sha1Function.compute(secret, {})}; - Database::Session& session {getDbSession()}; + db::Session& session {getDbSession()}; auto transaction {session.createWriteTransaction()}; - Database::User::pointer user {Database::User::find(session, userId)}; + db::User::pointer user {db::User::find(session, userId)}; if (!user) throw Exception {"User deleted"}; - Database::AuthToken::pointer authToken {session.create(secretHash, expiry, user)}; + db::AuthToken::pointer authToken {session.create(secretHash, expiry, user)}; LMS_LOG(UI, DEBUG, "Created auth token for user '" << user->getLoginName() << "', expiry = " << expiry.toString()); if (user->getAuthTokensCount() >= 50) - Database::AuthToken::removeExpiredTokens(session, Wt::WDateTime::currentDateTime()); + db::AuthToken::removeExpiredTokens(session, Wt::WDateTime::currentDateTime()); return secret; } @@ -75,10 +75,10 @@ namespace Auth { const std::string secretHash {sha1Function.compute(std::string {secret}, {})}; - Database::Session& session {getDbSession()}; + db::Session& session {getDbSession()}; auto transaction {session.createWriteTransaction()}; - Database::AuthToken::pointer authToken {Database::AuthToken::find(session, secretHash)}; + db::AuthToken::pointer authToken {db::AuthToken::find(session, secretHash)}; if (!authToken) return std::nullopt; @@ -127,17 +127,17 @@ namespace Auth } void - AuthTokenService::clearAuthTokens(Database::UserId userId) + AuthTokenService::clearAuthTokens(db::UserId userId) { - Database::Session& session {getDbSession()}; + db::Session& session {getDbSession()}; auto transaction {session.createWriteTransaction()}; - Database::User::pointer user {Database::User::find(session, userId)}; + db::User::pointer user {db::User::find(session, userId)}; if (!user) throw Exception {"User deleted"}; user.modify()->clearAuthTokens(); } -} // namespace Auth +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/AuthTokenService.hpp b/src/libs/services/auth/impl/AuthTokenService.hpp index e95a8a79..18234c24 100644 --- a/src/libs/services/auth/impl/AuthTokenService.hpp +++ b/src/libs/services/auth/impl/AuthTokenService.hpp @@ -25,17 +25,17 @@ #include "AuthServiceBase.hpp" #include "LoginThrottler.hpp" -namespace Database +namespace lms::db { class Session; } -namespace Auth +namespace lms::auth { class AuthTokenService : public IAuthTokenService, public AuthServiceBase { public: - AuthTokenService(Database::Db& db, std::size_t maxThrottlerEntries); + AuthTokenService(db::Db& db, std::size_t maxThrottlerEntries); AuthTokenService(const AuthTokenService&) = delete; AuthTokenService& operator=(const AuthTokenService&) = delete; @@ -44,8 +44,8 @@ namespace Auth private: AuthTokenProcessResult processAuthToken(const boost::asio::ip::address& clientAddress, std::string_view tokenValue) override; - std::string createAuthToken(Database::UserId userId, const Wt::WDateTime& expiry) override; - void clearAuthTokens(Database::UserId userId) override; + std::string createAuthToken(db::UserId userId, const Wt::WDateTime& expiry) override; + void clearAuthTokens(db::UserId userId) override; std::optional processAuthToken(std::string_view secret); diff --git a/src/libs/services/auth/impl/EnvService.cpp b/src/libs/services/auth/impl/EnvService.cpp index 26f972a6..f0b32c60 100644 --- a/src/libs/services/auth/impl/EnvService.cpp +++ b/src/libs/services/auth/impl/EnvService.cpp @@ -22,10 +22,10 @@ #include "services/auth/Types.hpp" #include "http-headers/HttpHeadersEnvService.hpp" -namespace Auth +namespace lms::auth { std::unique_ptr - createEnvService(std::string_view backendName, Database::Db& db) + createEnvService(std::string_view backendName, db::Db& db) { if (backendName == "http-headers") return std::make_unique(db); diff --git a/src/libs/services/auth/impl/LoginThrottler.cpp b/src/libs/services/auth/impl/LoginThrottler.cpp index 5d1fceea..be3e5be8 100644 --- a/src/libs/services/auth/impl/LoginThrottler.cpp +++ b/src/libs/services/auth/impl/LoginThrottler.cpp @@ -17,104 +17,97 @@ * along with LMS. If not, see . */ -/* This file contains some classes in order to get info from file using the libavconv */ + /* This file contains some classes in order to get info from file using the libavconv */ #include "LoginThrottler.hpp" -#include "utils/ILogger.hpp" -#include "utils/Random.hpp" +#include "core/ILogger.hpp" +#include "core/Random.hpp" -namespace Auth { - -static -boost::asio::ip::address_v6 -getAddressWithMask(const boost::asio::ip::address_v6& address, std::size_t prefix) +namespace lms::auth { - assert(prefix % 8 == 0); + namespace + { + boost::asio::ip::address_v6 getAddressWithMask(const boost::asio::ip::address_v6& address, std::size_t prefix) + { + assert(prefix % 8 == 0); - std::array truncatedBytes; + std::array truncatedBytes; - auto bytes {address.to_bytes()}; - std::copy(std::cbegin(bytes), std::next(std::cbegin(bytes), prefix / 8), truncatedBytes.begin()); + auto bytes{ address.to_bytes() }; + std::copy(std::cbegin(bytes), std::next(std::cbegin(bytes), prefix / 8), truncatedBytes.begin()); - return boost::asio::ip::address_v6 {truncatedBytes}; -} + return boost::asio::ip::address_v6{ truncatedBytes }; + } -static -boost::asio::ip::address -getAddressToThrottle(const boost::asio::ip::address& address) -{ - return address.is_v6() ? getAddressWithMask(address.to_v6(), 64) : address; -} + boost::asio::ip::address getAddressToThrottle(const boost::asio::ip::address& address) + { + return address.is_v6() ? getAddressWithMask(address.to_v6(), 64) : address; + } + } -void -LoginThrottler::removeOutdatedEntries() -{ - const Wt::WDateTime now {Wt::WDateTime::currentDateTime()}; + void LoginThrottler::removeOutdatedEntries() + { + const Wt::WDateTime now{ Wt::WDateTime::currentDateTime() }; - for (auto it {std::begin(_attemptsInfo)}; it != std::end(_attemptsInfo); ) - { - if (it->second.nextAttempt <= now) - it = _attemptsInfo.erase(it); - else - ++it; - } -} + for (auto it{ std::begin(_attemptsInfo) }; it != std::end(_attemptsInfo); ) + { + if (it->second.nextAttempt <= now) + it = _attemptsInfo.erase(it); + else + ++it; + } + } -void -LoginThrottler::onBadClientAttempt(const boost::asio::ip::address& address) -{ - const boost::asio::ip::address clientAddress {getAddressToThrottle(address)}; - const Wt::WDateTime now {Wt::WDateTime::currentDateTime()}; + void LoginThrottler::onBadClientAttempt(const boost::asio::ip::address& address) + { + const boost::asio::ip::address clientAddress{ getAddressToThrottle(address) }; + const Wt::WDateTime now{ Wt::WDateTime::currentDateTime() }; - if (_attemptsInfo.size() >= _maxEntries) - removeOutdatedEntries(); - if (_attemptsInfo.size() >= _maxEntries) - _attemptsInfo.erase(Random::pickRandom(_attemptsInfo)); + if (_attemptsInfo.size() >= _maxEntries) + removeOutdatedEntries(); + if (_attemptsInfo.size() >= _maxEntries) + _attemptsInfo.erase(core::random::pickRandom(_attemptsInfo)); - AttemptInfo& attemptInfo {_attemptsInfo[address]}; - if (attemptInfo.nextAttempt.isValid()) - { - assert(attemptInfo.nextAttempt <= now); // should not be called if throttled - attemptInfo = {}; - } + AttemptInfo& attemptInfo{ _attemptsInfo[address] }; + if (attemptInfo.nextAttempt.isValid()) + { + assert(attemptInfo.nextAttempt <= now); // should not be called if throttled + attemptInfo = {}; + } - attemptInfo.badConsecutiveAttemptCount += 1; + attemptInfo.badConsecutiveAttemptCount += 1; - LMS_LOG(AUTH, DEBUG, "Registering bad attempt for '" << clientAddress.to_string() << "', consecutive bad attempts count = " << attemptInfo.badConsecutiveAttemptCount); - if (attemptInfo.badConsecutiveAttemptCount >= _maxBadConsecutiveAttemptCount) - { - LMS_LOG(AUTH, DEBUG, "Throttling '" << clientAddress.to_string() << "'"); - attemptInfo.nextAttempt = now.addMSecs(std::chrono::duration_cast(_throttlingDuration).count()); - } - else - { - attemptInfo.nextAttempt = {}; - } -} + LMS_LOG(AUTH, DEBUG, "Registering bad attempt for '" << clientAddress.to_string() << "', consecutive bad attempts count = " << attemptInfo.badConsecutiveAttemptCount); + if (attemptInfo.badConsecutiveAttemptCount >= _maxBadConsecutiveAttemptCount) + { + LMS_LOG(AUTH, DEBUG, "Throttling '" << clientAddress.to_string() << "'"); + attemptInfo.nextAttempt = now.addMSecs(std::chrono::duration_cast(_throttlingDuration).count()); + } + else + { + attemptInfo.nextAttempt = {}; + } + } -void -LoginThrottler::onGoodClientAttempt(const boost::asio::ip::address& address) -{ - const boost::asio::ip::address clientAddress {getAddressToThrottle(address)}; + void LoginThrottler::onGoodClientAttempt(const boost::asio::ip::address& address) + { + const boost::asio::ip::address clientAddress{ getAddressToThrottle(address) }; - _attemptsInfo.erase(clientAddress); -} + _attemptsInfo.erase(clientAddress); + } -bool -LoginThrottler::isClientThrottled(const boost::asio::ip::address& address) const -{ - const boost::asio::ip::address clientAddress {getAddressToThrottle(address)}; + bool LoginThrottler::isClientThrottled(const boost::asio::ip::address& address) const + { + const boost::asio::ip::address clientAddress{ getAddressToThrottle(address) }; - auto it {_attemptsInfo.find(clientAddress)}; - if (it == _attemptsInfo.end()) - return false; + auto it{ _attemptsInfo.find(clientAddress) }; + if (it == _attemptsInfo.end()) + return false; - if (!it->second.nextAttempt.isValid()) - return false; - - return it->second.nextAttempt > Wt::WDateTime::currentDateTime(); -} - -} // Auth + if (!it->second.nextAttempt.isValid()) + return false; + return it->second.nextAttempt > Wt::WDateTime::currentDateTime(); + } +} \ No newline at end of file diff --git a/src/libs/services/auth/impl/LoginThrottler.hpp b/src/libs/services/auth/impl/LoginThrottler.hpp index 3d775b54..035d092d 100644 --- a/src/libs/services/auth/impl/LoginThrottler.hpp +++ b/src/libs/services/auth/impl/LoginThrottler.hpp @@ -24,10 +24,10 @@ #include -#include "utils/NetAddress.hpp" -#include "utils/Exception.hpp" +#include "core/NetAddress.hpp" +#include "core/Exception.hpp" -namespace Auth +namespace lms::auth { class LoginThrottler { diff --git a/src/libs/services/auth/impl/PasswordServiceBase.cpp b/src/libs/services/auth/impl/PasswordServiceBase.cpp index da81b6f1..5290b78f 100644 --- a/src/libs/services/auth/impl/PasswordServiceBase.cpp +++ b/src/libs/services/auth/impl/PasswordServiceBase.cpp @@ -30,16 +30,15 @@ #include "services/auth/Types.hpp" #include "database/Session.hpp" #include "database/User.hpp" -#include "utils/Exception.hpp" -#include "utils/ILogger.hpp" +#include "core/Exception.hpp" +#include "core/ILogger.hpp" -namespace Auth +namespace lms::auth { - static const Wt::Auth::SHA1HashFunction sha1Function; std::unique_ptr - createPasswordService(std::string_view passwordAuthenticationBackend, Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService) + createPasswordService(std::string_view passwordAuthenticationBackend, db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService) { if (passwordAuthenticationBackend == "internal") return std::make_unique(db, maxThrottlerEntries, authTokenService); @@ -51,7 +50,7 @@ namespace Auth throw Exception {"Authentication backend '" + std::string {passwordAuthenticationBackend} + "' is not supported!"}; } - PasswordServiceBase::PasswordServiceBase(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService) + PasswordServiceBase::PasswordServiceBase(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService) : AuthServiceBase {db} , _loginThrottler {maxThrottlerEntries} , _authTokenService {authTokenService} @@ -82,7 +81,7 @@ namespace Auth { _loginThrottler.onGoodClientAttempt(clientAddress); - const Database::UserId userId {getOrCreateUser(loginName)}; + const db::UserId userId {getOrCreateUser(loginName)}; onUserAuthenticated(userId); return {CheckResult::State::Granted, userId}; } @@ -93,5 +92,5 @@ namespace Auth } } } -} // namespace Auth +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/PasswordServiceBase.hpp b/src/libs/services/auth/impl/PasswordServiceBase.hpp index 9f9575ec..da431e84 100644 --- a/src/libs/services/auth/impl/PasswordServiceBase.hpp +++ b/src/libs/services/auth/impl/PasswordServiceBase.hpp @@ -25,19 +25,18 @@ #include "AuthServiceBase.hpp" #include "LoginThrottler.hpp" -namespace Database +namespace lms::db { class Db; class Session; } -namespace Auth +namespace lms::auth { - class PasswordServiceBase : public IPasswordService, public AuthServiceBase { public: - PasswordServiceBase(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService); + PasswordServiceBase(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService); PasswordServiceBase(const PasswordServiceBase&) = delete; PasswordServiceBase& operator=(const PasswordServiceBase&) = delete; @@ -58,5 +57,4 @@ namespace Auth LoginThrottler _loginThrottler; IAuthTokenService& _authTokenService; }; - -} // namespace Auth +} diff --git a/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.cpp b/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.cpp index de975251..ae0c7b85 100644 --- a/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.cpp +++ b/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.cpp @@ -21,15 +21,15 @@ #include -#include "utils/IConfig.hpp" -#include "utils/ILogger.hpp" -#include "utils/Service.hpp" +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/Service.hpp" -namespace Auth +namespace lms::auth { - HttpHeadersEnvService::HttpHeadersEnvService(Database::Db& db) + HttpHeadersEnvService::HttpHeadersEnvService(db::Db& db) : AuthServiceBase{ db } - , _fieldName{ Service::get()->getString("http-headers-login-field", "X-Forwarded-User") } + , _fieldName{ core::Service::get()->getString("http-headers-login-field", "X-Forwarded-User") } { LMS_LOG(AUTH, INFO, "Using http header field = '" << _fieldName << "'"); } @@ -42,7 +42,7 @@ namespace Auth LMS_LOG(AUTH, DEBUG, "Extracted login name = '" << loginName << "' from HTTP header"); - const Database::UserId userId{ getOrCreateUser(loginName) }; + const db::UserId userId{ getOrCreateUser(loginName) }; onUserAuthenticated(userId); return { CheckResult::State::Granted, userId }; } @@ -55,8 +55,8 @@ namespace Auth LMS_LOG(AUTH, DEBUG, "Extracted login name = '" << loginName << "' from HTTP header"); - const Database::UserId userId{ getOrCreateUser(loginName) }; + const db::UserId userId{ getOrCreateUser(loginName) }; onUserAuthenticated(userId); return { CheckResult::State::Granted, userId }; } -} // namespace Auth +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp b/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp index 02e4f94c..86cf48d0 100644 --- a/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp +++ b/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp @@ -22,12 +22,12 @@ #include "services/auth/IEnvService.hpp" #include "AuthServiceBase.hpp" -namespace Auth +namespace lms::auth { class HttpHeadersEnvService : public IEnvService, public AuthServiceBase { public: - HttpHeadersEnvService(Database::Db& db); + HttpHeadersEnvService(db::Db& db); private: CheckResult processEnv(const Wt::WEnvironment& env) override; @@ -36,5 +36,5 @@ namespace Auth std::string _fieldName; }; -} // namespace Auth +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/internal/InternalPasswordService.cpp b/src/libs/services/auth/impl/internal/InternalPasswordService.cpp index 4304aae2..9aecde91 100644 --- a/src/libs/services/auth/impl/internal/InternalPasswordService.cpp +++ b/src/libs/services/auth/impl/internal/InternalPasswordService.cpp @@ -24,12 +24,12 @@ #include "services/auth/Types.hpp" #include "database/Session.hpp" #include "database/User.hpp" -#include "utils/Exception.hpp" -#include "utils/ILogger.hpp" +#include "core/Exception.hpp" +#include "core/ILogger.hpp" -namespace Auth +namespace lms::auth { - InternalPasswordService::InternalPasswordService(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService) + InternalPasswordService::InternalPasswordService(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService) : PasswordServiceBase{ db, maxThrottlerEntries, authTokenService } { _validator.setMinimumLength(Wt::Auth::PasswordStrengthType::OneCharClass, 4); @@ -45,12 +45,12 @@ namespace Auth { LMS_LOG(AUTH, DEBUG, "Checking internal password for user '" << loginName << "'"); - Database::User::PasswordHash passwordHash; + db::User::PasswordHash passwordHash; { - Database::Session& session{ getDbSession() }; + db::Session& session{ getDbSession() }; auto transaction{ session.createReadTransaction() }; - const Database::User::pointer user{ Database::User::find(session, loginName) }; + const db::User::pointer user{ db::User::find(session, loginName) }; if (!user) { LMS_LOG(AUTH, DEBUG, "hashing random stuff"); @@ -81,24 +81,24 @@ namespace Auth { switch (context.userType) { - case Database::UserType::ADMIN: - case Database::UserType::REGULAR: + case db::UserType::ADMIN: + case db::UserType::REGULAR: return _validator.evaluateStrength(std::string{ password }, context.loginName, "").isValid() ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::TooWeak; - case Database::UserType::DEMO: + case db::UserType::DEMO: return password == context.loginName ? PasswordAcceptabilityResult::OK : PasswordAcceptabilityResult::MustMatchLoginName; } throw NotImplementedException{}; } - void InternalPasswordService::setPassword(Database::UserId userId, std::string_view newPassword) + void InternalPasswordService::setPassword(db::UserId userId, std::string_view newPassword) { - const Database::User::PasswordHash passwordHash{ hashPassword(newPassword) }; + const db::User::PasswordHash passwordHash{ hashPassword(newPassword) }; - Database::Session& session{ getDbSession() }; + db::Session& session{ getDbSession() }; auto transaction{ session.createWriteTransaction() }; - Database::User::pointer user{ Database::User::find(session, userId) }; + db::User::pointer user{ db::User::find(session, userId) }; if (!user) throw Exception{ "User not found!" }; @@ -116,7 +116,7 @@ namespace Auth getAuthTokenService().clearAuthTokens(userId); } - Database::User::PasswordHash InternalPasswordService::hashPassword(std::string_view password) const + db::User::PasswordHash InternalPasswordService::hashPassword(std::string_view password) const { const std::string salt{ Wt::WRandom::generateId(32) }; @@ -129,5 +129,5 @@ namespace Auth hashPassword(Wt::WRandom::generateId(32)); } -} // namespace Auth +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/internal/InternalPasswordService.hpp b/src/libs/services/auth/impl/internal/InternalPasswordService.hpp index 3cd9e2e4..c913d756 100644 --- a/src/libs/services/auth/impl/internal/InternalPasswordService.hpp +++ b/src/libs/services/auth/impl/internal/InternalPasswordService.hpp @@ -26,23 +26,23 @@ #include "PasswordServiceBase.hpp" #include "LoginThrottler.hpp" -namespace Auth +namespace lms::auth { class IAuthTokenService; class InternalPasswordService : public PasswordServiceBase { public: - InternalPasswordService(Database::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService); + InternalPasswordService(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService); private: bool checkUserPassword(std::string_view loginName, std::string_view password) override; bool canSetPasswords() const override; PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override; - void setPassword(Database::UserId userId, std::string_view newPassword) override; + void setPassword(db::UserId userId, std::string_view newPassword) override; - Database::User::PasswordHash hashPassword(std::string_view password) const; + db::User::PasswordHash hashPassword(std::string_view password) const; void hashRandomPassword() const; const Wt::Auth::BCryptHashFunction _hashFunc{ 7 }; // TODO parametrize this diff --git a/src/libs/services/auth/impl/pam/PAMPasswordService.cpp b/src/libs/services/auth/impl/pam/PAMPasswordService.cpp index 2f889b78..3d7d6be4 100644 --- a/src/libs/services/auth/impl/pam/PAMPasswordService.cpp +++ b/src/libs/services/auth/impl/pam/PAMPasswordService.cpp @@ -28,9 +28,9 @@ #include "services/auth/Types.hpp" #include "database/Session.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" -namespace Auth +namespace lms::auth { namespace { @@ -191,10 +191,10 @@ namespace Auth throw NotImplementedException{}; } - void PAMPasswordService::setPassword(Database::UserId, std::string_view) + void PAMPasswordService::setPassword(db::UserId, std::string_view) { throw NotImplementedException{}; } -} // namespace Auth +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/pam/PAMPasswordService.hpp b/src/libs/services/auth/impl/pam/PAMPasswordService.hpp index 2de5bc82..b2c2b80e 100644 --- a/src/libs/services/auth/impl/pam/PAMPasswordService.hpp +++ b/src/libs/services/auth/impl/pam/PAMPasswordService.hpp @@ -23,7 +23,7 @@ #include "PasswordServiceBase.hpp" -namespace Auth +namespace lms::auth { class PAMPasswordService: public PasswordServiceBase { @@ -34,6 +34,6 @@ namespace Auth bool checkUserPassword(std::string_view loginName,std::string_view password) override; bool canSetPasswords() const override; PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override; - void setPassword(Database::UserId userId, std::string_view newPassword) override; + void setPassword(db::UserId userId, std::string_view newPassword) override; }; } diff --git a/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp b/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp index 12f98593..65941cde 100644 --- a/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp +++ b/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp @@ -29,13 +29,13 @@ #include "database/UserId.hpp" -namespace Database +namespace lms::db { class Db; class User; } -namespace Auth +namespace lms::auth { class IAuthTokenService { @@ -54,7 +54,7 @@ namespace Auth struct AuthTokenInfo { - Database::UserId userId; + db::UserId userId; Wt::WDateTime expiry; }; @@ -66,9 +66,9 @@ namespace Auth virtual AuthTokenProcessResult processAuthToken(const boost::asio::ip::address& clientAddress, std::string_view tokenValue) = 0; // Returns a one time token - virtual std::string createAuthToken(Database::UserId userid, const Wt::WDateTime& expiry) = 0; - virtual void clearAuthTokens(Database::UserId userid) = 0; + virtual std::string createAuthToken(db::UserId userid, const Wt::WDateTime& expiry) = 0; + virtual void clearAuthTokens(db::UserId userid) = 0; }; - std::unique_ptr createAuthTokenService(Database::Db& db, std::size_t maxThrottlerEntryCount); + std::unique_ptr createAuthTokenService(db::Db& db, std::size_t maxThrottlerEntryCount); } diff --git a/src/libs/services/auth/include/services/auth/IEnvService.hpp b/src/libs/services/auth/include/services/auth/IEnvService.hpp index cb1a05ab..d35b6cef 100644 --- a/src/libs/services/auth/include/services/auth/IEnvService.hpp +++ b/src/libs/services/auth/include/services/auth/IEnvService.hpp @@ -24,7 +24,7 @@ #include "database/UserId.hpp" -namespace Database +namespace lms::db { class Db; class Session; @@ -40,7 +40,7 @@ namespace Wt::Http class Request; } -namespace Auth +namespace lms::auth { class IEnvService { @@ -58,12 +58,12 @@ namespace Auth }; State state {State::Denied}; - std::optional userId {}; + std::optional userId {}; }; virtual CheckResult processEnv(const Wt::WEnvironment& env) = 0; virtual CheckResult processRequest(const Wt::Http::Request& request) = 0; }; - std::unique_ptr createEnvService(std::string_view backendName, Database::Db& db); -} // namespace Auth + std::unique_ptr createEnvService(std::string_view backendName, db::Db& db); +} // namespace lms::auth diff --git a/src/libs/services/auth/include/services/auth/IPasswordService.hpp b/src/libs/services/auth/include/services/auth/IPasswordService.hpp index 0b5046e4..d92c9386 100644 --- a/src/libs/services/auth/include/services/auth/IPasswordService.hpp +++ b/src/libs/services/auth/include/services/auth/IPasswordService.hpp @@ -29,13 +29,13 @@ #include "services/auth/Types.hpp" #include "database/UserId.hpp" -namespace Database +namespace lms::db { class Db; class User; } -namespace Auth +namespace lms::auth { class IAuthTokenService; @@ -54,7 +54,7 @@ namespace Auth Throttled, }; State state {State::Denied}; - std::optional userId {}; + std::optional userId {}; std::optional expiry {}; }; virtual CheckResult checkUserPassword(const boost::asio::ip::address& clientAddress, @@ -70,9 +70,9 @@ namespace Auth MustMatchLoginName, }; virtual PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view password, const PasswordValidationContext& context) const = 0; - virtual void setPassword(Database::UserId userId, std::string_view newPassword) = 0; + virtual void setPassword(db::UserId userId, std::string_view newPassword) = 0; }; - std::unique_ptr createPasswordService(std::string_view authPasswordBackend, Database::Db& db, std::size_t maxThrottlerEntryCount, IAuthTokenService& authTokenService); + std::unique_ptr createPasswordService(std::string_view authPasswordBackend, db::Db& db, std::size_t maxThrottlerEntryCount, IAuthTokenService& authTokenService); } diff --git a/src/libs/services/auth/include/services/auth/Types.hpp b/src/libs/services/auth/include/services/auth/Types.hpp index 8b97df6e..7d4b67aa 100644 --- a/src/libs/services/auth/include/services/auth/Types.hpp +++ b/src/libs/services/auth/include/services/auth/Types.hpp @@ -21,31 +21,31 @@ #include #include "database/Types.hpp" -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -namespace Auth +namespace lms::auth { - class Exception : public ::LmsException + class Exception : public core::LmsException { - using LmsException::LmsException; + using core::LmsException::LmsException; }; class NotImplementedException : public Exception { public: - NotImplementedException() : Auth::Exception {"Not implemented"} {} + NotImplementedException() : Exception {"Not implemented"} {} }; class UserNotFoundException : public Exception { public: - UserNotFoundException() : Auth::Exception {"User not found"} {} + UserNotFoundException() : Exception {"User not found"} {} }; struct PasswordValidationContext { std::string loginName; - Database::UserType userType; + db::UserType userType; }; class PasswordException : public Exception diff --git a/src/libs/services/cover/CMakeLists.txt b/src/libs/services/cover/CMakeLists.txt index 41169c5d..1d9d65ef 100644 --- a/src/libs/services/cover/CMakeLists.txt +++ b/src/libs/services/cover/CMakeLists.txt @@ -20,7 +20,7 @@ target_link_libraries(lmsservice-cover PRIVATE target_link_libraries(lmsservice-cover PUBLIC lmsdatabase lmsimage - lmsutils + lmscore std::filesystem ) diff --git a/src/libs/services/cover/impl/CoverService.cpp b/src/libs/services/cover/impl/CoverService.cpp index 1a941a29..49540e48 100644 --- a/src/libs/services/cover/impl/CoverService.cpp +++ b/src/libs/services/cover/impl/CoverService.cpp @@ -31,14 +31,14 @@ #include "image/Exception.hpp" #include "image/IRawImage.hpp" -#include "utils/IConfig.hpp" -#include "utils/ILogger.hpp" -#include "utils/Path.hpp" -#include "utils/Random.hpp" -#include "utils/String.hpp" -#include "utils/Utils.hpp" +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/Path.hpp" +#include "core/Random.hpp" +#include "core/String.hpp" +#include "core/Utils.hpp" -namespace Cover +namespace lms::cover { namespace { @@ -47,16 +47,16 @@ namespace Cover bool hasCover{}; bool isMultiDisc{}; std::filesystem::path trackPath; - std::optional releaseId; + std::optional releaseId; }; - std::optional getTrackInfo(Database::Session& dbSession, Database::TrackId trackId) + std::optional getTrackInfo(db::Session& dbSession, db::TrackId trackId) { std::optional res; auto transaction{ dbSession.createReadTransaction() }; - const Database::Track::pointer track{ Database::Track::find(dbSession, trackId) }; + const db::Track::pointer track{ db::Track::find(dbSession, trackId) }; if (!track) return res; @@ -65,7 +65,7 @@ namespace Cover res->hasCover = track->hasCover(); res->trackPath = track->getPath(); - if (const Database::Release::pointer & release{ track->getRelease() }) + if (const db::Release::pointer & release{ track->getRelease() }) { res->releaseId = release->getId(); if (release->getTotalDisc() > 1) @@ -79,7 +79,7 @@ namespace Cover { std::vector res; - Service::get()->visitStrings("cover-preferred-file-names", + core::Service::get()->visitStrings("cover-preferred-file-names", [&res](std::string_view fileName) { res.emplace_back(fileName); @@ -92,7 +92,7 @@ namespace Cover { std::vector res; - Service::get()->visitStrings("artist-image-file-names", + core::Service::get()->visitStrings("artist-image-file-names", [&res](std::string_view fileName) { res.emplace_back(fileName); @@ -107,29 +107,29 @@ namespace Cover } } - std::unique_ptr createCoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath) + std::unique_ptr createCoverService(db::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath) { return std::make_unique(db, execPath, defaultCoverPath); } - using namespace Image; + using namespace image; - CoverService::CoverService(Database::Db& db, + CoverService::CoverService(db::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath) : _db{ db } , _defaultCoverPath{ defaultCoverPath } - , _maxCacheSize{ Service::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 } - , _maxFileSize{ Service::get()->getULong("cover-max-file-size", 10) * 1000 * 1000 } + , _maxCacheSize{ core::Service::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 } + , _maxFileSize{ core::Service::get()->getULong("cover-max-file-size", 10) * 1000 * 1000 } , _preferredFileNames{ constructPreferredFileNames() } , _artistFileNames{ constructArtistFileNames() } { - setJpegQuality(Service::get()->getULong("cover-jpeg-quality", 75)); + setJpegQuality(core::Service::get()->getULong("cover-jpeg-quality", 75)); LMS_LOG(COVER, INFO, "Default cover path = '" << _defaultCoverPath.string() << "'"); LMS_LOG(COVER, INFO, "Max cache size = " << _maxCacheSize); LMS_LOG(COVER, INFO, "Max file size = " << _maxFileSize); - LMS_LOG(COVER, INFO, "Preferred file names: " << StringUtils::joinStrings(_preferredFileNames, ",")); + LMS_LOG(COVER, INFO, "Preferred file names: " << core::stringUtils::joinStrings(_preferredFileNames, ",")); #if LMS_SUPPORT_IMAGE_GM GraphicsMagick::init(execPath); @@ -141,17 +141,17 @@ namespace Cover { getDefault(512); } - catch (const Image::ImageException& e) + catch (const image::Exception& e) { - throw LmsException("Cannot read default cover file '" + _defaultCoverPath.string() + "': " + e.what()); + throw core::LmsException("Cannot read default cover file '" + _defaultCoverPath.string() + "': " + e.what()); } } - std::unique_ptr CoverService::getFromAvMediaFile(const Av::IAudioFile& input, ImageSize width) const + std::unique_ptr CoverService::getFromAvMediaFile(const av::IAudioFile& input, ImageSize width) const { std::unique_ptr image; - input.visitAttachedPictures([&](const Av::Picture& picture) + input.visitAttachedPictures([&](const av::Picture& picture) { if (image) return; @@ -162,7 +162,7 @@ namespace Cover rawImage->resize(width); image = rawImage->encodeToJPEG(_jpegQuality); } - catch (const Image::ImageException& e) + catch (const image::Exception& e) { LMS_LOG(COVER, ERROR, "Cannot read embedded cover: " << e.what()); } @@ -181,7 +181,7 @@ namespace Cover rawImage->resize(width); image = rawImage->encodeToJPEG(_jpegQuality); } - catch (const ImageException& e) + catch (const image::Exception& e) { LMS_LOG(COVER, ERROR, "Cannot read cover in file '" << p.string() << "': " << e.what()); } @@ -320,9 +320,9 @@ namespace Cover try { - image = getFromAvMediaFile(*Av::parseAudioFile(p), width); + image = getFromAvMediaFile(*av::parseAudioFile(p), width); } - catch (Av::Exception& e) + catch (av::Exception& e) { LMS_LOG(COVER, ERROR, "Cannot get covers from track " << p.string() << ": " << e.what()); } @@ -330,14 +330,14 @@ namespace Cover return image; } - std::shared_ptr CoverService::getFromTrack(Database::TrackId trackId, ImageSize width) + std::shared_ptr CoverService::getFromTrack(db::TrackId trackId, ImageSize width) { return getFromTrack(_db.getTLSSession(), trackId, width, true /* allow release fallback*/); } - std::shared_ptr CoverService::getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width, bool allowReleaseFallback) + std::shared_ptr CoverService::getFromTrack(db::Session& dbSession, db::TrackId trackId, ImageSize width, bool allowReleaseFallback) { - using namespace Database; + using namespace db; const CacheEntryDesc cacheEntryDesc{ trackId, width }; @@ -369,9 +369,9 @@ namespace Cover return cover; } - std::shared_ptr CoverService::getFromRelease(Database::ReleaseId releaseId, ImageSize width) + std::shared_ptr CoverService::getFromRelease(db::ReleaseId releaseId, ImageSize width) { - using namespace Database; + using namespace db; const CacheEntryDesc cacheEntryDesc{ releaseId, width }; std::shared_ptr cover{ loadFromCache(cacheEntryDesc) }; @@ -418,9 +418,9 @@ namespace Cover return cover; } - std::shared_ptr CoverService::getFromArtist(Database::ArtistId artistId, ImageSize width) + std::shared_ptr CoverService::getFromArtist(db::ArtistId artistId, ImageSize width) { - using namespace Database; + using namespace db; const CacheEntryDesc cacheEntryDesc{ artistId, width }; std::shared_ptr artistImage{ loadFromCache(cacheEntryDesc) }; @@ -478,7 +478,7 @@ namespace Cover // /artist.jpg if (!releasePaths.empty()) { - const std::filesystem::path artistPath{ releasePaths.size() == 1 ? releasePaths.begin()->parent_path() : PathUtils::getLongestCommonPath(std::cbegin(releasePaths), std::cend(releasePaths)) }; + const std::filesystem::path artistPath{ releasePaths.size() == 1 ? releasePaths.begin()->parent_path() : core::pathUtils::getLongestCommonPath(std::cbegin(releasePaths), std::cend(releasePaths)) }; artistImage = getFromDirectory(artistPath, width, artistFileNamesWithGenericNames, false); } @@ -531,7 +531,7 @@ namespace Cover void CoverService::setJpegQuality(unsigned quality) { - _jpegQuality = Utils::clamp(quality, 1, 100); + _jpegQuality = core::utils::clamp(quality, 1, 100); LMS_LOG(COVER, INFO, "JPEG export quality = " << _jpegQuality); } @@ -542,7 +542,7 @@ namespace Cover while (_cacheSize + image->getDataSize() > _maxCacheSize && !_cache.empty()) { - auto itRandom{ Random::pickRandom(_cache) }; + auto itRandom{ core::random::pickRandom(_cache) }; _cacheSize -= itRandom->second->getDataSize(); _cache.erase(itRandom); } @@ -566,5 +566,5 @@ namespace Cover return it->second; } -} // namespace Cover +} // namespace lms::cover diff --git a/src/libs/services/cover/impl/CoverService.hpp b/src/libs/services/cover/impl/CoverService.hpp index d5755411..30f24bad 100644 --- a/src/libs/services/cover/impl/CoverService.hpp +++ b/src/libs/services/cover/impl/CoverService.hpp @@ -33,21 +33,21 @@ #include "image/IEncodedImage.hpp" #include "database/Types.hpp" -namespace Database +namespace lms::db { class Session; } -namespace Av +namespace lms::av { class IAudioFile; } -namespace Cover +namespace lms::cover { struct CacheEntryDesc { - std::variant id; + std::variant id; std::size_t size; bool operator==(const CacheEntryDesc& other) const @@ -61,64 +61,64 @@ namespace Cover namespace std { template<> - class hash + class hash { public: - size_t operator()(const Cover::CacheEntryDesc& e) const + size_t operator()(const lms::cover::CacheEntryDesc& e) const { size_t h{}; std::visit([&](auto id) { using IdType = std::decay_t; - h ^= std::hash()(id); + h ^= std::hash{}(id); }, e.id); - h ^= std::hash()(e.size) << 1; + h ^= std::hash{}(e.size) << 1; return h; } }; } // ns std -namespace Cover +namespace lms::cover { class CoverService : public ICoverService { public: - CoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath); + CoverService(db::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath); CoverService(const CoverService&) = delete; CoverService& operator=(const CoverService&) = delete; private: - std::shared_ptr getFromTrack(Database::TrackId trackId, Image::ImageSize width) override; - std::shared_ptr getFromRelease(Database::ReleaseId releaseId, Image::ImageSize width) override; - std::shared_ptr getFromArtist(Database::ArtistId artistId, Image::ImageSize width) override; - std::shared_ptr getDefault(Image::ImageSize width) override; + std::shared_ptr getFromTrack(db::TrackId trackId, image::ImageSize width) override; + std::shared_ptr getFromRelease(db::ReleaseId releaseId, image::ImageSize width) override; + std::shared_ptr getFromArtist(db::ArtistId artistId, image::ImageSize width) override; + std::shared_ptr getDefault(image::ImageSize width) override; void flushCache() override; void setJpegQuality(unsigned quality) override; - std::shared_ptr getFromTrack(Database::Session& dbSession, Database::TrackId trackId, Image::ImageSize width, bool allowReleaseFallback); - std::unique_ptr getFromAvMediaFile(const Av::IAudioFile& input, Image::ImageSize width) const; - std::unique_ptr getFromCoverFile(const std::filesystem::path& p, Image::ImageSize width) const; + std::shared_ptr getFromTrack(db::Session& dbSession, db::TrackId trackId, image::ImageSize width, bool allowReleaseFallback); + std::unique_ptr getFromAvMediaFile(const av::IAudioFile& input, image::ImageSize width) const; + std::unique_ptr getFromCoverFile(const std::filesystem::path& p, image::ImageSize width) const; - std::unique_ptr getFromTrack(const std::filesystem::path& path, Image::ImageSize width) const; + std::unique_ptr getFromTrack(const std::filesystem::path& path, image::ImageSize width) const; std::multimap getCoverPaths(const std::filesystem::path& directoryPath) const; - std::unique_ptr getFromDirectory(const std::filesystem::path& directory, Image::ImageSize width, const std::vector& preferredFileNames, bool allowPickRandom) const; - std::unique_ptr getFromSameNamedFile(const std::filesystem::path& filePath, Image::ImageSize width) const; + std::unique_ptr getFromDirectory(const std::filesystem::path& directory, image::ImageSize width, const std::vector& preferredFileNames, bool allowPickRandom) const; + std::unique_ptr getFromSameNamedFile(const std::filesystem::path& filePath, image::ImageSize width) const; bool checkCoverFile(const std::filesystem::path& directoryPath) const; - Database::Db& _db; + db::Db& _db; std::shared_mutex _cacheMutex; - std::unordered_map> _cache; - std::unordered_map> _defaultCoverCache; + std::unordered_map> _cache; + std::unordered_map> _defaultCoverCache; std::atomic _cacheMisses{}; std::atomic _cacheHits{}; std::size_t _cacheSize{}; - void saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr image); - std::shared_ptr loadFromCache(const CacheEntryDesc& entryDesc); + void saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr image); + std::shared_ptr loadFromCache(const CacheEntryDesc& entryDesc); const std::filesystem::path _defaultCoverPath; const std::size_t _maxCacheSize; @@ -129,5 +129,5 @@ namespace Cover unsigned _jpegQuality; }; -} // namespace Cover +} // namespace lms::cover diff --git a/src/libs/services/cover/include/services/cover/ICoverService.hpp b/src/libs/services/cover/include/services/cover/ICoverService.hpp index 4269087e..4825cc2d 100644 --- a/src/libs/services/cover/include/services/cover/ICoverService.hpp +++ b/src/libs/services/cover/include/services/cover/ICoverService.hpp @@ -27,30 +27,30 @@ #include "database/TrackId.hpp" #include "image/IEncodedImage.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Cover +namespace lms::cover { class ICoverService { public: virtual ~ICoverService() = default; - virtual std::shared_ptr getFromTrack(Database::TrackId trackId, Image::ImageSize width) = 0; - virtual std::shared_ptr getFromRelease(Database::ReleaseId releaseId, Image::ImageSize width) = 0; - virtual std::shared_ptr getFromArtist(Database::ArtistId artistId, Image::ImageSize width) = 0; + virtual std::shared_ptr getFromTrack(db::TrackId trackId, image::ImageSize width) = 0; + virtual std::shared_ptr getFromRelease(db::ReleaseId releaseId, image::ImageSize width) = 0; + virtual std::shared_ptr getFromArtist(db::ArtistId artistId, image::ImageSize width) = 0; - virtual std::shared_ptr getDefault(Image::ImageSize width) = 0; + virtual std::shared_ptr getDefault(image::ImageSize width) = 0; virtual void flushCache() = 0; virtual void setJpegQuality(unsigned quality) = 0; // from 1 to 100 }; - std::unique_ptr createCoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath); + std::unique_ptr createCoverService(db::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath); -} // namespace CoverArt +} // namespace lms::coverArt diff --git a/src/libs/services/feedback/CMakeLists.txt b/src/libs/services/feedback/CMakeLists.txt index 02e63624..03deaa21 100644 --- a/src/libs/services/feedback/CMakeLists.txt +++ b/src/libs/services/feedback/CMakeLists.txt @@ -19,7 +19,7 @@ target_include_directories(lmsfeedback PRIVATE ) target_link_libraries(lmsfeedback PRIVATE - lmsutils + lmscore ) target_link_libraries(lmsfeedback PUBLIC diff --git a/src/libs/services/feedback/impl/FeedbackService.cpp b/src/libs/services/feedback/impl/FeedbackService.cpp index b972743a..5826ead9 100644 --- a/src/libs/services/feedback/impl/FeedbackService.cpp +++ b/src/libs/services/feedback/impl/FeedbackService.cpp @@ -29,12 +29,12 @@ #include "database/StarredTrack.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "internal/InternalBackend.hpp" #include "listenbrainz/ListenBrainzBackend.hpp" -namespace Feedback +namespace lms::feedback { std::unique_ptr createFeedbackService(boost::asio::io_context& ioContext, Db& db) { @@ -45,8 +45,8 @@ namespace Feedback : _db{ db } { LMS_LOG(SCROBBLING, INFO, "Starting service..."); - _backends.emplace(Database::FeedbackBackend::Internal, std::make_unique(_db)); - _backends.emplace(Database::FeedbackBackend::ListenBrainz, std::make_unique(ioContext, _db)); + _backends.emplace(db::FeedbackBackend::Internal, std::make_unique(_db)); + _backends.emplace(db::FeedbackBackend::ListenBrainz, std::make_unique(ioContext, _db)); LMS_LOG(SCROBBLING, INFO, "Service started!"); } @@ -55,9 +55,9 @@ namespace Feedback LMS_LOG(SCROBBLING, INFO, "Service stopped!"); } - std::optional FeedbackService::getUserFeedbackBackend(UserId userId) + std::optional FeedbackService::getUserFeedbackBackend(UserId userId) { - std::optional feedbackBackend; + std::optional feedbackBackend; Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; diff --git a/src/libs/services/feedback/impl/FeedbackService.hpp b/src/libs/services/feedback/impl/FeedbackService.hpp index 3b28d469..893d517d 100644 --- a/src/libs/services/feedback/impl/FeedbackService.hpp +++ b/src/libs/services/feedback/impl/FeedbackService.hpp @@ -25,54 +25,54 @@ #include "services/feedback/IFeedbackService.hpp" #include "IFeedbackBackend.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Feedback +namespace lms::feedback { class FeedbackService : public IFeedbackService { public: - FeedbackService(boost::asio::io_context& ioContext, Database::Db& db); + FeedbackService(boost::asio::io_context& ioContext, db::Db& db); ~FeedbackService(); private: FeedbackService(const FeedbackService&) = delete; FeedbackService& operator=(const FeedbackService&) = delete; - void star(Database::UserId userId, Database::ArtistId artistId) override; - void unstar(Database::UserId userId, Database::ArtistId artistId) override; - bool isStarred(Database::UserId userId, Database::ArtistId artistId) override; - Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ArtistId artistId) override; + void star(db::UserId userId, db::ArtistId artistId) override; + void unstar(db::UserId userId, db::ArtistId artistId) override; + bool isStarred(db::UserId userId, db::ArtistId artistId) override; + Wt::WDateTime getStarredDateTime(db::UserId userId, db::ArtistId artistId) override; ArtistContainer findStarredArtists(const ArtistFindParameters& params) override; - void star(Database::UserId userId, Database::ReleaseId releaseId) override; - void unstar(Database::UserId userId, Database::ReleaseId releaseId) override; - bool isStarred(Database::UserId userId, Database::ReleaseId releasedId) override; - Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ReleaseId releasedId) override; + void star(db::UserId userId, db::ReleaseId releaseId) override; + void unstar(db::UserId userId, db::ReleaseId releaseId) override; + bool isStarred(db::UserId userId, db::ReleaseId releasedId) override; + Wt::WDateTime getStarredDateTime(db::UserId userId, db::ReleaseId releasedId) override; ReleaseContainer findStarredReleases(const FindParameters& params) override; - void star(Database::UserId userId, Database::TrackId trackId) override; - void unstar(Database::UserId userId, Database::TrackId trackId) override; - bool isStarred(Database::UserId userId, Database::TrackId trackId) override; - Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::TrackId trackId) override; + void star(db::UserId userId, db::TrackId trackId) override; + void unstar(db::UserId userId, db::TrackId trackId) override; + bool isStarred(db::UserId userId, db::TrackId trackId) override; + Wt::WDateTime getStarredDateTime(db::UserId userId, db::TrackId trackId) override; TrackContainer findStarredTracks(const FindParameters& params) override; - std::optional getUserFeedbackBackend(Database::UserId userId); + std::optional getUserFeedbackBackend(db::UserId userId); template - void star(Database::UserId userId, ObjIdType id); + void star(db::UserId userId, ObjIdType id); template - void unstar(Database::UserId userId, ObjIdType id); + void unstar(db::UserId userId, ObjIdType id); template - bool isStarred(Database::UserId userId, ObjIdType id); + bool isStarred(db::UserId userId, ObjIdType id); template - Wt::WDateTime getStarredDateTime(Database::UserId userId, ObjIdType id); + Wt::WDateTime getStarredDateTime(db::UserId userId, ObjIdType id); - Database::Db& _db; - std::unordered_map> _backends; + db::Db& _db; + std::unordered_map> _backends; }; } // ns Feedback diff --git a/src/libs/services/feedback/impl/FeedbackService.impl.hpp b/src/libs/services/feedback/impl/FeedbackService.impl.hpp index 96afbe94..bf8f6537 100644 --- a/src/libs/services/feedback/impl/FeedbackService.impl.hpp +++ b/src/libs/services/feedback/impl/FeedbackService.impl.hpp @@ -23,9 +23,9 @@ #include "database/Session.hpp" #include "database/User.hpp" -namespace Feedback +namespace lms::feedback { - using namespace Database; + using namespace db; template void FeedbackService::star(UserId userId, ObjIdType objId) diff --git a/src/libs/services/feedback/impl/IFeedbackBackend.hpp b/src/libs/services/feedback/impl/IFeedbackBackend.hpp index ffb6bf4b..0c0490db 100644 --- a/src/libs/services/feedback/impl/IFeedbackBackend.hpp +++ b/src/libs/services/feedback/impl/IFeedbackBackend.hpp @@ -23,19 +23,19 @@ #include "database/StarredReleaseId.hpp" #include "database/StarredTrackId.hpp" -namespace Feedback +namespace lms::feedback { class IFeedbackBackend { public: virtual ~IFeedbackBackend() = default; - virtual void onStarred(Database::StarredArtistId) = 0; - virtual void onUnstarred(Database::StarredArtistId) = 0; - virtual void onStarred(Database::StarredReleaseId) = 0; - virtual void onUnstarred(Database::StarredReleaseId) = 0; - virtual void onStarred(Database::StarredTrackId) = 0; - virtual void onUnstarred(Database::StarredTrackId) = 0; + virtual void onStarred(db::StarredArtistId) = 0; + virtual void onUnstarred(db::StarredArtistId) = 0; + virtual void onStarred(db::StarredReleaseId) = 0; + virtual void onUnstarred(db::StarredReleaseId) = 0; + virtual void onStarred(db::StarredTrackId) = 0; + virtual void onUnstarred(db::StarredTrackId) = 0; }; std::unique_ptr createFeedbackBackend(std::string_view backendName); diff --git a/src/libs/services/feedback/impl/internal/InternalBackend.cpp b/src/libs/services/feedback/impl/internal/InternalBackend.cpp index 473328c1..c20521bd 100644 --- a/src/libs/services/feedback/impl/internal/InternalBackend.cpp +++ b/src/libs/services/feedback/impl/internal/InternalBackend.cpp @@ -25,21 +25,21 @@ #include "database/StarredRelease.hpp" #include "database/StarredTrack.hpp" -namespace Feedback +namespace lms::feedback { namespace details { template - void onStarred(Database::Session& session, typename StarredObjType::IdType id) + void onStarred(db::Session& session, typename StarredObjType::IdType id) { auto transaction{ session.createWriteTransaction() }; if (auto starredObj{ StarredObjType::find(session, id) }) - starredObj.modify()->setSyncState(Database::SyncState::Synchronized); + starredObj.modify()->setSyncState(db::SyncState::Synchronized); } template - void onUnstarred(Database::Session& session, typename StarredObjType::IdType id) + void onUnstarred(db::Session& session, typename StarredObjType::IdType id) { auto transaction{ session.createWriteTransaction() }; @@ -48,37 +48,37 @@ namespace Feedback } } - InternalBackend::InternalBackend(Database::Db& db) + InternalBackend::InternalBackend(db::Db& db) : _db{ db } {} - void InternalBackend::onStarred(Database::StarredArtistId starredArtistId) + void InternalBackend::onStarred(db::StarredArtistId starredArtistId) { - details::onStarred(_db.getTLSSession(), starredArtistId); + details::onStarred(_db.getTLSSession(), starredArtistId); } - void InternalBackend::onUnstarred(Database::StarredArtistId starredArtistId) + void InternalBackend::onUnstarred(db::StarredArtistId starredArtistId) { - details::onUnstarred(_db.getTLSSession(), starredArtistId); + details::onUnstarred(_db.getTLSSession(), starredArtistId); } - void InternalBackend::onStarred(Database::StarredReleaseId starredReleaseId) + void InternalBackend::onStarred(db::StarredReleaseId starredReleaseId) { - details::onStarred(_db.getTLSSession(), starredReleaseId); + details::onStarred(_db.getTLSSession(), starredReleaseId); } - void InternalBackend::onUnstarred(Database::StarredReleaseId starredReleaseId) + void InternalBackend::onUnstarred(db::StarredReleaseId starredReleaseId) { - details::onUnstarred(_db.getTLSSession(), starredReleaseId); + details::onUnstarred(_db.getTLSSession(), starredReleaseId); } - void InternalBackend::onStarred(Database::StarredTrackId starredTrackId) + void InternalBackend::onStarred(db::StarredTrackId starredTrackId) { - details::onStarred(_db.getTLSSession(), starredTrackId); + details::onStarred(_db.getTLSSession(), starredTrackId); } - void InternalBackend::onUnstarred(Database::StarredTrackId starredTrackId) + void InternalBackend::onUnstarred(db::StarredTrackId starredTrackId) { - details::onUnstarred(_db.getTLSSession(), starredTrackId); + details::onUnstarred(_db.getTLSSession(), starredTrackId); } } // Feedback diff --git a/src/libs/services/feedback/impl/internal/InternalBackend.hpp b/src/libs/services/feedback/impl/internal/InternalBackend.hpp index 2b5bca01..c74343a8 100644 --- a/src/libs/services/feedback/impl/internal/InternalBackend.hpp +++ b/src/libs/services/feedback/impl/internal/InternalBackend.hpp @@ -21,27 +21,27 @@ #include "IFeedbackBackend.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Feedback +namespace lms::feedback { class InternalBackend final : public IFeedbackBackend { public: - InternalBackend(Database::Db& db); + InternalBackend(db::Db& db); private: - void onStarred(Database::StarredArtistId) override; - void onUnstarred(Database::StarredArtistId) override; - void onStarred(Database::StarredReleaseId) override; - void onUnstarred(Database::StarredReleaseId) override; - void onStarred(Database::StarredTrackId) override; - void onUnstarred(Database::StarredTrackId) override; + void onStarred(db::StarredArtistId) override; + void onUnstarred(db::StarredArtistId) override; + void onStarred(db::StarredReleaseId) override; + void onUnstarred(db::StarredReleaseId) override; + void onStarred(db::StarredTrackId) override; + void onUnstarred(db::StarredTrackId) override; - Database::Db& _db; + db::Db& _db; }; } // Feedback diff --git a/src/libs/services/feedback/impl/listenbrainz/Exception.hpp b/src/libs/services/feedback/impl/listenbrainz/Exception.hpp index 45664f92..f040178c 100644 --- a/src/libs/services/feedback/impl/listenbrainz/Exception.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/Exception.hpp @@ -21,11 +21,11 @@ #include "services/feedback/Exception.hpp" -namespace Feedback::ListenBrainz +namespace lms::feedback::listenBrainz { - class Exception : public ::Feedback::Exception + class Exception : public feedback::Exception { public: - using ::Feedback::Exception::Exception; + using feedback::Exception::Exception; }; } diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp b/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp index 79c2f531..d967aca4 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp @@ -19,7 +19,7 @@ #include "FeedbackTypes.hpp" -namespace Feedback::ListenBrainz +namespace lms::feedback::listenBrainz { std::ostream& operator<<(std::ostream& os, const Feedback& feedback) @@ -27,4 +27,4 @@ namespace Feedback::ListenBrainz os << "created = '" << feedback.created.toString() << "', recording MBID = '" << feedback.recordingMBID.getAsString() << "', score = " << static_cast(feedback.score); return os; } -} // Feedback::ListenBrainz +} // feedback::ListenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.hpp b/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.hpp index ec05760d..6b8f7be5 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.hpp @@ -21,9 +21,9 @@ #include #include -#include "utils/UUID.hpp" +#include "core/UUID.hpp" -namespace Feedback::ListenBrainz +namespace lms::feedback::listenBrainz { // See https://listenbrainz.readthedocs.io/en/production/dev/feedback-json/#feedback-json-doc enum class FeedbackType @@ -36,10 +36,10 @@ namespace Feedback::ListenBrainz struct Feedback { Wt::WDateTime created; - UUID recordingMBID; + core::UUID recordingMBID; FeedbackType score; }; std::ostream& operator<<(std::ostream& os, const Feedback& feedback); -} // Feedback::ListenBrainz +} // feedback::ListenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.cpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.cpp index bc925a98..3ed62f51 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.cpp @@ -27,13 +27,13 @@ #include "Exception.hpp" #include "Utils.hpp" -namespace Feedback::ListenBrainz +namespace lms::feedback::listenBrainz { namespace { Feedback parseFeedback(const Wt::Json::Object& feedbackObj) { - const std::optional recordingMBID{ UUID::fromString(static_cast(feedbackObj.get("recording_mbid"))) }; + const std::optional recordingMBID{ core::UUID::fromString(static_cast(feedbackObj.get("recording_mbid"))) }; if (!recordingMBID) throw Exception{ "MBID not found!" }; @@ -87,4 +87,4 @@ namespace Feedback::ListenBrainz return res; } -} // Feedback::ListenBrainz +} // feedback::ListenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.hpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.hpp index f745ceda..8f67a8c9 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.hpp @@ -23,7 +23,7 @@ #include "FeedbackTypes.hpp" -namespace Feedback::ListenBrainz +namespace lms::feedback::listenBrainz { class FeedbacksParser { @@ -37,4 +37,4 @@ namespace Feedback::ListenBrainz static Result parse(std::string_view msgBody); }; -} // Feedback::ListenBrainz +} // feedback::ListenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp index 949cc04d..27b48d5a 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp @@ -30,15 +30,15 @@ #include "database/StarredTrack.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "utils/IConfig.hpp" -#include "utils/http/IClient.hpp" -#include "utils/Service.hpp" +#include "core/IConfig.hpp" +#include "core/http/IClient.hpp" +#include "core/Service.hpp" #include "Exception.hpp" #include "FeedbacksParser.hpp" #include "Utils.hpp" -namespace Feedback::ListenBrainz +namespace lms::feedback::listenBrainz { namespace { @@ -59,37 +59,37 @@ namespace Feedback::ListenBrainz } } - FeedbacksSynchronizer::FeedbacksSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client) + FeedbacksSynchronizer::FeedbacksSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client) : _ioContext{ ioContext } , _db{ db } , _client{ client } - , _maxSyncFeedbackCount{ Service::get()->getULong("listenbrainz-max-sync-feedback-count", 1000) } - , _syncFeedbacksPeriod{ Service::get()->getULong("listenbrainz-sync-feedbacks-period-hours", 1) } + , _maxSyncFeedbackCount{ core::Service::get()->getULong("listenbrainz-max-sync-feedback-count", 1000) } + , _syncFeedbacksPeriod{ core::Service::get()->getULong("listenbrainz-sync-feedbacks-period-hours", 1) } { LOG(INFO, "Starting Feedbacks synchronizer, maxSyncFeedbackCount = " << _maxSyncFeedbackCount << ", _syncFeedbacksPeriod = " << _syncFeedbacksPeriod.count() << " hours"); scheduleSync(std::chrono::seconds{ 30 }); } - void FeedbacksSynchronizer::enqueFeedback(FeedbackType type, Database::StarredTrackId starredTrackId) + void FeedbacksSynchronizer::enqueFeedback(FeedbackType type, db::StarredTrackId starredTrackId) { try { - Database::Session& session{ _db.getTLSSession() }; + db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createWriteTransaction() }; - Database::StarredTrack::pointer starredTrack{ Database::StarredTrack::find(session, starredTrackId) }; + db::StarredTrack::pointer starredTrack{ db::StarredTrack::find(session, starredTrackId) }; if (!starredTrack) return; - std::optional recordingMBID{ starredTrack->getTrack()->getRecordingMBID() }; + std::optional recordingMBID{ starredTrack->getTrack()->getRecordingMBID() }; switch (type) { case FeedbackType::Love: - if (starredTrack->getSyncState() != Database::SyncState::PendingAdd) - starredTrack.modify()->setSyncState(Database::SyncState::PendingAdd); + if (starredTrack->getSyncState() != db::SyncState::PendingAdd) + starredTrack.modify()->setSyncState(db::SyncState::PendingAdd); break; case FeedbackType::Erase: @@ -102,7 +102,7 @@ namespace Feedback::ListenBrainz { // Send the erase order even if it is not on the remote LB server (it may be // queued for add, or not) - starredTrack.modify()->setSyncState(Database::SyncState::PendingRemove); + starredTrack.modify()->setSyncState(db::SyncState::PendingRemove); } break; @@ -116,11 +116,11 @@ namespace Feedback::ListenBrainz return; } - const std::optional listenBrainzToken{ starredTrack->getUser()->getListenBrainzToken() }; + const std::optional listenBrainzToken{ starredTrack->getUser()->getListenBrainzToken() }; if (!listenBrainzToken) return; - Http::ClientPOSTRequestParameters request; + core::http::ClientPOSTRequestParameters request; request.relativeUrl = "/1/feedback/recording-feedback"; request.message.addHeader("Authorization", "Token " + std::string{ listenBrainzToken->getAsString() }); @@ -146,14 +146,14 @@ namespace Feedback::ListenBrainz } } - void FeedbacksSynchronizer::onFeedbackSent(FeedbackType type, Database::StarredTrackId starredTrackId) + void FeedbacksSynchronizer::onFeedbackSent(FeedbackType type, db::StarredTrackId starredTrackId) { assert(_strand.running_in_this_thread()); - Database::Session& session{ _db.getTLSSession() }; + db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createWriteTransaction() }; - Database::StarredTrack::pointer starredTrack{ Database::StarredTrack::find(session, starredTrackId) }; + db::StarredTrack::pointer starredTrack{ db::StarredTrack::find(session, starredTrackId) }; if (!starredTrack) { LOG(DEBUG, "Starred track not found. deleted?"); @@ -165,7 +165,7 @@ namespace Feedback::ListenBrainz switch (type) { case FeedbackType::Love: - starredTrack.modify()->setSyncState(Database::SyncState::Synchronized); + starredTrack.modify()->setSyncState(db::SyncState::Synchronized); LOG(DEBUG, "State set to synchronized"); if (userContext.feedbackCount) @@ -193,20 +193,20 @@ namespace Feedback::ListenBrainz void FeedbacksSynchronizer::enquePendingFeedbacks() { - using namespace Database; + using namespace db; auto processPendingFeedbacks{ [this](SyncState scrobblingState, FeedbackType feedbackType) { RangeResults pendingFeedbacks; { - Database::Session& session {_db.getTLSSession()}; + db::Session& session {_db.getTLSSession()}; auto transaction {session.createReadTransaction()}; StarredTrack::FindParameters params; - params.setFeedbackBackend(Database::FeedbackBackend::ListenBrainz, scrobblingState) - .setRange(Database::Range {0, 100}); // don't flood too much? + params.setFeedbackBackend(db::FeedbackBackend::ListenBrainz, scrobblingState) + .setRange(db::Range {0, 100}); // don't flood too much? pendingFeedbacks = StarredTrack::find(session, params); } @@ -221,7 +221,7 @@ namespace Feedback::ListenBrainz processPendingFeedbacks(SyncState::PendingRemove, FeedbackType::Erase); } - FeedbacksSynchronizer::UserContext& FeedbacksSynchronizer::getUserContext(Database::UserId userId) + FeedbacksSynchronizer::UserContext& FeedbacksSynchronizer::getUserContext(db::UserId userId) { assert(_strand.running_in_this_thread()); @@ -274,14 +274,14 @@ namespace Feedback::ListenBrainz enquePendingFeedbacks(); - Database::RangeResults userIds; + db::RangeResults userIds; { - Database::Session& session{ _db.getTLSSession() }; + db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - userIds = Database::User::find(_db.getTLSSession(), Database::User::FindParameters{}.setFeedbackBackend(Database::FeedbackBackend::ListenBrainz)); + userIds = db::User::find(_db.getTLSSession(), db::User::FindParameters{}.setFeedbackBackend(db::FeedbackBackend::ListenBrainz)); } - for (const Database::UserId userId : userIds.results) + for (const db::UserId userId : userIds.results) startSync(getUserContext(userId)); if (!isSyncing()) @@ -315,20 +315,20 @@ namespace Feedback::ListenBrainz { assert(context.listenBrainzUserName.empty()); - const std::optional listenBrainzToken{ ListenBrainz::Utils::getListenBrainzToken(_db.getTLSSession(), context.userId) }; + const std::optional listenBrainzToken{ utils::getListenBrainzToken(_db.getTLSSession(), context.userId) }; if (!listenBrainzToken) { onSyncEnded(context); return; } - Http::ClientGETRequestParameters request; - request.priority = Http::ClientRequestParameters::Priority::Low; + core::http::ClientGETRequestParameters request; + request.priority = core::http::ClientRequestParameters::Priority::Low; request.relativeUrl = "/1/validate-token"; request.headers = { {"Authorization", "Token " + std::string {listenBrainzToken->getAsString()}} }; request.onSuccessFunc = [this, &context](std::string_view msgBody) { - context.listenBrainzUserName = ListenBrainz::Utils::parseValidateToken(msgBody); + context.listenBrainzUserName = utils::parseValidateToken(msgBody); if (context.listenBrainzUserName.empty()) { onSyncEnded(context); @@ -348,9 +348,9 @@ namespace Feedback::ListenBrainz { assert(!context.listenBrainzUserName.empty()); - Http::ClientGETRequestParameters request; + core::http::ClientGETRequestParameters request; request.relativeUrl = "/1/feedback/user/" + std::string{ context.listenBrainzUserName } + "/get-feedback?score=1&count=0"; - request.priority = Http::ClientRequestParameters::Priority::Low; + request.priority = core::http::ClientRequestParameters::Priority::Low; request.onSuccessFunc = [this, &context](std::string_view msgBody) { std::string msgBodyCopy{ msgBody }; @@ -383,9 +383,9 @@ namespace Feedback::ListenBrainz { assert(!context.listenBrainzUserName.empty()); - Http::ClientGETRequestParameters request; + core::http::ClientGETRequestParameters request; request.relativeUrl = "/1/feedback/user/" + context.listenBrainzUserName + "/get-feedback?offset=" + std::to_string(context.fetchedFeedbackCount); - request.priority = Http::ClientRequestParameters::Priority::Low; + request.priority = core::http::ClientRequestParameters::Priority::Low; request.onSuccessFunc = [this, &context](std::string_view msgBody) { std::string msgBodyCopy{ msgBody }; @@ -429,7 +429,7 @@ namespace Feedback::ListenBrainz void FeedbacksSynchronizer::tryImportFeedback(const Feedback& feedback, UserContext& context) { - using namespace Database; + using namespace db; Session& session{ _db.getTLSSession() }; @@ -451,7 +451,7 @@ namespace Feedback::ListenBrainz } trackId = tracks.front()->getId(); - needImport = !StarredTrack::exists(session, trackId, context.userId, Database::FeedbackBackend::ListenBrainz); + needImport = !StarredTrack::exists(session, trackId, context.userId, db::FeedbackBackend::ListenBrainz); // don't update starred date time // no need to update state if it was found as not synchronized @@ -473,7 +473,7 @@ namespace Feedback::ListenBrainz if (!user) return; - StarredTrack::pointer starredTrack{ session.create(track, user, Database::FeedbackBackend::ListenBrainz) }; + StarredTrack::pointer starredTrack{ session.create(track, user, db::FeedbackBackend::ListenBrainz) }; starredTrack.modify()->setSyncState(SyncState::Synchronized); starredTrack.modify()->setDateTime(feedback.created); @@ -485,4 +485,4 @@ namespace Feedback::ListenBrainz context.matchedFeedbackCount++; } } -} // namespace Feedback::ListenBrainz +} // namespace lms::feedback::listenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp index 32ad572b..8415f635 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp @@ -31,38 +31,40 @@ #include "FeedbackTypes.hpp" -namespace Database -{ - class Db; -} - -namespace Http +namespace lms { + namespace core::http + { class IClient; + } + namespace db + { + class Db; + } } -namespace Feedback::ListenBrainz +namespace lms::feedback::listenBrainz { class FeedbacksSynchronizer { public: - FeedbacksSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client); + FeedbacksSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client); - void enqueFeedback(FeedbackType type, Database::StarredTrackId starredTrackId); + void enqueFeedback(FeedbackType type, db::StarredTrackId starredTrackId); private: - void onFeedbackSent(FeedbackType type, Database::StarredTrackId starredTrackId); + void onFeedbackSent(FeedbackType type, db::StarredTrackId starredTrackId); void enquePendingFeedbacks(); struct UserContext { - UserContext(Database::UserId id) : userId{ id } {} + UserContext(db::UserId id) : userId{ id } {} UserContext(const UserContext&) = delete; UserContext& operator=(const UserContext&) = delete; - const Database::UserId userId; + const db::UserId userId; bool syncing{}; std::optional feedbackCount{}; @@ -75,7 +77,7 @@ namespace Feedback::ListenBrainz std::size_t importedFeedbackCount{}; }; - UserContext& getUserContext(Database::UserId userId); + UserContext& getUserContext(db::UserId userId); bool isSyncing() const; void scheduleSync(std::chrono::seconds fromNow); void startSync(); @@ -89,14 +91,14 @@ namespace Feedback::ListenBrainz boost::asio::io_context& _ioContext; boost::asio::io_context::strand _strand{ _ioContext }; - Database::Db& _db; + db::Db& _db; boost::asio::steady_timer _syncTimer{ _ioContext }; - Http::IClient& _client; + core::http::IClient& _client; - std::unordered_map _userContexts; + std::unordered_map _userContexts; const std::size_t _maxSyncFeedbackCount; const std::chrono::hours _syncFeedbacksPeriod; }; -} // Feedback::ListenBrainz +} // feedback::ListenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp index f5fdd2fc..b8d02727 100644 --- a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp @@ -24,30 +24,30 @@ #include "database/StarredArtist.hpp" #include "database/StarredRelease.hpp" #include "database/Track.hpp" -#include "utils/IConfig.hpp" -#include "utils/http/IClient.hpp" -#include "utils/ILogger.hpp" -#include "utils/Service.hpp" +#include "core/IConfig.hpp" +#include "core/http/IClient.hpp" +#include "core/ILogger.hpp" +#include "core/Service.hpp" #include "Utils.hpp" -namespace Feedback::ListenBrainz +namespace lms::feedback::listenBrainz { namespace details { template - void onStarred(Database::Session& session, typename StarredObjType::IdType id) + void onStarred(db::Session& session, typename StarredObjType::IdType id) { auto transaction{ session.createWriteTransaction() }; if (auto starredObj{ StarredObjType::find(session, id) }) { // maybe in the future this will be supported by ListenBrainz so set it to PendingAdd for all types - starredObj.modify()->setSyncState(Database::SyncState::PendingAdd); + starredObj.modify()->setSyncState(db::SyncState::PendingAdd); } } template - void onUnstarred(Database::Session& session, typename StarredObjType::IdType id) + void onUnstarred(db::Session& session, typename StarredObjType::IdType id) { auto transaction{ session.createWriteTransaction() }; @@ -56,11 +56,11 @@ namespace Feedback::ListenBrainz } } - ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, Database::Db& db) + ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, db::Db& db) : _ioContext{ ioContext } , _db{ db } - , _baseAPIUrl{ Service::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org") } - , _client{ Http::createClient(_ioContext, _baseAPIUrl) } + , _baseAPIUrl{ core::Service::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org") } + , _client{ core::http::createClient(_ioContext, _baseAPIUrl) } , _feedbacksSynchronizer{ _ioContext, db, *_client } { LOG(INFO, "Starting ListenBrainz feedback backend... API endpoint = '" << _baseAPIUrl << "'"); @@ -71,33 +71,33 @@ namespace Feedback::ListenBrainz LOG(INFO, "Stopped ListenBrainz feedback backend!"); } - void ListenBrainzBackend::onStarred(Database::StarredArtistId starredArtistId) + void ListenBrainzBackend::onStarred(db::StarredArtistId starredArtistId) { - details::onStarred(_db.getTLSSession(), starredArtistId); + details::onStarred(_db.getTLSSession(), starredArtistId); } - void ListenBrainzBackend::onUnstarred(Database::StarredArtistId starredArtistId) + void ListenBrainzBackend::onUnstarred(db::StarredArtistId starredArtistId) { - details::onUnstarred(_db.getTLSSession(), starredArtistId); + details::onUnstarred(_db.getTLSSession(), starredArtistId); } - void ListenBrainzBackend::onStarred(Database::StarredReleaseId starredReleaseId) + void ListenBrainzBackend::onStarred(db::StarredReleaseId starredReleaseId) { - details::onStarred(_db.getTLSSession(), starredReleaseId); + details::onStarred(_db.getTLSSession(), starredReleaseId); } - void ListenBrainzBackend::onUnstarred(Database::StarredReleaseId starredReleaseId) + void ListenBrainzBackend::onUnstarred(db::StarredReleaseId starredReleaseId) { - details::onUnstarred(_db.getTLSSession(), starredReleaseId); + details::onUnstarred(_db.getTLSSession(), starredReleaseId); } - void ListenBrainzBackend::onStarred(Database::StarredTrackId starredTrackId) + void ListenBrainzBackend::onStarred(db::StarredTrackId starredTrackId) { _feedbacksSynchronizer.enqueFeedback(FeedbackType::Love, starredTrackId); } - void ListenBrainzBackend::onUnstarred(Database::StarredTrackId starredtrackId) + void ListenBrainzBackend::onUnstarred(db::StarredTrackId starredtrackId) { _feedbacksSynchronizer.enqueFeedback(FeedbackType::Erase, starredtrackId); } -} // namespace Scrobbling::ListenBrainz +} // namespace lms::scrobbling::listenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp index 4f177b64..df69788f 100644 --- a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp @@ -26,35 +26,34 @@ #include "IFeedbackBackend.hpp" #include "FeedbacksSynchronizer.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Feedback::ListenBrainz +namespace lms::feedback::listenBrainz { class ListenBrainzBackend final : public IFeedbackBackend { public: - ListenBrainzBackend(boost::asio::io_context& ioContext, Database::Db& db); + ListenBrainzBackend(boost::asio::io_context& ioContext, db::Db& db); ~ListenBrainzBackend() override; private: ListenBrainzBackend(const ListenBrainzBackend&) = delete; ListenBrainzBackend& operator=(const ListenBrainzBackend&) = delete; - void onStarred(Database::StarredArtistId starredArtistId) override; - void onUnstarred(Database::StarredArtistId starredArtistId) override; - void onStarred(Database::StarredReleaseId starredReleaseId) override; - void onUnstarred(Database::StarredReleaseId starredReleaseId) override; - void onStarred(Database::StarredTrackId starredTrackId) override; - void onUnstarred(Database::StarredTrackId starredTrackId) override; + void onStarred(db::StarredArtistId starredArtistId) override; + void onUnstarred(db::StarredArtistId starredArtistId) override; + void onStarred(db::StarredReleaseId starredReleaseId) override; + void onUnstarred(db::StarredReleaseId starredReleaseId) override; + void onStarred(db::StarredTrackId starredTrackId) override; + void onUnstarred(db::StarredTrackId starredTrackId) override; boost::asio::io_context& _ioContext; - Database::Db& _db; + db::Db& _db; std::string _baseAPIUrl; - std::unique_ptr _client; + std::unique_ptr _client; FeedbacksSynchronizer _feedbacksSynchronizer; }; -} // Feedback::ListenBrainz - +} \ No newline at end of file diff --git a/src/libs/services/feedback/impl/listenbrainz/Utils.cpp b/src/libs/services/feedback/impl/listenbrainz/Utils.cpp index f512087d..ad545bf9 100644 --- a/src/libs/services/feedback/impl/listenbrainz/Utils.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/Utils.cpp @@ -25,13 +25,13 @@ #include "database/Session.hpp" #include "database/User.hpp" -namespace Feedback::ListenBrainz::Utils +namespace lms::feedback::listenBrainz::utils { - std::optional getListenBrainzToken(Database::Session& session, Database::UserId userId) + std::optional getListenBrainzToken(db::Session& session, db::UserId userId) { auto transaction{ session.createReadTransaction() }; - const Database::User::pointer user{ Database::User::find(session, userId) }; + const db::User::pointer user{ db::User::find(session, userId) }; if (!user) return std::nullopt; diff --git a/src/libs/services/feedback/impl/listenbrainz/Utils.hpp b/src/libs/services/feedback/impl/listenbrainz/Utils.hpp index df295817..3ae8ce46 100644 --- a/src/libs/services/feedback/impl/listenbrainz/Utils.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/Utils.hpp @@ -20,18 +20,18 @@ #pragma once #include "database/UserId.hpp" -#include "utils/ILogger.hpp" -#include "utils/UUID.hpp" +#include "core/ILogger.hpp" +#include "core/UUID.hpp" #define LOG(sev, message) LMS_LOG(FEEDBACK, sev, "[listenbrainz] " << message) -namespace Database +namespace lms::db { class Session; } -namespace Feedback::ListenBrainz::Utils +namespace lms::feedback::listenBrainz::utils { - std::optional getListenBrainzToken(Database::Session& session, Database::UserId userId); + std::optional getListenBrainzToken(db::Session& session, db::UserId userId); std::string parseValidateToken(std::string_view msgBody); } diff --git a/src/libs/services/feedback/include/services/feedback/Exception.hpp b/src/libs/services/feedback/include/services/feedback/Exception.hpp index 1a8414fb..03a3d09e 100644 --- a/src/libs/services/feedback/include/services/feedback/Exception.hpp +++ b/src/libs/services/feedback/include/services/feedback/Exception.hpp @@ -19,11 +19,11 @@ #pragma once -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -namespace Feedback +namespace lms::feedback { - class Exception : public LmsException + class Exception : public core::LmsException { public: using LmsException::LmsException; diff --git a/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp b/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp index 4abea134..04a4ae8a 100644 --- a/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp +++ b/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp @@ -33,67 +33,67 @@ #include "database/UserId.hpp" #include "database/Types.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Feedback +namespace lms::feedback { class IFeedbackService { public: virtual ~IFeedbackService() = default; - using ArtistContainer = Database::RangeResults; - using ReleaseContainer = Database::RangeResults; - using TrackContainer = Database::RangeResults; + using ArtistContainer = db::RangeResults; + using ReleaseContainer = db::RangeResults; + using TrackContainer = db::RangeResults; struct FindParameters { - Database::UserId user; - std::vector clusters; // if non empty, at least one artist that belongs to these clusters - std::optional range; - Database::MediaLibraryId library; + db::UserId user; + std::vector clusters; // if non empty, at least one artist that belongs to these clusters + std::optional range; + db::MediaLibraryId library; - FindParameters& setUser(const Database::UserId _user) { user = _user; return *this; } - FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } - FindParameters& setRange(std::optional _range) { range = _range; return *this; } - FindParameters& setMediaLibrary(Database::MediaLibraryId _library) { library = _library; return *this; } + FindParameters& setUser(const db::UserId _user) { user = _user; return *this; } + FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } + FindParameters& setRange(std::optional _range) { range = _range; return *this; } + FindParameters& setMediaLibrary(db::MediaLibraryId _library) { library = _library; return *this; } }; // Artists struct ArtistFindParameters : public FindParameters { - std::optional linkType; // if set, only artists that have produced at least one track with this link type - Database::ArtistSortMethod sortMethod{ Database::ArtistSortMethod::None }; + std::optional linkType; // if set, only artists that have produced at least one track with this link type + db::ArtistSortMethod sortMethod{ db::ArtistSortMethod::None }; - ArtistFindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } - ArtistFindParameters& setSortMethod(Database::ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } + ArtistFindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } + ArtistFindParameters& setSortMethod(db::ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } }; - virtual void star(Database::UserId userId, Database::ArtistId artistId) = 0; - virtual void unstar(Database::UserId userId, Database::ArtistId artistId) = 0; - virtual bool isStarred(Database::UserId userId, Database::ArtistId artistId) = 0; - virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ArtistId artistId) = 0; + virtual void star(db::UserId userId, db::ArtistId artistId) = 0; + virtual void unstar(db::UserId userId, db::ArtistId artistId) = 0; + virtual bool isStarred(db::UserId userId, db::ArtistId artistId) = 0; + virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::ArtistId artistId) = 0; virtual ArtistContainer findStarredArtists(const ArtistFindParameters& params) = 0; // Releases - virtual void star(Database::UserId userId, Database::ReleaseId releaseId) = 0; - virtual void unstar(Database::UserId userId, Database::ReleaseId releaseId) = 0; - virtual bool isStarred(Database::UserId userId, Database::ReleaseId artistId) = 0; - virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::ReleaseId artistId) = 0; + virtual void star(db::UserId userId, db::ReleaseId releaseId) = 0; + virtual void unstar(db::UserId userId, db::ReleaseId releaseId) = 0; + virtual bool isStarred(db::UserId userId, db::ReleaseId artistId) = 0; + virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::ReleaseId artistId) = 0; virtual ReleaseContainer findStarredReleases(const FindParameters& params) = 0; // Tracks - virtual void star(Database::UserId userId, Database::TrackId trackId) = 0; - virtual void unstar(Database::UserId userId, Database::TrackId trackId) = 0; - virtual bool isStarred(Database::UserId userId, Database::TrackId artistId) = 0; - virtual Wt::WDateTime getStarredDateTime(Database::UserId userId, Database::TrackId artistId) = 0; + virtual void star(db::UserId userId, db::TrackId trackId) = 0; + virtual void unstar(db::UserId userId, db::TrackId trackId) = 0; + virtual bool isStarred(db::UserId userId, db::TrackId artistId) = 0; + virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::TrackId artistId) = 0; virtual TrackContainer findStarredTracks(const FindParameters& params) = 0; }; - std::unique_ptr createFeedbackService(boost::asio::io_service& ioService, Database::Db& db); + std::unique_ptr createFeedbackService(boost::asio::io_service& ioService, db::Db& db); } // ns Feedback diff --git a/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp b/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp index e0fcc842..eb3d36f0 100644 --- a/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp +++ b/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp @@ -21,14 +21,14 @@ #include -namespace Database +namespace lms::db { class Db; } -namespace Recommendation +namespace lms::recommendation { class IEngine; - std::unique_ptr createClustersEngine(Database::Db& db); + std::unique_ptr createClustersEngine(db::Db& db); } diff --git a/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp b/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp index 5cbbde7c..f1b335e1 100644 --- a/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp +++ b/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp @@ -22,13 +22,13 @@ #include #include "IEngine.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Recommendation +namespace lms::recommendation { - std::unique_ptr createFeaturesEngine(Database::Db& db); + std::unique_ptr createFeaturesEngine(db::Db& db); } diff --git a/src/libs/services/recommendation/impl/IEngine.hpp b/src/libs/services/recommendation/impl/IEngine.hpp index 1735dd95..aaf9b907 100644 --- a/src/libs/services/recommendation/impl/IEngine.hpp +++ b/src/libs/services/recommendation/impl/IEngine.hpp @@ -23,14 +23,14 @@ #include "database/Types.hpp" #include "database/TrackListId.hpp" #include "services/recommendation/Types.hpp" -#include "utils/EnumSet.hpp" +#include "core/EnumSet.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Recommendation +namespace lms::recommendation { class IEngine { @@ -40,13 +40,13 @@ namespace Recommendation virtual void load(bool forceReload, const ProgressCallback& progressCallback = {}) = 0; virtual void requestCancelLoad() = 0; - virtual TrackContainer findSimilarTracksFromTrackList(Database::TrackListId tracklistId, std::size_t maxCount) const = 0; - virtual TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const = 0; - virtual ReleaseContainer getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const = 0; - virtual ArtistContainer getSimilarArtists(Database::ArtistId artistId, EnumSet linkTypes, std::size_t maxCount) const = 0; + virtual TrackContainer findSimilarTracksFromTrackList(db::TrackListId tracklistId, std::size_t maxCount) const = 0; + virtual TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const = 0; + virtual ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const = 0; + virtual ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const = 0; }; - std::unique_ptr createEngine(Database::Db& db); + std::unique_ptr createEngine(db::Db& db); } // ns Recommendation diff --git a/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp b/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp index 88a0f3bd..0d03a40b 100644 --- a/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp +++ b/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp @@ -26,18 +26,18 @@ #include "playlist-constraints/ConsecutiveArtists.hpp" #include "playlist-constraints/ConsecutiveReleases.hpp" #include "playlist-constraints/DuplicateTracks.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" -namespace Recommendation +namespace lms::recommendation { - using namespace Database; + using namespace db; - std::unique_ptr createPlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService) + std::unique_ptr createPlaylistGeneratorService(Db& db, IRecommendationService& recommendationService) { return std::make_unique(db, recommendationService); } - PlaylistGeneratorService::PlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService) + PlaylistGeneratorService::PlaylistGeneratorService(Db& db, IRecommendationService& recommendationService) : _db{ db } , _recommendationService{ recommendationService } { @@ -69,7 +69,7 @@ namespace Recommendation // select the similar track that has the best score for (std::size_t trackIndex{}; trackIndex < similarTracks.size(); ++trackIndex) { - using namespace Database::Debug; + using namespace db::Debug; finalResult.push_back(similarTracks[trackIndex]); @@ -95,7 +95,7 @@ namespace Recommendation return std::vector(std::cbegin(finalResult) + startingTracks.size(), std::cend(finalResult)); } - TrackContainer PlaylistGeneratorService::getTracksFromTrackList(Database::TrackListId tracklistId) const + TrackContainer PlaylistGeneratorService::getTracksFromTrackList(db::TrackListId tracklistId) const { TrackContainer tracks; diff --git a/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp b/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp index 8dd9ee03..afdf0273 100644 --- a/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp +++ b/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp @@ -23,20 +23,20 @@ #include "services/recommendation/IRecommendationService.hpp" #include "playlist-constraints/IConstraint.hpp" -namespace Recommendation +namespace lms::recommendation { class PlaylistGeneratorService : public IPlaylistGeneratorService { public: - PlaylistGeneratorService(Database::Db& db, Recommendation::IRecommendationService& recommendationService); + PlaylistGeneratorService(db::Db& db, IRecommendationService& recommendationService); private: - TrackContainer extendPlaylist(Database::TrackListId tracklistId, std::size_t maxCount) const override; + TrackContainer extendPlaylist(db::TrackListId tracklistId, std::size_t maxCount) const override; - TrackContainer getTracksFromTrackList(Database::TrackListId tracklistId) const; + TrackContainer getTracksFromTrackList(db::TrackListId tracklistId) const; - Database::Db& _db; - Recommendation::IRecommendationService& _recommendationService; + db::Db& _db; + IRecommendationService& _recommendationService; std::vector> _constraints; }; } // namespace Radio diff --git a/src/libs/services/recommendation/impl/RecommendationService.cpp b/src/libs/services/recommendation/impl/RecommendationService.cpp index 12fe6150..d1f95b9a 100644 --- a/src/libs/services/recommendation/impl/RecommendationService.cpp +++ b/src/libs/services/recommendation/impl/RecommendationService.cpp @@ -28,33 +28,33 @@ #include "database/Db.hpp" #include "database/Session.hpp" #include "database/ScanSettings.hpp" -#include "utils/Exception.hpp" -#include "utils/ILogger.hpp" +#include "core/Exception.hpp" +#include "core/ILogger.hpp" -namespace Recommendation +namespace lms::recommendation { namespace { - Database::ScanSettings::SimilarityEngineType getSimilarityEngineType(Database::Session& session) + db::ScanSettings::SimilarityEngineType getSimilarityEngineType(db::Session& session) { auto transaction{ session.createReadTransaction() }; - return Database::ScanSettings::get(session)->getSimilarityEngineType(); + return db::ScanSettings::get(session)->getSimilarityEngineType(); } } - std::unique_ptr createRecommendationService(Database::Db& db) + std::unique_ptr createRecommendationService(db::Db& db) { return std::make_unique(db); } - RecommendationService::RecommendationService(Database::Db& db) + RecommendationService::RecommendationService(db::Db& db) : _db{ db } { load(); } - TrackContainer RecommendationService::findSimilarTracks(Database::TrackListId trackListId, std::size_t maxCount) const + TrackContainer RecommendationService::findSimilarTracks(db::TrackListId trackListId, std::size_t maxCount) const { TrackContainer res; @@ -64,7 +64,7 @@ namespace Recommendation return _engine->findSimilarTracksFromTrackList(trackListId, maxCount); } - TrackContainer RecommendationService::findSimilarTracks(const std::vector& trackIds, std::size_t maxCount) const + TrackContainer RecommendationService::findSimilarTracks(const std::vector& trackIds, std::size_t maxCount) const { TrackContainer res; @@ -74,7 +74,7 @@ namespace Recommendation return _engine->findSimilarTracks(trackIds, maxCount); } - ReleaseContainer RecommendationService::getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const + ReleaseContainer RecommendationService::getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const { ReleaseContainer res; @@ -84,7 +84,7 @@ namespace Recommendation return _engine->getSimilarReleases(releaseId, maxCount);; } - ArtistContainer RecommendationService::getSimilarArtists(Database::ArtistId artistId, EnumSet linkTypes, std::size_t maxCount) const + ArtistContainer RecommendationService::getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const { ArtistContainer res; @@ -98,7 +98,7 @@ namespace Recommendation void RecommendationService::load() { - using namespace Database; + using namespace db; switch (getSimilarityEngineType(_db.getTLSSession())) { diff --git a/src/libs/services/recommendation/impl/RecommendationService.hpp b/src/libs/services/recommendation/impl/RecommendationService.hpp index a27956e2..4389aad0 100644 --- a/src/libs/services/recommendation/impl/RecommendationService.hpp +++ b/src/libs/services/recommendation/impl/RecommendationService.hpp @@ -24,12 +24,12 @@ #include "services/recommendation/IRecommendationService.hpp" #include "IEngine.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Recommendation +namespace lms::recommendation { enum class EngineType { @@ -40,7 +40,7 @@ namespace Recommendation class RecommendationService : public IRecommendationService { public: - RecommendationService(Database::Db& db); + RecommendationService(db::Db& db); ~RecommendationService() = default; RecommendationService(const RecommendationService&) = delete; @@ -49,16 +49,16 @@ namespace Recommendation private: void load() override; - TrackContainer findSimilarTracks(Database::TrackListId tracklistId, std::size_t maxCount) const override; - TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const override; - ReleaseContainer getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const override; - ArtistContainer getSimilarArtists(Database::ArtistId artistId, EnumSet linkTypes, std::size_t maxCount) const override; + TrackContainer findSimilarTracks(db::TrackListId tracklistId, std::size_t maxCount) const override; + TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const override; + ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const override; + ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const override; void setEnginePriorities(const std::vector& engineTypes); void clearEngines(); void loadPendingEngine(EngineType engineType, std::unique_ptr engine, bool forceReload, const ProgressCallback& progressCallback); - Database::Db& _db; + db::Db& _db; std::optional _engineType; std::unique_ptr _engine; }; diff --git a/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp b/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp index 504fc7f5..d8a8df8f 100644 --- a/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp +++ b/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp @@ -27,9 +27,9 @@ #include "database/Track.hpp" #include "database/TrackList.hpp" -namespace Recommendation { +namespace lms::recommendation { - using namespace Database; + using namespace db; std::unique_ptr createClustersEngine(Db& db) { @@ -92,7 +92,7 @@ namespace Recommendation { return res; } - ArtistContainer ClusterEngine::getSimilarArtists(ArtistId artistId, EnumSet artistLinkTypes, std::size_t maxCount) const + ArtistContainer ClusterEngine::getSimilarArtists(ArtistId artistId, core::EnumSet artistLinkTypes, std::size_t maxCount) const { if (maxCount == 0) return {}; @@ -108,4 +108,4 @@ namespace Recommendation { return std::move(similarArtistIds.results); } -} // namespace Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp b/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp index 09c14072..da600bde 100644 --- a/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp +++ b/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp @@ -21,13 +21,13 @@ #include "IEngine.hpp" -namespace Recommendation +namespace lms::recommendation { class ClusterEngine : public IEngine { public: - ClusterEngine(Database::Db& db) : _db {db} {} + ClusterEngine(db::Db& db) : _db {db} {} ClusterEngine(const ClusterEngine&) = delete; ClusterEngine(ClusterEngine&&) = delete; @@ -38,13 +38,13 @@ namespace Recommendation void load(bool, const ProgressCallback&) override {} void requestCancelLoad() override {} - TrackContainer findSimilarTracksFromTrackList(Database::TrackListId tracklistId, std::size_t maxCount) const override; - TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const override; - ReleaseContainer getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const override; - ArtistContainer getSimilarArtists(Database::ArtistId artistId, EnumSet linkTypes, std::size_t maxCount) const override; + TrackContainer findSimilarTracksFromTrackList(db::TrackListId tracklistId, std::size_t maxCount) const override; + TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const override; + ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const override; + ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const override; - Database::Db& _db; + db::Db& _db; }; -} // namespace Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/features/FeaturesDefs.cpp b/src/libs/services/recommendation/impl/features/FeaturesDefs.cpp index 1982b683..418ef863 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesDefs.cpp +++ b/src/libs/services/recommendation/impl/features/FeaturesDefs.cpp @@ -22,9 +22,9 @@ #include #include -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -namespace Recommendation { +namespace lms::recommendation { static const std::unordered_map featureDefinitions { @@ -373,7 +373,7 @@ getFeatureDef(const FeatureName& featureName) { auto it {featureDefinitions.find(featureName)}; if (it == std::cend(featureDefinitions)) - throw LmsException {"Unhandled requested feature '" + featureName + "'"}; + throw core::LmsException {"Unhandled requested feature '" + featureName + "'"}; return it->second; } @@ -389,5 +389,5 @@ getFeatureNames() return res; } -} // namespace Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/features/FeaturesDefs.hpp b/src/libs/services/recommendation/impl/features/FeaturesDefs.hpp index d6e1d759..6a83b7fb 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesDefs.hpp +++ b/src/libs/services/recommendation/impl/features/FeaturesDefs.hpp @@ -24,7 +24,7 @@ #include #include -namespace Recommendation { +namespace lms::recommendation { using FeatureName = std::string; using FeatureNames = std::unordered_set; @@ -46,4 +46,4 @@ struct FeatureSettings }; using FeatureSettingsMap = std::unordered_map; -} // namespace Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp b/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp index cf82b13e..0cec15ba 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp @@ -30,12 +30,12 @@ #include "database/TrackFeatures.hpp" #include "database/TrackList.hpp" #include "som/DataNormalizer.hpp" -#include "utils/ILogger.hpp" -#include "utils/Random.hpp" +#include "core/ILogger.hpp" +#include "core/Random.hpp" -namespace Recommendation +namespace lms::recommendation { - using namespace Database; + using namespace db; std::unique_ptr createFeaturesEngine(Db& db) { @@ -44,10 +44,10 @@ namespace Recommendation namespace { - std::optional convertFeatureValuesMapToInputVector(const FeatureValuesMap& featureValuesMap, std::size_t nbDimensions) + std::optional convertFeatureValuesMapToInputVector(const FeatureValuesMap& featureValuesMap, std::size_t nbDimensions) { std::size_t i{}; - std::optional res{ SOM::InputVector {nbDimensions} }; + std::optional res{ som::InputVector {nbDimensions} }; for (const auto& [featureName, values] : featureValuesMap) { if (values.size() != getFeatureDef(featureName).nbDimensions) @@ -64,9 +64,9 @@ namespace Recommendation return res; } - SOM::InputVector getInputVectorWeights(const FeatureSettingsMap& featureSettingsMap, std::size_t nbDimensions) + som::InputVector getInputVectorWeights(const FeatureSettingsMap& featureSettingsMap, std::size_t nbDimensions) { - SOM::InputVector weights{ nbDimensions }; + som::InputVector weights{ nbDimensions }; std::size_t index{}; for (const auto& [featureName, featureSettings] : featureSettingsMap) { @@ -120,7 +120,7 @@ namespace Recommendation LMS_LOG(RECOMMENDATION, DEBUG, "Getting Track features DONE (found " << trackFeaturesIds.results.size() << " track features)"); } - std::vector samples; + std::vector samples; std::vector samplesTrackIds; samples.reserve(trackFeaturesIds.results.size()); @@ -143,7 +143,7 @@ namespace Recommendation if (featureValuesMap.empty()) continue; - std::optional inputVector{ convertFeatureValuesMapToInputVector(featureValuesMap, nbDimensions) }; + std::optional inputVector{ convertFeatureValuesMapToInputVector(featureValuesMap, nbDimensions) }; if (!inputVector) continue; @@ -159,13 +159,13 @@ namespace Recommendation } LMS_LOG(RECOMMENDATION, DEBUG, "Normalizing data..."); - SOM::DataNormalizer dataNormalizer{ nbDimensions }; + som::DataNormalizer dataNormalizer{ nbDimensions }; dataNormalizer.computeNormalizationFactors(samples); for (auto& sample : samples) dataNormalizer.normalizeData(sample); - SOM::Coordinate size{ static_cast(std::sqrt(samples.size() / trainSettings.sampleCountPerNeuron)) }; + som::Coordinate size{ static_cast(std::sqrt(samples.size() / trainSettings.sampleCountPerNeuron)) }; if (size < 2) { LMS_LOG(RECOMMENDATION, WARNING, "Very few tracks (" << samples.size() << ") are being used by the features engine, expect bad behaviors"); @@ -173,12 +173,12 @@ namespace Recommendation } LMS_LOG(RECOMMENDATION, INFO, "Found " << samples.size() << " tracks, constructing a " << size << "*" << size << " network"); - SOM::Network network{ size, size, nbDimensions }; + som::Network network{ size, size, nbDimensions }; - SOM::InputVector weights{ getInputVectorWeights(trainSettings.featureSettingsMap, nbDimensions) }; + som::InputVector weights{ getInputVectorWeights(trainSettings.featureSettingsMap, nbDimensions) }; network.setDataWeights(weights); - auto somProgressCallback{ [&](const SOM::Network::CurrentIteration& iter) + auto somProgressCallback{ [&](const som::Network::CurrentIteration& iter) { LMS_LOG(RECOMMENDATION, DEBUG, "Current pass = " << iter.idIteration << " / " << iter.iterationCount); progressCallback(Progress {iter.idIteration, iter.iterationCount}); @@ -186,7 +186,7 @@ namespace Recommendation LMS_LOG(RECOMMENDATION, DEBUG, "Training network..."); network.train(samples, trainSettings.iterationCount, - progressCallback ? somProgressCallback : SOM::Network::ProgressCallback{}, + progressCallback ? somProgressCallback : som::Network::ProgressCallback{}, [this] { return _loadCancelled; }); LMS_LOG(RECOMMENDATION, DEBUG, "Training network DONE"); @@ -197,7 +197,7 @@ namespace Recommendation if (_loadCancelled) return; - const SOM::Position position{ network.getClosestRefVectorPosition(samples[i]) }; + const som::Position position{ network.getClosestRefVectorPosition(samples[i]) }; trackPositions[samplesTrackIds[i]].push_back(position); } @@ -275,7 +275,7 @@ namespace Recommendation return similarReleaseIds; } - ArtistContainer FeaturesEngine::getSimilarArtists(ArtistId artistId, EnumSet linkTypes, std::size_t maxCount) const + ArtistContainer FeaturesEngine::getSimilarArtists(ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const { auto getSimilarArtistIdsForLinkType{ [&](TrackArtistLinkType linkType) { @@ -313,7 +313,7 @@ namespace Recommendation } while (res.size() > maxCount) - res.erase(Random::pickRandom(res)); + res.erase(core::random::pickRandom(res)); return res; } @@ -349,15 +349,15 @@ namespace Recommendation _loadCancelled = true; } - void FeaturesEngine::load(const SOM::Network& network, const TrackPositions& trackPositions) + void FeaturesEngine::load(const som::Network& network, const TrackPositions& trackPositions) { - using namespace Database; + using namespace db; _networkRefVectorsDistanceMedian = network.computeRefVectorsDistanceMedian(); LMS_LOG(RECOMMENDATION, DEBUG, "Median distance betweend ref vectors = " << _networkRefVectorsDistanceMedian); - const SOM::Coordinate width{ network.getWidth() }; - const SOM::Coordinate height{ network.getHeight() }; + const som::Coordinate width{ network.getWidth() }; + const som::Coordinate height{ network.getHeight() }; _releaseMatrix = ReleaseMatrix{ width, height }; _trackMatrix = TrackMatrix{ width, height }; @@ -377,22 +377,22 @@ namespace Recommendation if (!track) continue; - for (const SOM::Position& position : positions) + for (const som::Position& position : positions) { - Utils::push_back_if_not_present(_trackPositions[trackId], position); - Utils::push_back_if_not_present(_trackMatrix[position], trackId); + core::utils::push_back_if_not_present(_trackPositions[trackId], position); + core::utils::push_back_if_not_present(_trackMatrix[position], trackId); if (Release::pointer release{ track->getRelease() }) { const ReleaseId releaseId{ release->getId() }; - Utils::push_back_if_not_present(_releasePositions[releaseId], position); - Utils::push_back_if_not_present(_releaseMatrix[position], releaseId); + core::utils::push_back_if_not_present(_releasePositions[releaseId], position); + core::utils::push_back_if_not_present(_releaseMatrix[position], releaseId); } for (const TrackArtistLink::pointer& artistLink : track->getArtistLinks()) { const ArtistId artistId{ artistLink->getArtist()->getId() }; - Utils::push_back_if_not_present(_artistPositions[artistId], position); + core::utils::push_back_if_not_present(_artistPositions[artistId], position); auto itArtists{ _artistMatrix.find(artistLink->getType()) }; if (itArtists == std::cend(_artistMatrix)) { @@ -400,12 +400,12 @@ namespace Recommendation assert(inserted); itArtists = it; } - Utils::push_back_if_not_present(itArtists->second[position], artistId); + core::utils::push_back_if_not_present(itArtists->second[position], artistId); } } } - _network = std::make_unique(network); + _network = std::make_unique(network); LMS_LOG(RECOMMENDATION, INFO, "Classifier successfully loaded!"); } diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp b/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp index d6839f16..8506dee7 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp @@ -28,178 +28,174 @@ #include "som/DataNormalizer.hpp" #include "som/Network.hpp" -#include "utils/Utils.hpp" +#include "core/Utils.hpp" #include "IEngine.hpp" #include "FeaturesEngineCache.hpp" #include "FeaturesDefs.hpp" -namespace Database +namespace lms::db { - class Session; + class Session; } -namespace Recommendation { - -using FeatureWeight = double; - -class FeaturesEngine : public IEngine +namespace lms::recommendation { - public: - FeaturesEngine(Database::Db& db) : _db {db} {} + using FeatureWeight = double; - FeaturesEngine(const FeaturesEngine&) = delete; - FeaturesEngine(FeaturesEngine&&) = delete; - FeaturesEngine& operator=(const FeaturesEngine&) = delete; - FeaturesEngine& operator=(FeaturesEngine&&) = delete; + class FeaturesEngine : public IEngine + { + public: + FeaturesEngine(db::Db& db) : _db{ db } {} - static const FeatureSettingsMap& getDefaultTrainFeatureSettings(); + FeaturesEngine(const FeaturesEngine&) = delete; + FeaturesEngine(FeaturesEngine&&) = delete; + FeaturesEngine& operator=(const FeaturesEngine&) = delete; + FeaturesEngine& operator=(FeaturesEngine&&) = delete; - private: - void load(bool forceReload, const ProgressCallback& progressCallback) override; - void requestCancelLoad() override; + static const FeatureSettingsMap& getDefaultTrainFeatureSettings(); - TrackContainer findSimilarTracksFromTrackList(Database::TrackListId tracklistId, std::size_t maxCount) const override; - TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const override; - ReleaseContainer getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const override; - ArtistContainer getSimilarArtists(Database::ArtistId artistId, EnumSet linkTypes, std::size_t maxCount) const override; + private: + void load(bool forceReload, const ProgressCallback& progressCallback) override; + void requestCancelLoad() override; - void loadFromCache(FeaturesEngineCache&& cache); + TrackContainer findSimilarTracksFromTrackList(db::TrackListId tracklistId, std::size_t maxCount) const override; + TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const override; + ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const override; + ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const override; - // Use training (may be very slow) - struct TrainSettings - { - std::size_t iterationCount {10}; - float sampleCountPerNeuron {4}; - FeatureSettingsMap featureSettingsMap; - }; - void loadFromTraining(const TrainSettings& trainSettings, const ProgressCallback& progressCallback); + void loadFromCache(FeaturesEngineCache&& cache); - template - using ObjectPositions = std::unordered_map>; + // Use training (may be very slow) + struct TrainSettings + { + std::size_t iterationCount{ 10 }; + float sampleCountPerNeuron{ 4 }; + FeatureSettingsMap featureSettingsMap; + }; + void loadFromTraining(const TrainSettings& trainSettings, const ProgressCallback& progressCallback); - using ArtistPositions = ObjectPositions; - using ReleasePositions = ObjectPositions; - using TrackPositions = ObjectPositions; + template + using ObjectPositions = std::unordered_map>; - template - using ObjectMatrix = SOM::Matrix>; - using ArtistMatrix = ObjectMatrix; - using ReleaseMatrix = ObjectMatrix; - using TrackMatrix = ObjectMatrix; + using ArtistPositions = ObjectPositions; + using ReleasePositions = ObjectPositions; + using TrackPositions = ObjectPositions; - void load(const SOM::Network& network, const TrackPositions& tracksPosition); + template + using ObjectMatrix = som::Matrix>; + using ArtistMatrix = ObjectMatrix; + using ReleaseMatrix = ObjectMatrix; + using TrackMatrix = ObjectMatrix; - FeaturesEngineCache toCache() const; + void load(const som::Network& network, const TrackPositions& tracksPosition); - template - static std::vector getMatchingRefVectorsPosition(const std::vector& ids, const ObjectPositions& objectPositions); + FeaturesEngineCache toCache() const; - template - static std::vector getObjectsIds(const std::vector& positions, const ObjectMatrix& objectsMatrix); + template + static std::vector getMatchingRefVectorsPosition(const std::vector& ids, const ObjectPositions& objectPositions); - template - std::vector getSimilarObjects(const std::vector& ids, - const ObjectMatrix& objectMatrix, - const ObjectPositions& objectPositions, - std::size_t maxCount) const; + template + static std::vector getObjectsIds(const std::vector& positions, const ObjectMatrix& objectsMatrix); - Database::Db& _db; - bool _loadCancelled {}; - std::unique_ptr _network; - double _networkRefVectorsDistanceMedian {}; + template + std::vector getSimilarObjects(const std::vector& ids, + const ObjectMatrix& objectMatrix, + const ObjectPositions& objectPositions, + std::size_t maxCount) const; - ArtistPositions _artistPositions; - std::unordered_map _artistMatrix; + db::Db& _db; + bool _loadCancelled{}; + std::unique_ptr _network; + double _networkRefVectorsDistanceMedian{}; - ReleasePositions _releasePositions; - ReleaseMatrix _releaseMatrix; + ArtistPositions _artistPositions; + std::unordered_map _artistMatrix; - TrackPositions _trackPositions; - TrackMatrix _trackMatrix; -}; + ReleasePositions _releasePositions; + ReleaseMatrix _releaseMatrix; -template -std::vector -FeaturesEngine::getMatchingRefVectorsPosition(const std::vector& ids, const ObjectPositions& objectPositions) -{ - std::vector res; + TrackPositions _trackPositions; + TrackMatrix _trackMatrix; + }; - if (ids.empty()) - return res; + template + std::vector FeaturesEngine::getMatchingRefVectorsPosition(const std::vector& ids, const ObjectPositions& objectPositions) + { + std::vector res; - for (const IdType id : ids) - { - auto it = objectPositions.find(id); - if (it == objectPositions.end()) - continue; + if (ids.empty()) + return res; - for (const SOM::Position& position : it->second) - Utils::push_back_if_not_present(res, position); - } + for (const IdType id : ids) + { + auto it = objectPositions.find(id); + if (it == objectPositions.end()) + continue; - return res; + for (const som::Position& position : it->second) + core::utils::push_back_if_not_present(res, position); + } + + return res; + } + + template + std::vector FeaturesEngine::getObjectsIds(const std::vector& positions, const ObjectMatrix& objectMatrix) + { + std::vector res; + + for (const som::Position& position : positions) + { + for (const IdType id : objectMatrix.get(position)) + core::utils::push_back_if_not_present(res, id); + } + + return res; + } + + template + std::vector FeaturesEngine::getSimilarObjects(const std::vector& ids, + const ObjectMatrix& objectMatrix, + const ObjectPositions& objectPositions, + std::size_t maxCount) const + { + std::vector res; + + std::vector searchedRefVectorsPosition{ getMatchingRefVectorsPosition(ids, objectPositions) }; + if (searchedRefVectorsPosition.empty()) + return res; + + while (1) + { + std::vector closestObjectIds{ getObjectsIds(searchedRefVectorsPosition, objectMatrix) }; + + // Remove objects that are already in input or already reported + closestObjectIds.erase(std::remove_if(std::begin(closestObjectIds), std::end(closestObjectIds), + [&](IdType id) + { + return std::find(std::cbegin(ids), std::cend(ids), id) != std::cend(ids); + }) + , std::end(closestObjectIds)); + + for (IdType id : closestObjectIds) + { + if (res.size() == maxCount) + break; + + core::utils::push_back_if_not_present(res, id); + } + + if (res.size() == maxCount) + break; + + // If there is not enough objects, try again with closest neighbour until there is too much distance + const std::optional closestRefVectorPosition{ _network->getClosestRefVectorPosition(searchedRefVectorsPosition, _networkRefVectorsDistanceMedian * 0.75) }; + if (!closestRefVectorPosition) + break; + + core::utils::push_back_if_not_present(searchedRefVectorsPosition, closestRefVectorPosition.value()); + } + + return res; + } } - -template -std::vector -FeaturesEngine::getObjectsIds(const std::vector& positions, const ObjectMatrix& objectMatrix) -{ - std::vector res; - - for (const SOM::Position& position : positions) - { - for (const IdType id : objectMatrix.get(position)) - Utils::push_back_if_not_present(res, id); - } - - return res; -} - -template -std::vector -FeaturesEngine::getSimilarObjects(const std::vector& ids, - const ObjectMatrix& objectMatrix, - const ObjectPositions& objectPositions, - std::size_t maxCount) const -{ - std::vector res; - - std::vector searchedRefVectorsPosition {getMatchingRefVectorsPosition(ids, objectPositions)}; - if (searchedRefVectorsPosition.empty()) - return res; - - while (1) - { - std::vector closestObjectIds {getObjectsIds(searchedRefVectorsPosition, objectMatrix)}; - - // Remove objects that are already in input or already reported - closestObjectIds.erase(std::remove_if(std::begin(closestObjectIds), std::end(closestObjectIds), - [&](IdType id) - { - return std::find(std::cbegin(ids), std::cend(ids), id) != std::cend(ids); - }) - , std::end(closestObjectIds)); - - for (IdType id : closestObjectIds) - { - if (res.size() == maxCount) - break; - - Utils::push_back_if_not_present(res, id); - } - - if (res.size() == maxCount) - break; - - // If there is not enough objects, try again with closest neighbour until there is too much distance - const std::optional closestRefVectorPosition {_network->getClosestRefVectorPosition(searchedRefVectorsPosition, _networkRefVectorsDistanceMedian * 0.75)}; - if (!closestRefVectorPosition) - break; - - Utils::push_back_if_not_present(searchedRefVectorsPosition, closestRefVectorPosition.value()); - } - - return res; -} - -} // ns Recommendation diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngineCache.cpp b/src/libs/services/recommendation/impl/features/FeaturesEngineCache.cpp index ca2e3587..dd3279dd 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngineCache.cpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngineCache.cpp @@ -22,17 +22,17 @@ #include #include -#include "utils/IConfig.hpp" -#include "utils/ILogger.hpp" -#include "utils/Service.hpp" +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/Service.hpp" -namespace Recommendation +namespace lms::recommendation { namespace { std::filesystem::path getCacheDirectory() { - return Service::get()->getPath("working-dir") / "cache" / "features"; + return core::Service::get()->getPath("working-dir") / "cache" / "features"; } std::filesystem::path getCacheNetworkFilePath() @@ -45,7 +45,7 @@ namespace Recommendation return getCacheDirectory() / "track_positions"; } - bool networkToCacheFile(const SOM::Network& network, std::filesystem::path path) + bool networkToCacheFile(const som::Network& network, std::filesystem::path path) { try { @@ -55,12 +55,12 @@ namespace Recommendation root.put("height", network.getHeight()); root.put("dim_count", network.getInputDimCount()); - for (SOM::InputVector::value_type weight : network.getDataWeights()) + for (som::InputVector::value_type weight : network.getDataWeights()) root.add("weights.weight", weight); - for (SOM::Coordinate x = 0; x < network.getWidth(); ++x) + for (som::Coordinate x = 0; x < network.getWidth(); ++x) { - for (SOM::Coordinate y = 0; y < network.getWidth(); ++y) + for (som::Coordinate y = 0; y < network.getWidth(); ++y) { const auto& refVector = network.getRefVector({ x, y }); @@ -88,7 +88,7 @@ namespace Recommendation } } - std::optional FeaturesEngineCache::createNetworkFromCacheFile(const std::filesystem::path& path) + std::optional FeaturesEngineCache::createNetworkFromCacheFile(const std::filesystem::path& path) { if (!std::filesystem::exists(path)) return std::nullopt; @@ -101,14 +101,14 @@ namespace Recommendation boost::property_tree::read_xml(path.string(), root); - SOM::Coordinate width{ root.get("width") }; - SOM::Coordinate height{ root.get("height") }; + som::Coordinate width{ root.get("width") }; + som::Coordinate height{ root.get("height") }; std::size_t dimCount{ root.get("dim_count") }; - SOM::Network res{ width, height, dimCount }; + som::Network res{ width, height, dimCount }; { - SOM::InputVector weights{ dimCount }; + som::InputVector weights{ dimCount }; std::size_t i{}; for (const auto& val : root.get_child("weights")) weights[i++] = val.second.get_value(); @@ -118,13 +118,13 @@ namespace Recommendation for (const auto& node : root.get_child("ref_vectors")) { - SOM::Coordinate x{ node.second.get("coord_x") }; - SOM::Coordinate y{ node.second.get("coord_y") }; + som::Coordinate x{ node.second.get("coord_x") }; + som::Coordinate y{ node.second.get("coord_y") }; - SOM::InputVector refVector{ dimCount }; + som::InputVector refVector{ dimCount }; std::size_t i{}; for (const auto& val : node.second.get_child("values")) - refVector[i++] = val.second.get_value(); + refVector[i++] = val.second.get_value(); res.setRefVector({ x, y }, refVector); } @@ -152,7 +152,7 @@ namespace Recommendation node.put("id", id.getValue()); - for (const SOM::Position& position : positions) + for (const som::Position& position : positions) { boost::property_tree::ptree positionNode; positionNode.put("x", position.x); @@ -188,11 +188,11 @@ namespace Recommendation for (const auto& object : root.get_child("objects")) { - const Database::TrackId id{ object.second.get("id") }; + const db::TrackId id{ object.second.get("id") }; for (const auto& position : object.second.get_child("position")) { - auto x = position.second.get("x"); - auto y = position.second.get("y"); + auto x = position.second.get("x"); + auto y = position.second.get("y"); res[id].push_back({ x, y }); } @@ -230,7 +230,7 @@ namespace Recommendation void FeaturesEngineCache::write() const { - std::filesystem::create_directories(Service::get()->getPath("working-dir") / "cache" / "features"); + std::filesystem::create_directories(core::Service::get()->getPath("working-dir") / "cache" / "features"); if (!networkToCacheFile(_network, getCacheNetworkFilePath()) || !objectPositionToCacheFile(_trackPositions, getCacheTrackPositionsFilePath())) @@ -239,10 +239,10 @@ namespace Recommendation } } - FeaturesEngineCache::FeaturesEngineCache(SOM::Network network, TrackPositions trackPositions) + FeaturesEngineCache::FeaturesEngineCache(som::Network network, TrackPositions trackPositions) : _network{ std::move(network) }, _trackPositions{ std::move(trackPositions) } { } -} // namespace Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp b/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp index c545ebef..c83bbf50 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp @@ -25,29 +25,30 @@ #include "database/TrackId.hpp" #include "som/Network.hpp" -namespace Recommendation { - -class FeaturesEngineCache +namespace lms::recommendation { - public: - static void invalidate(); - static std::optional read(); - void write() const; + class FeaturesEngineCache + { + public: + static void invalidate(); - private: - using TrackPositions = std::unordered_map>; + static std::optional read(); + void write() const; - FeaturesEngineCache(SOM::Network network, TrackPositions trackPositions); + private: + using TrackPositions = std::unordered_map>; - static std::optional createNetworkFromCacheFile(const std::filesystem::path& path); - static std::optional createObjectPositionsFromCacheFile(const std::filesystem::path& path); - static bool objectPositionToCacheFile(const TrackPositions& trackPositions, const std::filesystem::path& path); + FeaturesEngineCache(som::Network network, TrackPositions trackPositions); - friend class FeaturesEngine; + static std::optional createNetworkFromCacheFile(const std::filesystem::path& path); + static std::optional createObjectPositionsFromCacheFile(const std::filesystem::path& path); + static bool objectPositionToCacheFile(const TrackPositions& trackPositions, const std::filesystem::path& path); - SOM::Network _network; - TrackPositions _trackPositions; -}; + friend class FeaturesEngine; -} // namespace Recommendation + som::Network _network; + TrackPositions _trackPositions; + }; + +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp index b0207491..831041c4 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp @@ -25,9 +25,9 @@ #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" -namespace Recommendation::PlaylistGeneratorConstraint +namespace lms::recommendation::PlaylistGeneratorConstraint { namespace { @@ -44,12 +44,12 @@ namespace Recommendation::PlaylistGeneratorConstraint } } - ConsecutiveArtists::ConsecutiveArtists(Database::Db& db) + ConsecutiveArtists::ConsecutiveArtists(db::Db& db) : _db {db} {} float - ConsecutiveArtists::computeScore(const std::vector& trackIds, std::size_t trackIndex) + ConsecutiveArtists::computeScore(const std::vector& trackIds, std::size_t trackIndex) { assert(!trackIds.empty()); assert(trackIndex <= trackIds.size() - 1); @@ -73,9 +73,9 @@ namespace Recommendation::PlaylistGeneratorConstraint } ArtistContainer - ConsecutiveArtists::getArtists(Database::TrackId trackId) + ConsecutiveArtists::getArtists(db::TrackId trackId) { - using namespace Database; + using namespace db; ArtistContainer res; @@ -93,5 +93,5 @@ namespace Recommendation::PlaylistGeneratorConstraint } -} // namespace Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp index f4593f18..36dc4e26 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp @@ -23,23 +23,23 @@ #include "database/ReleaseId.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Recommendation::PlaylistGeneratorConstraint +namespace lms::recommendation::PlaylistGeneratorConstraint { class ConsecutiveArtists : public IConstraint { public: - ConsecutiveArtists(Database::Db& db); + ConsecutiveArtists(db::Db& db); private: float computeScore(const TrackContainer& trackIds, std::size_t trackIndex) override; - ArtistContainer getArtists(Database::TrackId trackId); + ArtistContainer getArtists(db::TrackId trackId); - Database::Db& _db; + db::Db& _db; }; -} // namespace Recommendation::PlaylistGeneratorConstraint +} // namespace lms::recommendation::PlaylistGeneratorConstraint diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp index 1cf54dfb..9bd83231 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp @@ -23,21 +23,21 @@ #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" -namespace Recommendation::PlaylistGeneratorConstraint +namespace lms::recommendation::PlaylistGeneratorConstraint { - ConsecutiveReleases::ConsecutiveReleases(Database::Db& db) + ConsecutiveReleases::ConsecutiveReleases(db::Db& db) : _db {db} {} float - ConsecutiveReleases::computeScore(const std::vector& trackIds, std::size_t trackIndex) + ConsecutiveReleases::computeScore(const std::vector& trackIds, std::size_t trackIndex) { assert(!trackIds.empty()); assert(trackIndex <= trackIds.size() - 1); - const Database::ReleaseId releaseId {getReleaseId(trackIds[trackIndex])}; + const db::ReleaseId releaseId {getReleaseId(trackIds[trackIndex])}; constexpr std::size_t rangeSize{ 3 }; // check up to rangeSize tracks before/after the target track static_assert(rangeSize > 0); @@ -55,10 +55,10 @@ namespace Recommendation::PlaylistGeneratorConstraint return score; } - Database::ReleaseId - ConsecutiveReleases::getReleaseId(Database::TrackId trackId) + db::ReleaseId + ConsecutiveReleases::getReleaseId(db::TrackId trackId) { - using namespace Database; + using namespace db; Session& dbSession {_db.getTLSSession()}; auto transaction {dbSession.createReadTransaction()}; @@ -73,5 +73,5 @@ namespace Recommendation::PlaylistGeneratorConstraint return release->getId(); } -} // namespace Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp index 095ffc8d..7c9ace33 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp @@ -23,24 +23,24 @@ #include "database/ReleaseId.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Recommendation::PlaylistGeneratorConstraint +namespace lms::recommendation::PlaylistGeneratorConstraint { class ConsecutiveReleases : public IConstraint { public: - ConsecutiveReleases(Database::Db& db); + ConsecutiveReleases(db::Db& db); private: - float computeScore(const std::vector& trackIds, std::size_t trackIndex) override; + float computeScore(const std::vector& trackIds, std::size_t trackIndex) override; - Database::ReleaseId getReleaseId(Database::TrackId trackId); + db::ReleaseId getReleaseId(db::TrackId trackId); - Database::Db& _db; + db::Db& _db; }; -} // namespace Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.cpp b/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.cpp index e315c831..aff6c47f 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.cpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.cpp @@ -21,13 +21,13 @@ #include -namespace Recommendation::PlaylistGeneratorConstraint +namespace lms::recommendation::PlaylistGeneratorConstraint { float - DuplicateTracks::computeScore(const std::vector& trackIds, std::size_t trackIndex) + DuplicateTracks::computeScore(const std::vector& trackIds, std::size_t trackIndex) { const auto count {std::count(std::cbegin(trackIds), std::cend(trackIds), trackIds[trackIndex])}; return count == 1 ? 0 : 1000; } -} // namespace Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.hpp b/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.hpp index a5f3c821..5b76c3e5 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.hpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.hpp @@ -21,12 +21,12 @@ #include "IConstraint.hpp" -namespace Recommendation::PlaylistGeneratorConstraint +namespace lms::recommendation::PlaylistGeneratorConstraint { class DuplicateTracks : public IConstraint { private: - float computeScore(const std::vector& trackIds, std::size_t trackIndex) override; + float computeScore(const std::vector& trackIds, std::size_t trackIndex) override; }; -} // namespace Recommendation::PlaylistGeneratorConstraints +} // namespace lms::recommendation::PlaylistGeneratorConstraints diff --git a/src/libs/services/recommendation/impl/playlist-constraints/IConstraint.hpp b/src/libs/services/recommendation/impl/playlist-constraints/IConstraint.hpp index be24b393..e73c44f5 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/IConstraint.hpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/IConstraint.hpp @@ -23,7 +23,7 @@ #include "services/recommendation/Types.hpp" -namespace Recommendation::PlaylistGeneratorConstraint +namespace lms::recommendation::PlaylistGeneratorConstraint { class IConstraint { @@ -36,4 +36,4 @@ namespace Recommendation::PlaylistGeneratorConstraint // > 1 : violation virtual float computeScore(const TrackContainer& trackIds, std::size_t trackIndex) = 0; }; -} // namespace Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp b/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp index 3304f2c8..54d59ef4 100644 --- a/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp +++ b/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp @@ -24,12 +24,12 @@ #include "database/Types.hpp" #include "services/recommendation/Types.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Recommendation +namespace lms::recommendation { class IRecommendationService; class IPlaylistGeneratorService @@ -38,9 +38,9 @@ namespace Recommendation virtual ~IPlaylistGeneratorService() = default; // extend an existing playlist with similar tracks (but use playlist contraints) - virtual TrackContainer extendPlaylist(Database::TrackListId tracklistId, std::size_t maxCount) const = 0; + virtual TrackContainer extendPlaylist(db::TrackListId tracklistId, std::size_t maxCount) const = 0; }; - std::unique_ptr createPlaylistGeneratorService(Database::Db& db, IRecommendationService& recommandationService); + std::unique_ptr createPlaylistGeneratorService(db::Db& db, IRecommendationService& recommandationService); } // ns Recommendation diff --git a/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp b/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp index 2bd70f72..31bb8dea 100644 --- a/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp +++ b/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp @@ -21,17 +21,17 @@ #include #include -#include "utils/EnumSet.hpp" +#include "core/EnumSet.hpp" #include "database/TrackListId.hpp" #include "database/Types.hpp" #include "services/recommendation/Types.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Recommendation +namespace lms::recommendation { class IRecommendationService { @@ -40,12 +40,12 @@ namespace Recommendation virtual void load() = 0; - virtual TrackContainer findSimilarTracks(Database::TrackListId tracklistId, std::size_t maxCount) const = 0; - virtual TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const = 0; - virtual ReleaseContainer getSimilarReleases(Database::ReleaseId releaseId, std::size_t maxCount) const = 0; - virtual ArtistContainer getSimilarArtists(Database::ArtistId artistId, EnumSet linkTypes, std::size_t maxCount) const = 0; + virtual TrackContainer findSimilarTracks(db::TrackListId tracklistId, std::size_t maxCount) const = 0; + virtual TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const = 0; + virtual ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const = 0; + virtual ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const = 0; }; - std::unique_ptr createRecommendationService(Database::Db& db); + std::unique_ptr createRecommendationService(db::Db& db); } // ns Recommendation diff --git a/src/libs/services/recommendation/include/services/recommendation/Types.hpp b/src/libs/services/recommendation/include/services/recommendation/Types.hpp index 7cfebeb1..84fe3775 100644 --- a/src/libs/services/recommendation/include/services/recommendation/Types.hpp +++ b/src/libs/services/recommendation/include/services/recommendation/Types.hpp @@ -5,7 +5,7 @@ #include "database/ReleaseId.hpp" #include "database/TrackId.hpp" -namespace Recommendation +namespace lms::recommendation { struct Progress { @@ -17,8 +17,8 @@ namespace Recommendation template using ResultContainer = std::vector; - using ArtistContainer = ResultContainer; - using ReleaseContainer = ResultContainer; - using TrackContainer = ResultContainer; + using ArtistContainer = ResultContainer; + using ReleaseContainer = ResultContainer; + using TrackContainer = ResultContainer; -} // namespace Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/scanner/CMakeLists.txt b/src/libs/services/scanner/CMakeLists.txt index 8a0508e9..7b81b49e 100644 --- a/src/libs/services/scanner/CMakeLists.txt +++ b/src/libs/services/scanner/CMakeLists.txt @@ -21,7 +21,7 @@ target_link_libraries(lmsscanner PRIVATE lmsdatabase lmsmetadata lmsrecommendation - lmsutils + lmscore ) target_link_libraries(lmsscanner PUBLIC diff --git a/src/libs/services/scanner/impl/IScanStep.hpp b/src/libs/services/scanner/impl/IScanStep.hpp index f7253002..e131da4b 100644 --- a/src/libs/services/scanner/impl/IScanStep.hpp +++ b/src/libs/services/scanner/impl/IScanStep.hpp @@ -24,7 +24,7 @@ #include "services/scanner/ScannerStats.hpp" -namespace Scanner +namespace lms::scanner { class IScanStep { diff --git a/src/libs/services/scanner/impl/ScanStepBase.hpp b/src/libs/services/scanner/impl/ScanStepBase.hpp index 6e774e60..21bd8dd5 100644 --- a/src/libs/services/scanner/impl/ScanStepBase.hpp +++ b/src/libs/services/scanner/impl/ScanStepBase.hpp @@ -25,12 +25,12 @@ #include "IScanStep.hpp" #include "ScannerSettings.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Scanner +namespace lms::scanner { class ScanStepBase : public IScanStep { @@ -43,7 +43,7 @@ namespace Scanner const ScannerSettings& settings; ProgressCallback progressCallback; bool& abortScan; - Database::Db& db; + db::Db& db; }; ScanStepBase(InitParams& initParams) : _settings {initParams.settings} @@ -56,6 +56,6 @@ namespace Scanner const ScannerSettings& _settings; ProgressCallback _progressCallback; bool& _abortScan; - Database::Db& _db; + db::Db& _db; }; } diff --git a/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.cpp b/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.cpp index dd54f90c..938af789 100644 --- a/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.cpp @@ -22,13 +22,13 @@ #include "database/Db.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" -namespace Scanner +namespace lms::scanner { void ScanStepCheckDuplicatedDbFiles::process(ScanContext& context) { - using namespace Database; + using namespace db; if (_abortScan) return; diff --git a/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.hpp b/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.hpp index 655ed863..3599791d 100644 --- a/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.hpp +++ b/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.hpp @@ -21,7 +21,7 @@ #include "ScanStepBase.hpp" -namespace Scanner +namespace lms::scanner { class ScanStepCheckDuplicatedDbFiles : public ScanStepBase { diff --git a/src/libs/services/scanner/impl/ScanStepComputeClusterStats.cpp b/src/libs/services/scanner/impl/ScanStepComputeClusterStats.cpp index b6944f61..a62d354e 100644 --- a/src/libs/services/scanner/impl/ScanStepComputeClusterStats.cpp +++ b/src/libs/services/scanner/impl/ScanStepComputeClusterStats.cpp @@ -21,14 +21,14 @@ #include "database/Db.hpp" #include "database/Cluster.hpp" #include "database/Session.hpp" -#include "utils/ILogger.hpp" -#include "utils/Path.hpp" +#include "core/ILogger.hpp" +#include "core/Path.hpp" -namespace Scanner +namespace lms::scanner { void ScanStepComputeClusterStats::process(ScanContext& context) { - using namespace Database; + using namespace db; if (context.stats.nbChanges() == 0) return; diff --git a/src/libs/services/scanner/impl/ScanStepComputeClusterStats.hpp b/src/libs/services/scanner/impl/ScanStepComputeClusterStats.hpp index 4ccd460d..8000f454 100644 --- a/src/libs/services/scanner/impl/ScanStepComputeClusterStats.hpp +++ b/src/libs/services/scanner/impl/ScanStepComputeClusterStats.hpp @@ -21,7 +21,7 @@ #include "ScanStepBase.hpp" -namespace Scanner +namespace lms::scanner { class ScanStepComputeClusterStats : public ScanStepBase { diff --git a/src/libs/services/scanner/impl/ScanStepDiscoverFiles.cpp b/src/libs/services/scanner/impl/ScanStepDiscoverFiles.cpp index 3904399a..d582bf18 100644 --- a/src/libs/services/scanner/impl/ScanStepDiscoverFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepDiscoverFiles.cpp @@ -19,10 +19,10 @@ #include "ScanStepDiscoverFiles.hpp" -#include "utils/ILogger.hpp" -#include "utils/Path.hpp" +#include "core/ILogger.hpp" +#include "core/Path.hpp" -namespace Scanner +namespace lms::scanner { void ScanStepDiscoverFiles::process(ScanContext& context) { @@ -31,12 +31,12 @@ namespace Scanner for (const ScannerSettings::MediaLibraryInfo& mediaLibrary : _settings.mediaLibraries) { std::size_t currentDirectoryProcessElemsCount{}; - PathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) + core::pathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) { if (_abortScan) return false; - if (!ec && PathUtils::hasFileAnyExtension(path, _settings.supportedExtensions)) + if (!ec && core::pathUtils::hasFileAnyExtension(path, _settings.supportedExtensions)) { context.currentStepStats.processedElems++; currentDirectoryProcessElemsCount++; diff --git a/src/libs/services/scanner/impl/ScanStepDiscoverFiles.hpp b/src/libs/services/scanner/impl/ScanStepDiscoverFiles.hpp index 1e17ca34..60f3e240 100644 --- a/src/libs/services/scanner/impl/ScanStepDiscoverFiles.hpp +++ b/src/libs/services/scanner/impl/ScanStepDiscoverFiles.hpp @@ -21,7 +21,7 @@ #include "ScanStepBase.hpp" -namespace Scanner +namespace lms::scanner { class ScanStepDiscoverFiles : public ScanStepBase { diff --git a/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.cpp b/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.cpp index a0fa888f..b0f9901e 100644 --- a/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.cpp @@ -25,12 +25,12 @@ #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "utils/ILogger.hpp" -#include "utils/Path.hpp" +#include "core/ILogger.hpp" +#include "core/Path.hpp" -namespace Scanner +namespace lms::scanner { - using namespace Database; + using namespace db; namespace { @@ -81,7 +81,7 @@ namespace Scanner void ScanStepRemoveOrphanDbFiles::removeOrphanTracks(ScanContext& context) { - using namespace Database; + using namespace db; if (_abortScan) return; @@ -150,25 +150,25 @@ namespace Scanner void ScanStepRemoveOrphanDbFiles::removeOrphanClusters() { LMS_LOG(DBUPDATER, DEBUG, "Checking orphan clusters..."); - removeOrphanEntries(_db.getTLSSession(), _abortScan); + removeOrphanEntries(_db.getTLSSession(), _abortScan); } void ScanStepRemoveOrphanDbFiles::removeOrphanClusterTypes() { LMS_LOG(DBUPDATER, DEBUG, "Checking orphan cluster types..."); - removeOrphanEntries(_db.getTLSSession(), _abortScan); + removeOrphanEntries(_db.getTLSSession(), _abortScan); } void ScanStepRemoveOrphanDbFiles::removeOrphanArtists() { LMS_LOG(DBUPDATER, DEBUG, "Checking orphan artists..."); - removeOrphanEntries(_db.getTLSSession(), _abortScan); + removeOrphanEntries(_db.getTLSSession(), _abortScan); } void ScanStepRemoveOrphanDbFiles::removeOrphanReleases() { LMS_LOG(DBUPDATER, DEBUG, "Checking orphan releases..."); - removeOrphanEntries(_db.getTLSSession(), _abortScan); + removeOrphanEntries(_db.getTLSSession(), _abortScan); } bool ScanStepRemoveOrphanDbFiles::checkFile(const std::filesystem::path& p) @@ -186,14 +186,14 @@ namespace Scanner if (std::none_of(std::cbegin(_settings.mediaLibraries), std::cend(_settings.mediaLibraries), [&](const ScannerSettings::MediaLibraryInfo& libraryInfo) { - return PathUtils::isPathInRootPath(p, libraryInfo.rootDirectory, &excludeDirFileName); + return core::pathUtils::isPathInRootPath(p, libraryInfo.rootDirectory, &excludeDirFileName); })) { LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': out of media directory"); return false; } - if (!PathUtils::hasFileAnyExtension(p, _settings.supportedExtensions)) + if (!core::pathUtils::hasFileAnyExtension(p, _settings.supportedExtensions)) { LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': file format no longer handled"); return false; diff --git a/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.hpp b/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.hpp index 11f78385..5b56fd90 100644 --- a/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.hpp +++ b/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.hpp @@ -23,7 +23,7 @@ #include "ScanStepBase.hpp" -namespace Scanner +namespace lms::scanner { class ScanStepRemoveOrphanDbFiles : public ScanStepBase { diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp index 54cc7b06..3513ca78 100644 --- a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp @@ -30,19 +30,19 @@ #include "database/TrackArtistLink.hpp" #include "metadata/Exception.hpp" #include "metadata/IParser.hpp" -#include "utils/Exception.hpp" -#include "utils/IConfig.hpp" -#include "utils/ILogger.hpp" -#include "utils/Path.hpp" -#include "utils/ITraceLogger.hpp" +#include "core/Exception.hpp" +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/Path.hpp" +#include "core/ITraceLogger.hpp" -namespace Scanner +namespace lms::scanner { - using namespace Database; + using namespace db; namespace { - Artist::pointer createArtist(Session& session, const MetaData::Artist& artistInfo) + Artist::pointer createArtist(Session& session, const metadata::Artist& artistInfo) { Artist::pointer artist{ session.create(artistInfo.name) }; @@ -54,7 +54,7 @@ namespace Scanner return artist; } - void updateArtistIfNeeded(Artist::pointer artist, const MetaData::Artist& artistInfo) + void updateArtistIfNeeded(Artist::pointer artist, const metadata::Artist& artistInfo) { // Name may have been updated if (artist->getName() != artistInfo.name) @@ -69,11 +69,11 @@ namespace Scanner } } - std::vector getOrCreateArtists(Session& session, const std::vector& artistsInfo, bool allowFallbackOnMBIDEntries) + std::vector getOrCreateArtists(Session& session, const std::vector& artistsInfo, bool allowFallbackOnMBIDEntries) { std::vector artists; - for (const MetaData::Artist& artistInfo : artistsInfo) + for (const metadata::Artist& artistInfo : artistsInfo) { Artist::pointer artist; @@ -126,7 +126,7 @@ namespace Scanner return releaseType; } - void updateReleaseIfNeeded(Session& session, Release::pointer release, const MetaData::Release& releaseInfo) + void updateReleaseIfNeeded(Session& session, Release::pointer release, const metadata::Release& releaseInfo) { if (release->getName() != releaseInfo.name) release.modify()->setName(releaseInfo.name); @@ -146,7 +146,7 @@ namespace Scanner } } - Release::pointer getOrCreateRelease(Session& session, const MetaData::Release& releaseInfo, const std::filesystem::path& expectedReleaseDirectory) + Release::pointer getOrCreateRelease(Session& session, const metadata::Release& releaseInfo, const std::filesystem::path& expectedReleaseDirectory) { Release::pointer release; @@ -185,7 +185,7 @@ namespace Scanner return Release::pointer{}; } - std::vector getOrCreateClusters(Session& session, const MetaData::Track& track) + std::vector getOrCreateClusters(Session& session, const metadata::Track& track) { std::vector clusters; @@ -217,23 +217,23 @@ namespace Scanner return clusters; } - MetaData::ParserReadStyle getParserReadStyle() + metadata::ParserReadStyle getParserReadStyle() { - std::string_view readStyle{ Service::get()->getString("scanner-parser-read-style", "average") }; + std::string_view readStyle{ core::Service::get()->getString("scanner-parser-read-style", "average") }; if (readStyle == "fast") - return MetaData::ParserReadStyle::Fast; + return metadata::ParserReadStyle::Fast; else if (readStyle == "average") - return MetaData::ParserReadStyle::Average; + return metadata::ParserReadStyle::Average; else if (readStyle == "accurate") - return MetaData::ParserReadStyle::Accurate; + return metadata::ParserReadStyle::Accurate; - throw LmsException{ "Invalid value for 'scanner-parser-read-style'" }; + throw core::LmsException{ "Invalid value for 'scanner-parser-read-style'" }; } std::size_t getScanMetaDataThreadCount() { - std::size_t threadCount{ Service::get()->getULong("scanner-metadata-thread-count", 0) }; + std::size_t threadCount{ core::Service::get()->getULong("scanner-metadata-thread-count", 0) }; if (threadCount == 0) threadCount = std::max(std::thread::hardware_concurrency() / 2, 1); @@ -242,7 +242,7 @@ namespace Scanner } } // namespace - ScanStepScanFiles::MetadataScanQueue::MetadataScanQueue(MetaData::IParser& parser, std::size_t threadCount, bool& abort) + ScanStepScanFiles::MetadataScanQueue::MetadataScanQueue(metadata::IParser& parser, std::size_t threadCount, bool& abort) : _metadataParser{ parser } , _scanContextRunner{ _scanContext, threadCount, "ScannerMetadata" } , _abort{ abort } @@ -259,7 +259,7 @@ namespace Scanner { LMS_SCOPED_TRACE_OVERVIEW("Scanner", "AudioFileParseJob"); - std::unique_ptr track; + std::unique_ptr track; if (_abort) { @@ -272,7 +272,7 @@ namespace Scanner { track = _metadataParser.parse(path); } - catch (const MetaData::Exception& e) + catch (const metadata::Exception& e) { LMS_LOG(DBUPDATER, INFO, "Failed to parse '" << path.string() << "'"); } @@ -323,7 +323,7 @@ namespace Scanner ScanStepScanFiles::ScanStepScanFiles(InitParams& initParams) : ScanStepBase{ initParams } - , _metadataParser{ MetaData::createParser(MetaData::ParserBackend::TagLib, getParserReadStyle()) } // For now, always use TagLib + , _metadataParser{ metadata::createParser(metadata::ParserBackend::TagLib, getParserReadStyle()) } // For now, always use TagLib , _metadataScanQueue{ *_metadataParser, getScanMetaDataThreadCount(), _abortScan } { LMS_LOG(DBUPDATER, INFO, "Using " << _metadataScanQueue.getThreadCount() << " thread(s) for scanning file metadata"); @@ -347,7 +347,7 @@ namespace Scanner for (const ScannerSettings::MediaLibraryInfo& mediaLibrary : _settings.mediaLibraries) { - PathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) + core::pathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) { LMS_SCOPED_TRACE_DETAILED("Scanner", "OnExploreFile"); @@ -359,7 +359,7 @@ namespace Scanner LMS_LOG(DBUPDATER, ERROR, "Cannot process entry '" << path.string() << "': " << ec.message()); context.stats.errors.emplace_back(ScanError{ path, ScanErrorType::CannotReadFile, ec.message() }); } - else if (PathUtils::hasFileAnyExtension(path, _settings.supportedExtensions)) + else if (core::pathUtils::hasFileAnyExtension(path, _settings.supportedExtensions)) { if (checkFileNeedScan(context, path, mediaLibrary)) _metadataScanQueue.pushScanRequest(path); @@ -392,9 +392,9 @@ namespace Scanner Wt::WDateTime lastWriteTime; try { - lastWriteTime = PathUtils::getLastWriteTime(file); + lastWriteTime = core::pathUtils::getLastWriteTime(file); } - catch (LmsException& e) + catch (core::LmsException& e) { LMS_LOG(DBUPDATER, ERROR, e.what()); stats.skips++; @@ -405,7 +405,7 @@ namespace Scanner if (!context.forceScan) { // Skip file if last write is the same - Database::Session& dbSession{ _db.getTLSSession() }; + db::Session& dbSession{ _db.getTLSSession() }; auto transaction{ _db.getTLSSession().createReadTransaction() }; const Track::pointer track{ Track::findByPath(dbSession, file) }; @@ -429,12 +429,12 @@ namespace Scanner if (needUpdateLibrary) { - Database::Session& dbSession{ _db.getTLSSession() }; + db::Session& dbSession{ _db.getTLSSession() }; auto transaction{ _db.getTLSSession().createWriteTransaction() }; Track::pointer track{ Track::findByPath(dbSession, file) }; assert(track); - track.modify()->setMediaLibrary(Database::MediaLibrary::find(dbSession, libraryInfo.id)); // may be null, will be handled in the next scan anyway + track.modify()->setMediaLibrary(db::MediaLibrary::find(dbSession, libraryInfo.id)); // may be null, will be handled in the next scan anyway stats.updates++; return false; } @@ -446,7 +446,7 @@ namespace Scanner { LMS_SCOPED_TRACE_OVERVIEW("Scanner", "ProcessScanResults"); - Database::Session& dbSession{ _db.getTLSSession() }; + db::Session& dbSession{ _db.getTLSSession() }; auto transaction{ dbSession.createWriteTransaction() }; for (const MetaDataScanResult& scanResult : scanResults) @@ -471,22 +471,22 @@ namespace Scanner } } - void ScanStepScanFiles::processFileMetaData(ScanContext& context, const std::filesystem::path& file, const MetaData::Track& trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo) + void ScanStepScanFiles::processFileMetaData(ScanContext& context, const std::filesystem::path& file, const metadata::Track& trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo) { ScanStats& stats{ context.stats }; Wt::WDateTime lastWriteTime; try { - lastWriteTime = PathUtils::getLastWriteTime(file); + lastWriteTime = core::pathUtils::getLastWriteTime(file); } - catch (LmsException& e) + catch (core::LmsException& e) { LMS_LOG(DBUPDATER, ERROR, e.what()); stats.skips++; return; } - Database::Session& dbSession{ _db.getTLSSession() }; + db::Session& dbSession{ _db.getTLSSession() }; Track::pointer track{ Track::findByPath(dbSession, file) }; if (trackMetadata.mbid && (!track || _settings.skipDuplicateMBID)) @@ -519,7 +519,7 @@ namespace Scanner if (std::none_of(std::cbegin(_settings.mediaLibraries), std::cend(_settings.mediaLibraries), [&](const ScannerSettings::MediaLibraryInfo& libraryInfo) { - return PathUtils::isPathInRootPath(file, libraryInfo.rootDirectory, &excludeDirFileName); + return core::pathUtils::isPathInRootPath(file, libraryInfo.rootDirectory, &excludeDirFileName); })) { continue; diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.hpp b/src/libs/services/scanner/impl/ScanStepScanFiles.hpp index 23fbe661..d656ea99 100644 --- a/src/libs/services/scanner/impl/ScanStepScanFiles.hpp +++ b/src/libs/services/scanner/impl/ScanStepScanFiles.hpp @@ -28,10 +28,10 @@ #include #include "metadata/IParser.hpp" -#include "utils/IOContextRunner.hpp" +#include "core/IOContextRunner.hpp" #include "ScanStepBase.hpp" -namespace Scanner +namespace lms::scanner { class ScanStepScanFiles : public ScanStepBase { @@ -47,18 +47,18 @@ namespace Scanner struct MetaDataScanResult { std::filesystem::path path; - std::unique_ptr trackMetaData; + std::unique_ptr trackMetaData; }; void processMetaDataScanResults(ScanContext& context, std::span scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo); - void processFileMetaData(ScanContext& context, const std::filesystem::path& file, const MetaData::Track& trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo); + void processFileMetaData(ScanContext& context, const std::filesystem::path& file, const metadata::Track& trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo); - std::unique_ptr _metadataParser; + std::unique_ptr _metadataParser; const std::vector _extraTagsToParse; class MetadataScanQueue { public: - MetadataScanQueue(MetaData::IParser& parser, std::size_t threadCount, bool& abort); + MetadataScanQueue(metadata::IParser& parser, std::size_t threadCount, bool& abort); std::size_t getThreadCount() const { return _scanContextRunner.getThreadCount(); } @@ -70,9 +70,9 @@ namespace Scanner void wait(std::size_t maxScanRequestCount = 0); // wait until ongoing scan request count <= maxScanRequestCount private: - MetaData::IParser& _metadataParser; + metadata::IParser& _metadataParser; boost::asio::io_context _scanContext; - IOContextRunner _scanContextRunner; + core::IOContextRunner _scanContextRunner; mutable std::mutex _mutex ; std::size_t _ongoingScanCount{}; diff --git a/src/libs/services/scanner/impl/ScannerService.cpp b/src/libs/services/scanner/impl/ScannerService.cpp index 3f4d3ca8..96ef1c41 100644 --- a/src/libs/services/scanner/impl/ScannerService.cpp +++ b/src/libs/services/scanner/impl/ScannerService.cpp @@ -25,11 +25,10 @@ #include "database/MediaLibrary.hpp" #include "database/TrackFeatures.hpp" #include "database/ScanSettings.hpp" -#include "utils/Exception.hpp" -#include "utils/IConfig.hpp" -#include "utils/ILogger.hpp" -#include "utils/Path.hpp" -#include "utils/Tuple.hpp" +#include "core/Exception.hpp" +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/Path.hpp" #include "ScanStepCheckDuplicatedDbFiles.hpp" #include "ScanStepDiscoverFiles.hpp" @@ -37,9 +36,9 @@ #include "ScanStepScanFiles.hpp" #include "ScanStepComputeClusterStats.hpp" -namespace Scanner +namespace lms::scanner { - using namespace Database; + using namespace db; namespace { @@ -357,7 +356,7 @@ namespace Scanner { ScannerSettings newSettings; - newSettings.skipDuplicateMBID = Service::get()->getBool("scanner-skip-duplicate-mbid", false); + newSettings.skipDuplicateMBID = core::Service::get()->getBool("scanner-skip-duplicate-mbid", false); { auto transaction{ _dbSession.createReadTransaction() }; @@ -371,7 +370,7 @@ namespace Scanner const auto fileExtensions{ scanSettings->getAudioFileExtensions() }; newSettings.supportedExtensions.reserve(fileExtensions.size()); std::transform(std::cbegin(fileExtensions), std::end(fileExtensions), std::back_inserter(newSettings.supportedExtensions), - [](const std::filesystem::path& extension) { return std::filesystem::path{ StringUtils::stringToLower(extension.string()) }; }); + [](const std::filesystem::path& extension) { return std::filesystem::path{ core::stringUtils::stringToLower(extension.string()) }; }); } MediaLibrary::find(_dbSession, [&](const MediaLibrary::pointer& mediaLibrary) @@ -411,4 +410,4 @@ namespace Scanner notifyInProgress(stepStats); } -} // namespace Scanner +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScannerService.hpp b/src/libs/services/scanner/impl/ScannerService.hpp index 6d0ff1b0..dec1c26c 100644 --- a/src/libs/services/scanner/impl/ScannerService.hpp +++ b/src/libs/services/scanner/impl/ScannerService.hpp @@ -34,16 +34,16 @@ #include "database/Session.hpp" #include "database/Types.hpp" #include "services/scanner/IScannerService.hpp" -#include "utils/Path.hpp" +#include "core/Path.hpp" #include "IScanStep.hpp" #include "ScannerSettings.hpp" -namespace Scanner +namespace lms::scanner { class ScannerService : public IScannerService { public: - ScannerService(Database::Db& db); + ScannerService(db::Db& db); ~ScannerService(); ScannerService(const ScannerService&) = delete; @@ -88,8 +88,8 @@ namespace Scanner boost::asio::system_timer _scheduleTimer{ _ioService }; Events _events; std::chrono::system_clock::time_point _lastScanInProgressEmit{}; - Database::Db& _db; - Database::Session _dbSession; + db::Db& _db; + db::Session _dbSession; mutable std::shared_mutex _statusMutex; State _curState{ State::NotScheduled }; diff --git a/src/libs/services/scanner/impl/ScannerSettings.hpp b/src/libs/services/scanner/impl/ScannerSettings.hpp index 603a976f..2186ddb1 100644 --- a/src/libs/services/scanner/impl/ScannerSettings.hpp +++ b/src/libs/services/scanner/impl/ScannerSettings.hpp @@ -26,13 +26,13 @@ #include "database/MediaLibraryId.hpp" #include "database/ScanSettings.hpp" -namespace Scanner +namespace lms::scanner { struct ScannerSettings { std::size_t scanVersion{}; Wt::WTime startTime; - Database::ScanSettings::UpdatePeriod updatePeriod{ Database::ScanSettings::UpdatePeriod::Never }; + db::ScanSettings::UpdatePeriod updatePeriod{ db::ScanSettings::UpdatePeriod::Never }; std::vector supportedExtensions; bool skipDuplicateMBID{}; std::vector extraTags; @@ -41,7 +41,7 @@ namespace Scanner struct MediaLibraryInfo { - Database::MediaLibraryId id; + db::MediaLibraryId id; std::filesystem::path rootDirectory; bool operator<=>(const MediaLibraryInfo& other) const = default; diff --git a/src/libs/services/scanner/impl/ScannerStats.cpp b/src/libs/services/scanner/impl/ScannerStats.cpp index e4b71180..ad68a8fe 100644 --- a/src/libs/services/scanner/impl/ScannerStats.cpp +++ b/src/libs/services/scanner/impl/ScannerStats.cpp @@ -19,7 +19,7 @@ #include "services/scanner/ScannerStats.hpp" -namespace Scanner { +namespace lms::scanner { ScanError::ScanError(const std::filesystem::path& _file, ScanErrorType _error, const std::string& _systemError) : file {_file}, @@ -46,5 +46,5 @@ ScanStepStats::progress() const return (processedElems / static_cast(totalElems ? totalElems : 1)) * 100; } -} // namespace Scanner +} // namespace lms::scanner diff --git a/src/libs/services/scanner/include/services/scanner/IScannerService.hpp b/src/libs/services/scanner/include/services/scanner/IScannerService.hpp index af248408..f6906835 100644 --- a/src/libs/services/scanner/include/services/scanner/IScannerService.hpp +++ b/src/libs/services/scanner/include/services/scanner/IScannerService.hpp @@ -24,12 +24,12 @@ #include "ScannerEvents.hpp" #include "ScannerStats.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Scanner +namespace lms::scanner { class IScannerService @@ -62,7 +62,7 @@ namespace Scanner virtual Events& getEvents() = 0; }; - std::unique_ptr createScannerService(Database::Db& db); + std::unique_ptr createScannerService(db::Db& db); } // Scanner diff --git a/src/libs/services/scanner/include/services/scanner/ScannerEvents.hpp b/src/libs/services/scanner/include/services/scanner/ScannerEvents.hpp index 6de2505a..19dfe89d 100644 --- a/src/libs/services/scanner/include/services/scanner/ScannerEvents.hpp +++ b/src/libs/services/scanner/include/services/scanner/ScannerEvents.hpp @@ -24,7 +24,7 @@ #include "ScannerStats.hpp" -namespace Scanner +namespace lms::scanner { struct Events diff --git a/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp b/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp index 62c25ef8..02094980 100644 --- a/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp +++ b/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp @@ -26,7 +26,7 @@ #include "database/TrackId.hpp" -namespace Scanner +namespace lms::scanner { enum class ScanErrorType { @@ -53,7 +53,7 @@ namespace Scanner struct ScanDuplicate { - Database::TrackId trackId; + db::TrackId trackId; DuplicateReason reason; }; @@ -104,5 +104,5 @@ namespace Scanner std::size_t nbFiles() const; std::size_t nbChanges() const; }; -} // namespace Scanner +} // namespace lms::scanner diff --git a/src/libs/services/scrobbling/CMakeLists.txt b/src/libs/services/scrobbling/CMakeLists.txt index 2f78000a..5021f6a1 100644 --- a/src/libs/services/scrobbling/CMakeLists.txt +++ b/src/libs/services/scrobbling/CMakeLists.txt @@ -19,7 +19,7 @@ target_include_directories(lmsscrobbling PRIVATE ) target_link_libraries(lmsscrobbling PRIVATE - lmsutils + lmscore ) target_link_libraries(lmsscrobbling PUBLIC diff --git a/src/libs/services/scrobbling/impl/IScrobblingBackend.hpp b/src/libs/services/scrobbling/impl/IScrobblingBackend.hpp index e8db366d..99b1805e 100644 --- a/src/libs/services/scrobbling/impl/IScrobblingBackend.hpp +++ b/src/libs/services/scrobbling/impl/IScrobblingBackend.hpp @@ -25,14 +25,14 @@ #include "services/scrobbling/Listen.hpp" -namespace Database +namespace lms::db { class Session; class TrackList; class User; } -namespace Scrobbling +namespace lms::scrobbling { class IScrobblingBackend { diff --git a/src/libs/services/scrobbling/impl/ScrobblingService.cpp b/src/libs/services/scrobbling/impl/ScrobblingService.cpp index acbf32ff..f72b90ed 100644 --- a/src/libs/services/scrobbling/impl/ScrobblingService.cpp +++ b/src/libs/services/scrobbling/impl/ScrobblingService.cpp @@ -26,20 +26,20 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "internal/InternalBackend.hpp" #include "listenbrainz/ListenBrainzBackend.hpp" -namespace Scrobbling +namespace lms::scrobbling { - using namespace Database; + using namespace db; namespace { - Database::Listen::StatsFindParameters convertToListenFindParameters(const ScrobblingService::FindParameters& params) + db::Listen::StatsFindParameters convertToListenFindParameters(const ScrobblingService::FindParameters& params) { - Database::Listen::StatsFindParameters listenFindParams; + db::Listen::StatsFindParameters listenFindParams; listenFindParams.setUser(params.user); listenFindParams.setClusters(params.clusters); listenFindParams.setRange(params.range); @@ -49,9 +49,9 @@ namespace Scrobbling return listenFindParams; } - Database::Listen::ArtistStatsFindParameters convertToListenFindParameters(const ScrobblingService::ArtistFindParameters& params) + db::Listen::ArtistStatsFindParameters convertToListenFindParameters(const ScrobblingService::ArtistFindParameters& params) { - return Database::Listen::ArtistStatsFindParameters{ convertToListenFindParameters(static_cast(params)), params.linkType }; + return db::Listen::ArtistStatsFindParameters{ convertToListenFindParameters(static_cast(params)), params.linkType }; } } @@ -65,7 +65,7 @@ namespace Scrobbling { LMS_LOG(SCROBBLING, INFO, "Starting service..."); _scrobblingBackends.emplace(ScrobblingBackend::Internal, std::make_unique(_db)); - _scrobblingBackends.emplace(ScrobblingBackend::ListenBrainz, std::make_unique(ioContext, _db)); + _scrobblingBackends.emplace(ScrobblingBackend::ListenBrainz, std::make_unique(ioContext, _db)); LMS_LOG(SCROBBLING, INFO, "Service started!"); } @@ -112,13 +112,13 @@ namespace Scrobbling if (!backend) return res; - Database::Listen::ArtistStatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; + db::Listen::ArtistStatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; listenFindParams.setScrobblingBackend(backend); Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - res = Database::Listen::getRecentArtists(session, listenFindParams); + res = db::Listen::getRecentArtists(session, listenFindParams); return res; } @@ -130,13 +130,13 @@ namespace Scrobbling if (!backend) return res; - Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; + db::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; listenFindParams.setScrobblingBackend(backend); Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - res = Database::Listen::getRecentReleases(session, listenFindParams); + res = db::Listen::getRecentReleases(session, listenFindParams); return res; } @@ -148,31 +148,31 @@ namespace Scrobbling if (!backend) return res; - Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; + db::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; listenFindParams.setScrobblingBackend(backend); Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - res = Database::Listen::getRecentTracks(session, listenFindParams); + res = db::Listen::getRecentTracks(session, listenFindParams); return res; } - std::size_t ScrobblingService::getCount(Database::UserId userId, Database::ReleaseId releaseId) + std::size_t ScrobblingService::getCount(db::UserId userId, db::ReleaseId releaseId) { Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - return Database::Listen::getCount(session, userId, releaseId); + return db::Listen::getCount(session, userId, releaseId); } - std::size_t ScrobblingService::getCount(Database::UserId userId, Database::TrackId trackId) + std::size_t ScrobblingService::getCount(db::UserId userId, db::TrackId trackId) { Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - return Database::Listen::getCount(session, userId, trackId); + return db::Listen::getCount(session, userId, trackId); } - Wt::WDateTime ScrobblingService::getLastListenDateTime(Database::UserId userId, Database::ReleaseId releaseId) + Wt::WDateTime ScrobblingService::getLastListenDateTime(db::UserId userId, db::ReleaseId releaseId) { const auto backend{ getUserBackend(userId) }; if (!backend) @@ -181,11 +181,11 @@ namespace Scrobbling Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - const Database::Listen::pointer listen{ Database::Listen::getMostRecentListen(session, userId, *backend, releaseId) }; + const db::Listen::pointer listen{ db::Listen::getMostRecentListen(session, userId, *backend, releaseId) }; return listen ? listen->getDateTime() : Wt::WDateTime{}; } - Wt::WDateTime ScrobblingService::getLastListenDateTime(Database::UserId userId, Database::TrackId trackId) + Wt::WDateTime ScrobblingService::getLastListenDateTime(db::UserId userId, db::TrackId trackId) { const auto backend{ getUserBackend(userId) }; if (!backend) @@ -194,7 +194,7 @@ namespace Scrobbling Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - const Database::Listen::pointer listen{ Database::Listen::getMostRecentListen(session, userId, *backend, trackId) }; + const db::Listen::pointer listen{ db::Listen::getMostRecentListen(session, userId, *backend, trackId) }; return listen ? listen->getDateTime() : Wt::WDateTime{}; } @@ -207,13 +207,13 @@ namespace Scrobbling if (!backend) return res; - Database::Listen::ArtistStatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; + db::Listen::ArtistStatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; listenFindParams.setScrobblingBackend(backend); Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - res = Database::Listen::getTopArtists(session, listenFindParams); + res = db::Listen::getTopArtists(session, listenFindParams); return res; } @@ -225,13 +225,13 @@ namespace Scrobbling if (!backend) return res; - Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; + db::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; listenFindParams.setScrobblingBackend(backend); Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - res = Database::Listen::getTopReleases(session, listenFindParams); + res = db::Listen::getTopReleases(session, listenFindParams); return res; } @@ -243,13 +243,13 @@ namespace Scrobbling if (!backend) return res; - Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; + db::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) }; listenFindParams.setScrobblingBackend(backend); Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - res = Database::Listen::getTopTracks(session, listenFindParams); + res = db::Listen::getTopTracks(session, listenFindParams); return res; } } // ns Scrobbling diff --git a/src/libs/services/scrobbling/impl/ScrobblingService.hpp b/src/libs/services/scrobbling/impl/ScrobblingService.hpp index d7c7345c..ce816f0c 100644 --- a/src/libs/services/scrobbling/impl/ScrobblingService.hpp +++ b/src/libs/services/scrobbling/impl/ScrobblingService.hpp @@ -26,12 +26,12 @@ #include "services/scrobbling/IScrobblingService.hpp" #include "IScrobblingBackend.hpp" -namespace Scrobbling +namespace lms::scrobbling { class ScrobblingService : public IScrobblingService { public: - ScrobblingService(boost::asio::io_context& ioContext, Database::Db& db); + ScrobblingService(boost::asio::io_context& ioContext, db::Db& db); ~ScrobblingService(); private: @@ -43,20 +43,20 @@ namespace Scrobbling ReleaseContainer getRecentReleases(const FindParameters& params) override; TrackContainer getRecentTracks(const FindParameters& params) override; - std::size_t getCount(Database::UserId userId, Database::ReleaseId releaseId) override; - std::size_t getCount(Database::UserId userId, Database::TrackId trackId) override; + std::size_t getCount(db::UserId userId, db::ReleaseId releaseId) override; + std::size_t getCount(db::UserId userId, db::TrackId trackId) override; - Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::ReleaseId releaseId) override; - Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::TrackId trackId) override; + Wt::WDateTime getLastListenDateTime(db::UserId userId, db::ReleaseId releaseId) override; + Wt::WDateTime getLastListenDateTime(db::UserId userId, db::TrackId trackId) override; ArtistContainer getTopArtists(const ArtistFindParameters& params) override; ReleaseContainer getTopReleases(const FindParameters& params) override; TrackContainer getTopTracks(const FindParameters& params) override; - std::optional getUserBackend(Database::UserId userId); + std::optional getUserBackend(db::UserId userId); - Database::Db& _db; - std::unordered_map> _scrobblingBackends; + db::Db& _db; + std::unordered_map> _scrobblingBackends; }; } // ns Scrobbling diff --git a/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp b/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp index 81696458..5512acbe 100644 --- a/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp +++ b/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp @@ -25,9 +25,9 @@ #include "database/Track.hpp" #include "database/User.hpp" -namespace Scrobbling +namespace lms::scrobbling { - InternalBackend::InternalBackend(Database::Db& db) + InternalBackend::InternalBackend(db::Db& db) : _db{ db } {} @@ -47,22 +47,22 @@ namespace Scrobbling void InternalBackend::addTimedListen(const TimedListen& listen) { - Database::Session& session{ _db.getTLSSession() }; + db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createWriteTransaction() }; - if (Database::Listen::find(session, listen.userId, listen.trackId, Database::ScrobblingBackend::Internal, listen.listenedAt)) + if (db::Listen::find(session, listen.userId, listen.trackId, db::ScrobblingBackend::Internal, listen.listenedAt)) return; - const Database::User::pointer user{ Database::User::find(session, listen.userId) }; + const db::User::pointer user{ db::User::find(session, listen.userId) }; if (!user) return; - const Database::Track::pointer track{ Database::Track::find(session, listen.trackId) }; + const db::Track::pointer track{ db::Track::find(session, listen.trackId) }; if (!track) return; - auto dbListen{ session.create(user, track, Database::ScrobblingBackend::Internal, listen.listenedAt) }; - dbListen.modify()->setSyncState(Database::SyncState::Synchronized); + auto dbListen{ session.create(user, track, db::ScrobblingBackend::Internal, listen.listenedAt) }; + dbListen.modify()->setSyncState(db::SyncState::Synchronized); } } // Scrobbling diff --git a/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp b/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp index bd3a3c17..349addf9 100644 --- a/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp +++ b/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp @@ -21,24 +21,24 @@ #include "IScrobblingBackend.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Scrobbling +namespace lms::scrobbling { class InternalBackend final : public IScrobblingBackend { public: - InternalBackend(Database::Db& db); + InternalBackend(db::Db& db); private: void listenStarted(const Listen& listen) override; void listenFinished(const Listen& listen, std::optional duration) override; void addTimedListen(const TimedListen& listen) override; - Database::Db& _db; + db::Db& _db; }; } // Scrobbling diff --git a/src/libs/services/scrobbling/impl/listenbrainz/Exception.hpp b/src/libs/services/scrobbling/impl/listenbrainz/Exception.hpp index b4714606..7bf30fff 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/Exception.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/Exception.hpp @@ -21,11 +21,11 @@ #include "services/scrobbling/Exception.hpp" -namespace Scrobbling::ListenBrainz +namespace lms::scrobbling::listenBrainz { - class Exception : public Scrobbling::Exception + class Exception : public scrobbling::Exception { public: - using Scrobbling::Exception::Exception; + using scrobbling::Exception::Exception; }; } diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp index ead18109..7bf6dccd 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp @@ -22,15 +22,15 @@ #include "database/Db.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "utils/IConfig.hpp" -#include "utils/http/IClient.hpp" -#include "utils/ILogger.hpp" -#include "utils/Service.hpp" +#include "core/IConfig.hpp" +#include "core/http/IClient.hpp" +#include "core/ILogger.hpp" +#include "core/Service.hpp" #include "Utils.hpp" -namespace Scrobbling::ListenBrainz +namespace lms::scrobbling::listenBrainz { - using namespace Database; + using namespace db; namespace { @@ -53,8 +53,8 @@ namespace Scrobbling::ListenBrainz ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, Db& db) : _ioContext{ ioContext } , _db{ db } - , _baseAPIUrl{ Service::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org") } - , _client{ Http::createClient(_ioContext, _baseAPIUrl) } + , _baseAPIUrl{ core::Service::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org") } + , _client{ core::http::createClient(_ioContext, _baseAPIUrl) } , _listensSynchronizer{ _ioContext, db, *_client } { LOG(INFO, "Starting ListenBrainz backend... API endpoint = '" << _baseAPIUrl << "'"); @@ -83,5 +83,5 @@ namespace Scrobbling::ListenBrainz { _listensSynchronizer.enqueListen(timedListen); } -} // namespace Scrobbling::ListenBrainz +} // namespace lms::scrobbling::listenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp index 887dc84f..ba1bccbc 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp @@ -26,17 +26,17 @@ #include "IScrobblingBackend.hpp" #include "ListensSynchronizer.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Scrobbling::ListenBrainz +namespace lms::scrobbling::listenBrainz { class ListenBrainzBackend final : public IScrobblingBackend { public: - ListenBrainzBackend(boost::asio::io_context& ioContext, Database::Db& db); + ListenBrainzBackend(boost::asio::io_context& ioContext, db::Db& db); ~ListenBrainzBackend() override; private: @@ -51,10 +51,10 @@ namespace Scrobbling::ListenBrainz void enqueListen(const Listen& listen, const Wt::WDateTime& timePoint); boost::asio::io_context& _ioContext; - Database::Db& _db; + db::Db& _db; std::string _baseAPIUrl; - std::unique_ptr _client; + std::unique_ptr _client; ListensSynchronizer _listensSynchronizer; }; -} // Scrobbling::ListenBrainz +} // scrobbling::ListenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp index 88f7d40d..690c4186 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp @@ -19,7 +19,7 @@ #include "ListenTypes.hpp" -namespace Scrobbling::ListenBrainz +namespace lms::scrobbling::listenBrainz { std::ostream& operator<<(std::ostream& os, const Listen& listen) @@ -38,4 +38,4 @@ namespace Scrobbling::ListenBrainz return os; } -} // Scrobbling::ListenBrainz +} // scrobbling::ListenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.hpp b/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.hpp index 10f21477..37c64dc6 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.hpp @@ -23,21 +23,21 @@ #include #include -#include "utils/UUID.hpp" +#include "core/UUID.hpp" -namespace Scrobbling::ListenBrainz +namespace lms::scrobbling::listenBrainz { struct Listen { std::string trackName; std::string releaseName; std::string artistName; - std::optional recordingMBID; - std::optional trackMBID; - std::optional releaseMBID; + std::optional recordingMBID; + std::optional trackMBID; + std::optional releaseMBID; std::optional trackNumber; Wt::WDateTime listenedAt; }; std::ostream& operator<<(std::ostream& os, const Listen& listen); -} // Scrobbling::ListenBrainz +} diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.cpp index 3c40d004..b7bf7d45 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.cpp @@ -24,10 +24,10 @@ #include #include -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "Utils.hpp" -namespace Scrobbling::ListenBrainz +namespace lms::scrobbling::listenBrainz { namespace { @@ -50,9 +50,9 @@ namespace Scrobbling::ListenBrainz if (metadata.type("additional_info") == Wt::Json::Type::Object) { const Wt::Json::Object& additionalInfo = metadata.get("additional_info"); - listen.trackMBID = UUID::fromString(additionalInfo.get("track_mbid").orIfNull("")); - listen.recordingMBID = UUID::fromString(additionalInfo.get("recording_mbid").orIfNull("")); - listen.releaseMBID = UUID::fromString(additionalInfo.get("release_mbid").orIfNull("")); + listen.trackMBID = core::UUID::fromString(additionalInfo.get("track_mbid").orIfNull("")); + listen.recordingMBID = core::UUID::fromString(additionalInfo.get("recording_mbid").orIfNull("")); + listen.releaseMBID = core::UUID::fromString(additionalInfo.get("release_mbid").orIfNull("")); // tracknumber should be an integer but some players encode as strings int trackNumber{ additionalInfo.get("tracknumber").toNumber().orIfNull(-1) }; @@ -102,4 +102,4 @@ namespace Scrobbling::ListenBrainz return result; } -} // Scrobbling::ListenBrainz +} // scrobbling::ListenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.hpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.hpp index 33563b93..c85a9814 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.hpp @@ -23,7 +23,7 @@ #include "ListenTypes.hpp" -namespace Scrobbling::ListenBrainz +namespace lms::scrobbling::listenBrainz { class ListensParser { @@ -36,4 +36,4 @@ namespace Scrobbling::ListenBrainz static Result parse(std::string_view msgBody); }; -} // Scrobbling::ListenBrainz +} // scrobbling::ListenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp index d73c666b..c221c3ca 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp @@ -33,28 +33,28 @@ #include "database/Track.hpp" #include "database/User.hpp" #include "services/scrobbling/Exception.hpp" -#include "utils/IConfig.hpp" -#include "utils/http/IClient.hpp" -#include "utils/Service.hpp" +#include "core/IConfig.hpp" +#include "core/http/IClient.hpp" +#include "core/Service.hpp" #include "ListensParser.hpp" #include "Utils.hpp" -namespace Scrobbling::ListenBrainz +namespace lms::scrobbling::listenBrainz { namespace { - std::optional listenToJsonPayload(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint) + std::optional listenToJsonPayload(db::Session& session, const scrobbling::Listen& listen, const Wt::WDateTime& timePoint) { auto transaction{ session.createReadTransaction() }; - const Database::Track::pointer track{ Database::Track::find(session, listen.trackId) }; + const db::Track::pointer track{ db::Track::find(session, listen.trackId) }; if (!track) return std::nullopt; - auto artists{ track->getArtists({Database::TrackArtistLinkType::Artist}) }; + auto artists{ track->getArtists({db::TrackArtistLinkType::Artist}) }; if (artists.empty()) - artists = track->getArtists({ Database::TrackArtistLinkType::ReleaseArtist }); + artists = track->getArtists({ db::TrackArtistLinkType::ReleaseArtist }); if (artists.empty()) { @@ -75,7 +75,7 @@ namespace Scrobbling::ListenBrainz { Wt::Json::Array artistMBIDs; - for (const Database::Artist::pointer& artist : artists) + for (const db::Artist::pointer& artist : artists) { if (auto MBID{ artist->getMBID() }) artistMBIDs.push_back(Wt::Json::Value{ std::string {MBID->getAsString()} }); @@ -109,7 +109,7 @@ namespace Scrobbling::ListenBrainz return payload; } - std::string listenToJsonString(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint, std::string_view listenType) + std::string listenToJsonString(db::Session& session, const scrobbling::Listen& listen, const Wt::WDateTime& timePoint, std::string_view listenType) { std::string res; @@ -142,9 +142,9 @@ namespace Scrobbling::ListenBrainz } } - Database::TrackId tryGetMatchingTrack(Database::Session& session, const Listen& listen) + db::TrackId tryGetMatchingTrack(db::Session& session, const Listen& listen) { - using namespace Database; + using namespace db; auto transaction{ session.createReadTransaction() }; @@ -209,12 +209,12 @@ namespace Scrobbling::ListenBrainz } } - ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client) + ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client) : _ioContext{ ioContext } , _db{ db } , _client{ client } - , _maxSyncListenCount{ Service::get()->getULong("listenbrainz-max-sync-listen-count", 1000) } - , _syncListensPeriod{ Service::get()->getULong("listenbrainz-sync-listens-period-hours", 1) } + , _maxSyncListenCount{ core::Service::get()->getULong("listenbrainz-max-sync-listen-count", 1000) } + , _syncListensPeriod{ core::Service::get()->getULong("listenbrainz-sync-listens-period-hours", 1) } { LOG(INFO, "Starting Listens synchronizer, maxSyncListenCount = " << _maxSyncListenCount << ", _syncListensPeriod = " << _syncListensPeriod.count() << " hours"); @@ -227,28 +227,28 @@ namespace Scrobbling::ListenBrainz enqueListen(listen, listen.listenedAt); } - void ListensSynchronizer::enqueListenNow(const Scrobbling::Listen& listen) + void ListensSynchronizer::enqueListenNow(const scrobbling::Listen& listen) { enqueListen(listen, {}); } - void ListensSynchronizer::enqueListen(const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint) + void ListensSynchronizer::enqueListen(const scrobbling::Listen& listen, const Wt::WDateTime& timePoint) { - Http::ClientPOSTRequestParameters request; + core::http::ClientPOSTRequestParameters request; request.relativeUrl = "/1/submit-listens"; if (timePoint.isValid()) { const TimedListen timedListen{ listen, timePoint }; // We want the listen to be sent again later in case of failure, so we just save it as pending send - saveListen(timedListen, Database::SyncState::PendingAdd); + saveListen(timedListen, db::SyncState::PendingAdd); - request.priority = Http::ClientRequestParameters::Priority::Normal; + request.priority = core::http::ClientRequestParameters::Priority::Normal; request.onSuccessFunc = [this, timedListen](std::string_view) { _strand.dispatch([this, timedListen] { - if (saveListen(timedListen, Database::SyncState::Synchronized)) + if (saveListen(timedListen, db::SyncState::Synchronized)) { UserContext& context{ getUserContext(timedListen.userId) }; if (context.listenCount) @@ -261,7 +261,7 @@ namespace Scrobbling::ListenBrainz else { // We want "listen now" to appear as soon as possible - request.priority = Http::ClientRequestParameters::Priority::High; + request.priority = core::http::ClientRequestParameters::Priority::High; // don't retry on failure } @@ -272,7 +272,7 @@ namespace Scrobbling::ListenBrainz return; } - const std::optional listenBrainzToken{ Utils::getListenBrainzToken(_db.getTLSSession(), listen.userId) }; + const std::optional listenBrainzToken{ utils::getListenBrainzToken(_db.getTLSSession(), listen.userId) }; if (!listenBrainzToken) { LOG(DEBUG, "No listenbrainz token found: skipping"); @@ -285,14 +285,14 @@ namespace Scrobbling::ListenBrainz _client.sendPOSTRequest(std::move(request)); } - bool ListensSynchronizer::saveListen(const TimedListen& listen, Database::SyncState scrobblingState) + bool ListensSynchronizer::saveListen(const TimedListen& listen, db::SyncState scrobblingState) { - using namespace Database; + using namespace db; Session& session{ _db.getTLSSession() }; auto transaction{ session.createWriteTransaction() }; // TODO: unique only if needed - Database::Listen::pointer dbListen{ Database::Listen::find(session, listen.userId, listen.trackId, Database::ScrobblingBackend::ListenBrainz, listen.listenedAt) }; + db::Listen::pointer dbListen{ db::Listen::find(session, listen.userId, listen.trackId, db::ScrobblingBackend::ListenBrainz, listen.listenedAt) }; if (!dbListen) { const User::pointer user{ User::find(session, listen.userId) }; @@ -303,7 +303,7 @@ namespace Scrobbling::ListenBrainz if (!track) return false; - dbListen = session.create(user, track, Database::ScrobblingBackend::ListenBrainz, listen.listenedAt); + dbListen = session.create(user, track, db::ScrobblingBackend::ListenBrainz, listen.listenedAt); dbListen.modify()->setSyncState(scrobblingState); LOG(DEBUG, "LISTEN CREATED for user " << user->getLoginName() << ", track '" << track->getName() << "' AT " << listen.listenedAt.toString()); @@ -323,21 +323,21 @@ namespace Scrobbling::ListenBrainz std::vector pendingListens; { - Database::Session& session{ _db.getTLSSession() }; + db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createWriteTransaction() }; - Database::Listen::FindParameters params; - params.setScrobblingBackend(Database::ScrobblingBackend::ListenBrainz) - .setSyncState(Database::SyncState::PendingAdd) - .setRange(Database::Range{ 0, 100 }); // don't flood too much? + db::Listen::FindParameters params; + params.setScrobblingBackend(db::ScrobblingBackend::ListenBrainz) + .setSyncState(db::SyncState::PendingAdd) + .setRange(db::Range{ 0, 100 }); // don't flood too much? - const Database::RangeResults results{ Database::Listen::find(session, params) }; + const db::RangeResults results{ db::Listen::find(session, params) }; pendingListens.reserve(results.results.size()); - for (Database::ListenId listenId : results.results) + for (db::ListenId listenId : results.results) { - const Database::Listen::pointer listen{ Database::Listen::find(session, listenId) }; + const db::Listen::pointer listen{ db::Listen::find(session, listenId) }; TimedListen timedListen; timedListen.listenedAt = listen->getDateTime(); @@ -354,7 +354,7 @@ namespace Scrobbling::ListenBrainz enqueListen(pendingListen); } - ListensSynchronizer::UserContext& ListensSynchronizer::getUserContext(Database::UserId userId) + ListensSynchronizer::UserContext& ListensSynchronizer::getUserContext(db::UserId userId) { assert(_strand.running_in_this_thread()); @@ -406,14 +406,14 @@ namespace Scrobbling::ListenBrainz enquePendingListens(); - Database::RangeResults userIds; + db::RangeResults userIds; { - Database::Session& session{ _db.getTLSSession() }; + db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - userIds = Database::User::find(_db.getTLSSession(), Database::User::FindParameters{}.setScrobblingBackend(Database::ScrobblingBackend::ListenBrainz)); + userIds = db::User::find(_db.getTLSSession(), db::User::FindParameters{}.setScrobblingBackend(db::ScrobblingBackend::ListenBrainz)); } - for (const Database::UserId userId : userIds.results) + for (const db::UserId userId : userIds.results) startSync(getUserContext(userId)); if (!isSyncing()) @@ -448,20 +448,20 @@ namespace Scrobbling::ListenBrainz { assert(context.listenBrainzUserName.empty()); - const std::optional listenBrainzToken{ Utils::getListenBrainzToken(_db.getTLSSession(), context.userId) }; + const std::optional listenBrainzToken{ utils::getListenBrainzToken(_db.getTLSSession(), context.userId) }; if (!listenBrainzToken) { onSyncEnded(context); return; } - Http::ClientGETRequestParameters request; - request.priority = Http::ClientRequestParameters::Priority::Low; + core::http::ClientGETRequestParameters request; + request.priority = core::http::ClientRequestParameters::Priority::Low; request.relativeUrl = "/1/validate-token"; request.headers = { {"Authorization", "Token " + std::string {listenBrainzToken->getAsString()}} }; request.onSuccessFunc = [this, &context](std::string_view msgBody) { - context.listenBrainzUserName = Utils::parseValidateToken(msgBody); + context.listenBrainzUserName = utils::parseValidateToken(msgBody); if (context.listenBrainzUserName.empty()) { onSyncEnded(context); @@ -481,9 +481,9 @@ namespace Scrobbling::ListenBrainz { assert(!context.listenBrainzUserName.empty()); - Http::ClientGETRequestParameters request; + core::http::ClientGETRequestParameters request; request.relativeUrl = "/1/user/" + std::string{ context.listenBrainzUserName } + "/listen-count"; - request.priority = Http::ClientRequestParameters::Priority::Low; + request.priority = core::http::ClientRequestParameters::Priority::Low; request.onSuccessFunc = [this, &context](std::string_view msgBody) { const auto listenCount{ parseListenCount(msgBody) }; @@ -517,9 +517,9 @@ namespace Scrobbling::ListenBrainz { assert(!context.listenBrainzUserName.empty()); - Http::ClientGETRequestParameters request; + core::http::ClientGETRequestParameters request; request.relativeUrl = "/1/user/" + context.listenBrainzUserName + "/listens?max_ts=" + std::to_string(context.maxDateTime.toTime_t()); - request.priority = Http::ClientRequestParameters::Priority::Low; + request.priority = core::http::ClientRequestParameters::Priority::Low; request.onSuccessFunc = [this, &context](std::string_view msgBody) { processGetListensResponse(msgBody, context); @@ -541,7 +541,7 @@ namespace Scrobbling::ListenBrainz void ListensSynchronizer::processGetListensResponse(std::string_view msgBody, UserContext& context) { - Database::Session& session{ _db.getTLSSession() }; + db::Session& session{ _db.getTLSSession() }; context.maxDateTime = {}; // invalidate to break in case no more listens are fetched ListensParser::Result result{ ListensParser::parse(msgBody) }; @@ -559,14 +559,14 @@ namespace Scrobbling::ListenBrainz if (!context.maxDateTime.isValid() || context.maxDateTime > parsedListen.listenedAt) context.maxDateTime = parsedListen.listenedAt; - if (const Database::TrackId trackId{ tryGetMatchingTrack(session, parsedListen) }; trackId.isValid()) + if (const db::TrackId trackId{ tryGetMatchingTrack(session, parsedListen) }; trackId.isValid()) { context.matchedListenCount++; - const Scrobbling::TimedListen listen{ {context.userId, trackId}, parsedListen.listenedAt }; - if (saveListen(listen, Database::SyncState::Synchronized)) + const scrobbling::TimedListen listen{ {context.userId, trackId}, parsedListen.listenedAt }; + if (saveListen(listen, db::SyncState::Synchronized)) context.importedListenCount++; } } } -} // namespace Scrobbling::ListenBrainz +} // namespace lms::scrobbling::listenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp index e9a6ffd1..afdcdac6 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp @@ -30,42 +30,44 @@ #include "services/scrobbling/Listen.hpp" -namespace Database +namespace lms { - class Db; + namespace core::http + { + class IClient; + } + namespace db + { + class Db; + } } -namespace Http -{ - class IClient; -} - -namespace Scrobbling::ListenBrainz +namespace lms::scrobbling::listenBrainz { class ListensSynchronizer { public: - ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client); + ListensSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client); void enqueListen(const TimedListen& listen); void enqueListenNow(const Listen& listen); private: void enqueListen(const Listen& listen, const Wt::WDateTime& timePoint); - bool saveListen(const TimedListen& listen, Database::SyncState scrobblinState); + bool saveListen(const TimedListen& listen, db::SyncState scrobblinState); void enquePendingListens(); struct UserContext { - UserContext(Database::UserId id) : userId {id} {} + UserContext(db::UserId id) : userId {id} {} UserContext(const UserContext&) = delete; UserContext(UserContext&&) = delete; UserContext& operator=(const UserContext&) = delete; UserContext& operator=(UserContext&&) = delete; - const Database::UserId userId; + const db::UserId userId; bool syncing {}; std::optional listenCount {}; @@ -77,7 +79,7 @@ namespace Scrobbling::ListenBrainz std::size_t importedListenCount{}; }; - UserContext& getUserContext(Database::UserId userId); + UserContext& getUserContext(db::UserId userId); bool isSyncing() const; void scheduleSync(std::chrono::seconds fromNow); void startSync(); @@ -90,14 +92,14 @@ namespace Scrobbling::ListenBrainz boost::asio::io_context& _ioContext; boost::asio::io_context::strand _strand {_ioContext}; - Database::Db& _db; + db::Db& _db; boost::asio::steady_timer _syncTimer {_ioContext}; - Http::IClient& _client; + core::http::IClient& _client; - std::unordered_map _userContexts; + std::unordered_map _userContexts; const std::size_t _maxSyncListenCount; const std::chrono::hours _syncListensPeriod; }; -} // Scrobbling::ListenBrainz +} // scrobbling::ListenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp b/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp index 8c98575d..118ce4fd 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp @@ -25,13 +25,13 @@ #include "database/Session.hpp" #include "database/User.hpp" -namespace Scrobbling::ListenBrainz::Utils +namespace lms::scrobbling::listenBrainz::utils { - std::optional getListenBrainzToken(Database::Session& session, Database::UserId userId) + std::optional getListenBrainzToken(db::Session& session, db::UserId userId) { auto transaction{ session.createReadTransaction() }; - const Database::User::pointer user{ Database::User::find(session, userId) }; + const db::User::pointer user{ db::User::find(session, userId) }; if (!user) return std::nullopt; diff --git a/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp b/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp index 640afff7..ab8204db 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp @@ -20,18 +20,18 @@ #pragma once #include "database/UserId.hpp" -#include "utils/ILogger.hpp" -#include "utils/UUID.hpp" +#include "core/ILogger.hpp" +#include "core/UUID.hpp" #define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[listenbrainz] " << message) -namespace Database +namespace lms::db { class Session; } -namespace Scrobbling::ListenBrainz::Utils +namespace lms::scrobbling::listenBrainz::utils { - std::optional getListenBrainzToken(Database::Session& session, Database::UserId userId); + std::optional getListenBrainzToken(db::Session& session, db::UserId userId); std::string parseValidateToken(std::string_view msgBody); } diff --git a/src/libs/services/scrobbling/include/services/scrobbling/Exception.hpp b/src/libs/services/scrobbling/include/services/scrobbling/Exception.hpp index 699bfa19..85c72db3 100644 --- a/src/libs/services/scrobbling/include/services/scrobbling/Exception.hpp +++ b/src/libs/services/scrobbling/include/services/scrobbling/Exception.hpp @@ -19,11 +19,11 @@ #pragma once -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -namespace Scrobbling +namespace lms::scrobbling { - class Exception : public LmsException + class Exception : public core::LmsException { public: using LmsException::LmsException; diff --git a/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp b/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp index bd8aab9e..f61162cb 100644 --- a/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp +++ b/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp @@ -33,12 +33,12 @@ #include "database/TrackId.hpp" #include "database/Types.hpp" -namespace Database +namespace lms::db { class Db; } -namespace Scrobbling +namespace lms::scrobbling { class IScrobblingService { @@ -52,33 +52,33 @@ namespace Scrobbling virtual void addTimedListen(const TimedListen& listen) = 0; // Stats - using ArtistContainer = Database::RangeResults; - using ReleaseContainer = Database::RangeResults; - using TrackContainer = Database::RangeResults; + using ArtistContainer = db::RangeResults; + using ReleaseContainer = db::RangeResults; + using TrackContainer = db::RangeResults; struct FindParameters { - Database::UserId user; - std::vector clusters; // if non empty, at least one artist that belongs to these clusters - std::optional range; - Database::MediaLibraryId library; // if set, match this library - Database::ArtistId artist; // if set, match this artist + db::UserId user; + std::vector clusters; // if non empty, at least one artist that belongs to these clusters + std::optional range; + db::MediaLibraryId library; // if set, match this library + db::ArtistId artist; // if set, match this artist - FindParameters& setUser(const Database::UserId _user) { user = _user; return *this; } - FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } - FindParameters& setRange(std::optional _range) { range = _range; return *this; } - FindParameters& setMediaLibrary(Database::MediaLibraryId _library) { library = _library; return *this; } - FindParameters& setArtist(Database::ArtistId _artist) { artist = _artist; return *this; } + FindParameters& setUser(const db::UserId _user) { user = _user; return *this; } + FindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } + FindParameters& setRange(std::optional _range) { range = _range; return *this; } + FindParameters& setMediaLibrary(db::MediaLibraryId _library) { library = _library; return *this; } + FindParameters& setArtist(db::ArtistId _artist) { artist = _artist; return *this; } }; // Artists struct ArtistFindParameters : public FindParameters { - std::optional linkType; // if set, only artists that have produced at least one track with this link type - Database::ArtistSortMethod sortMethod{ Database::ArtistSortMethod::None }; + std::optional linkType; // if set, only artists that have produced at least one track with this link type + db::ArtistSortMethod sortMethod{ db::ArtistSortMethod::None }; - ArtistFindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } - ArtistFindParameters& setSortMethod(Database::ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } + ArtistFindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } + ArtistFindParameters& setSortMethod(db::ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } }; // From most recent to oldest @@ -86,11 +86,11 @@ namespace Scrobbling virtual ReleaseContainer getRecentReleases(const FindParameters& params) = 0; virtual TrackContainer getRecentTracks(const FindParameters& params) = 0; - virtual std::size_t getCount(Database::UserId userId, Database::ReleaseId releaseId) = 0; - virtual std::size_t getCount(Database::UserId userId, Database::TrackId trackId) = 0; + virtual std::size_t getCount(db::UserId userId, db::ReleaseId releaseId) = 0; + virtual std::size_t getCount(db::UserId userId, db::TrackId trackId) = 0; - virtual Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::ReleaseId releaseId) = 0; - virtual Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::TrackId trackId) = 0; + virtual Wt::WDateTime getLastListenDateTime(db::UserId userId, db::ReleaseId releaseId) = 0; + virtual Wt::WDateTime getLastListenDateTime(db::UserId userId, db::TrackId trackId) = 0; // Top virtual ArtistContainer getTopArtists(const ArtistFindParameters& params) = 0; @@ -98,5 +98,5 @@ namespace Scrobbling virtual TrackContainer getTopTracks(const FindParameters& params) = 0; }; - std::unique_ptr createScrobblingService(boost::asio::io_service& ioService, Database::Db& db); + std::unique_ptr createScrobblingService(boost::asio::io_service& ioService, db::Db& db); } // ns Scrobbling diff --git a/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp b/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp index d0c7bba6..499522ba 100644 --- a/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp +++ b/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp @@ -24,12 +24,12 @@ #include "database/TrackId.hpp" #include "database/UserId.hpp" -namespace Scrobbling +namespace lms::scrobbling { struct Listen { - Database::UserId userId{}; - Database::TrackId trackId{}; + db::UserId userId{}; + db::TrackId trackId{}; }; struct TimedListen : public Listen diff --git a/src/libs/services/scrobbling/test/CMakeLists.txt b/src/libs/services/scrobbling/test/CMakeLists.txt index b8a36f1b..3ba0023c 100644 --- a/src/libs/services/scrobbling/test/CMakeLists.txt +++ b/src/libs/services/scrobbling/test/CMakeLists.txt @@ -5,7 +5,7 @@ add_executable(test-scrobbling ) target_link_libraries(test-scrobbling PRIVATE - lmsutils + lmscore lmsscrobbling GTest::GTest ) diff --git a/src/libs/services/scrobbling/test/Listenbrainz.cpp b/src/libs/services/scrobbling/test/Listenbrainz.cpp index 565f06f8..86ea1840 100644 --- a/src/libs/services/scrobbling/test/Listenbrainz.cpp +++ b/src/libs/services/scrobbling/test/Listenbrainz.cpp @@ -21,115 +21,115 @@ #include "listenbrainz/ListensParser.hpp" -using namespace Scrobbling::ListenBrainz; - -TEST(Listenbrainz, parseListens_empty) +namespace lms::scrobbling::listenBrainz::tests { - ListensParser::Result result {ListensParser::parse("")}; - EXPECT_EQ(result.listenCount, 0); - EXPECT_EQ(result.listens.size(), 0); + TEST(Listenbrainz, parseListens_empty) + { + ListensParser::Result result {ListensParser::parse("")}; + EXPECT_EQ(result.listenCount, 0); + EXPECT_EQ(result.listens.size(), 0); - result = ListensParser::parse(R"({"payload":{"count":0,"latest_listen_ts":1664105200,"listens":[],"user_id":"epoupon"}})"); - EXPECT_EQ(result.listenCount, 0); - EXPECT_EQ(result.listens.size(), 0); -} + result = ListensParser::parse(R"({"payload":{"count":0,"latest_listen_ts":1664105200,"listens":[],"user_id":"epoupon"}})"); + EXPECT_EQ(result.listenCount, 0); + EXPECT_EQ(result.listens.size(), 0); + } -TEST(Listenbrainz, parseListens_single_missingMBID) -{ - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1663159479,"listens":[{"inserted_at":1650541124,"listened_at":1650541124,"recording_msid":"0e1418e3-b485-413a-84af-6316312cb116","track_metadata":{"additional_info":{"artist_msid":"ab5b27ad-e579-441c-ac60-d5dd9975c044","listening_from":"LMS","recording_msid":"0e1418e3-b485-413a-84af-6316312cb116","release_msid":"3f22f274-a9ee-4cb2-8dd1-f3bd18407099","tracknumber":8},"artist_name":"Broke For Free","release_name":"YEKOMS","track_name":"U2B"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; - EXPECT_EQ(result.listenCount, 1); - ASSERT_EQ(result.listens.size(), 1); - EXPECT_EQ(result.listens[0].trackName, "U2B"); - EXPECT_EQ(result.listens[0].releaseName, "YEKOMS"); - EXPECT_EQ(result.listens[0].artistName, "Broke For Free"); - EXPECT_EQ(result.listens[0].recordingMBID, std::nullopt); - EXPECT_EQ(result.listens[0].releaseMBID, std::nullopt); - EXPECT_EQ(result.listens[0].trackNumber, 8); + TEST(Listenbrainz, parseListens_single_missingMBID) + { + ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1663159479,"listens":[{"inserted_at":1650541124,"listened_at":1650541124,"recording_msid":"0e1418e3-b485-413a-84af-6316312cb116","track_metadata":{"additional_info":{"artist_msid":"ab5b27ad-e579-441c-ac60-d5dd9975c044","listening_from":"LMS","recording_msid":"0e1418e3-b485-413a-84af-6316312cb116","release_msid":"3f22f274-a9ee-4cb2-8dd1-f3bd18407099","tracknumber":8},"artist_name":"Broke For Free","release_name":"YEKOMS","track_name":"U2B"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; + EXPECT_EQ(result.listenCount, 1); + ASSERT_EQ(result.listens.size(), 1); + EXPECT_EQ(result.listens[0].trackName, "U2B"); + EXPECT_EQ(result.listens[0].releaseName, "YEKOMS"); + EXPECT_EQ(result.listens[0].artistName, "Broke For Free"); + EXPECT_EQ(result.listens[0].recordingMBID, std::nullopt); + EXPECT_EQ(result.listens[0].releaseMBID, std::nullopt); + EXPECT_EQ(result.listens[0].trackNumber, 8); - Wt::WDateTime listenedAt; - listenedAt.setTime_t(1650541124); - EXPECT_EQ(result.listens[0].listenedAt, listenedAt); -} + Wt::WDateTime listenedAt; + listenedAt.setTime_t(1650541124); + EXPECT_EQ(result.listens[0].listenedAt, listenedAt); + } -TEST(Listenbrainz, parseListens_tworesults) -{ - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":2,"latest_listen_ts":1664028167,"listens":[{"inserted_at":1664028167,"listened_at":1664028167,"recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"5427a943-a096-4d0b-8b9a-53aca9ed61ac","tracknumber":5},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Juparo"},"user_name":"epoupon"},{"inserted_at":1664027919,"listened_at":1664027918,"recording_msid":"fe5abc47-89cd-4235-80b5-00f47cecbe01","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"d89d042c-8cc1-4526-9080-5bab728ee15f","recording_msid":"fe5abc47-89cd-4235-80b5-00f47cecbe01","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"9f33a17f-e33e-492f-85a4-7b2e9e09613e","tracknumber":4},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"d89d042c-8cc1-4526-9080-5bab728ee15f","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Melt"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; - EXPECT_EQ(result.listenCount, 2); - ASSERT_EQ(result.listens.size(), 2); - EXPECT_EQ(result.listens[0].trackName, "Juparo"); - EXPECT_EQ(result.listens[0].releaseName, "Petal"); - EXPECT_EQ(result.listens[0].artistName, "Broke For Free"); - ASSERT_TRUE(result.listens[0].recordingMBID.has_value()); - EXPECT_EQ(result.listens[0].recordingMBID->getAsString(), "46ae879f-2dbe-46d3-99ad-05c116f97a30"); - ASSERT_TRUE(result.listens[0].releaseMBID.has_value()); - EXPECT_EQ(result.listens[0].releaseMBID->getAsString(), "44915500-fbb9-4060-98ce-59a57a429edc"); - EXPECT_EQ(result.listens[0].trackNumber, 5); + TEST(Listenbrainz, parseListens_tworesults) + { + ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":2,"latest_listen_ts":1664028167,"listens":[{"inserted_at":1664028167,"listened_at":1664028167,"recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"5427a943-a096-4d0b-8b9a-53aca9ed61ac","tracknumber":5},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Juparo"},"user_name":"epoupon"},{"inserted_at":1664027919,"listened_at":1664027918,"recording_msid":"fe5abc47-89cd-4235-80b5-00f47cecbe01","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"d89d042c-8cc1-4526-9080-5bab728ee15f","recording_msid":"fe5abc47-89cd-4235-80b5-00f47cecbe01","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"9f33a17f-e33e-492f-85a4-7b2e9e09613e","tracknumber":4},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"d89d042c-8cc1-4526-9080-5bab728ee15f","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Melt"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; + EXPECT_EQ(result.listenCount, 2); + ASSERT_EQ(result.listens.size(), 2); + EXPECT_EQ(result.listens[0].trackName, "Juparo"); + EXPECT_EQ(result.listens[0].releaseName, "Petal"); + EXPECT_EQ(result.listens[0].artistName, "Broke For Free"); + ASSERT_TRUE(result.listens[0].recordingMBID.has_value()); + EXPECT_EQ(result.listens[0].recordingMBID->getAsString(), "46ae879f-2dbe-46d3-99ad-05c116f97a30"); + ASSERT_TRUE(result.listens[0].releaseMBID.has_value()); + EXPECT_EQ(result.listens[0].releaseMBID->getAsString(), "44915500-fbb9-4060-98ce-59a57a429edc"); + EXPECT_EQ(result.listens[0].trackNumber, 5); - EXPECT_EQ(result.listens[1].trackName, "Melt"); - EXPECT_EQ(result.listens[1].releaseName, "Petal"); - EXPECT_EQ(result.listens[1].artistName, "Broke For Free"); - ASSERT_TRUE(result.listens[1].recordingMBID.has_value()); - EXPECT_EQ(result.listens[1].recordingMBID->getAsString(), "d89d042c-8cc1-4526-9080-5bab728ee15f"); - ASSERT_TRUE(result.listens[1].releaseMBID.has_value()); - EXPECT_EQ(result.listens[1].releaseMBID->getAsString(), "44915500-fbb9-4060-98ce-59a57a429edc"); - EXPECT_EQ(result.listens[1].trackNumber, 4); -} + EXPECT_EQ(result.listens[1].trackName, "Melt"); + EXPECT_EQ(result.listens[1].releaseName, "Petal"); + EXPECT_EQ(result.listens[1].artistName, "Broke For Free"); + ASSERT_TRUE(result.listens[1].recordingMBID.has_value()); + EXPECT_EQ(result.listens[1].recordingMBID->getAsString(), "d89d042c-8cc1-4526-9080-5bab728ee15f"); + ASSERT_TRUE(result.listens[1].releaseMBID.has_value()); + EXPECT_EQ(result.listens[1].releaseMBID->getAsString(), "44915500-fbb9-4060-98ce-59a57a429edc"); + EXPECT_EQ(result.listens[1].trackNumber, 4); + } -TEST(Listenbrainz, parseListens_tworesults_butinvalid) -{ - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":2,"latest_listen_ts":1664028167,"listens":[{"inserted_at":1664028167,"listened_at":1664028167,"recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"5427a943-a096-4d0b-8b9a-53aca9ed61ac","tracknumber":5},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Juparo"},"user_name":"epoupon"},{"inserted_at":1664027919}],"user_id":"epoupon"}})")}; - EXPECT_EQ(result.listenCount, 2); - ASSERT_EQ(result.listens.size(), 1); - EXPECT_EQ(result.listens[0].trackName, "Juparo"); - EXPECT_EQ(result.listens[0].releaseName, "Petal"); - EXPECT_EQ(result.listens[0].artistName, "Broke For Free"); - ASSERT_TRUE(result.listens[0].recordingMBID.has_value()); - EXPECT_EQ(result.listens[0].recordingMBID->getAsString(), "46ae879f-2dbe-46d3-99ad-05c116f97a30"); - ASSERT_TRUE(result.listens[0].releaseMBID.has_value()); - EXPECT_EQ(result.listens[0].releaseMBID->getAsString(), "44915500-fbb9-4060-98ce-59a57a429edc"); - EXPECT_EQ(result.listens[0].trackNumber, 5); -} + TEST(Listenbrainz, parseListens_tworesults_butinvalid) + { + ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":2,"latest_listen_ts":1664028167,"listens":[{"inserted_at":1664028167,"listened_at":1664028167,"recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"5427a943-a096-4d0b-8b9a-53aca9ed61ac","tracknumber":5},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Juparo"},"user_name":"epoupon"},{"inserted_at":1664027919}],"user_id":"epoupon"}})")}; + EXPECT_EQ(result.listenCount, 2); + ASSERT_EQ(result.listens.size(), 1); + EXPECT_EQ(result.listens[0].trackName, "Juparo"); + EXPECT_EQ(result.listens[0].releaseName, "Petal"); + EXPECT_EQ(result.listens[0].artistName, "Broke For Free"); + ASSERT_TRUE(result.listens[0].recordingMBID.has_value()); + EXPECT_EQ(result.listens[0].recordingMBID->getAsString(), "46ae879f-2dbe-46d3-99ad-05c116f97a30"); + ASSERT_TRUE(result.listens[0].releaseMBID.has_value()); + EXPECT_EQ(result.listens[0].releaseMBID->getAsString(), "44915500-fbb9-4060-98ce-59a57a429edc"); + EXPECT_EQ(result.listens[0].trackNumber, 5); + } -TEST(Listenbrainz, parseListens_entryNotFromLms) -{ - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664105730,"listens":[{"inserted_at":1664105730,"listened_at":1664105730,"recording_msid":"6a11ff4d-0623-4b2e-98e0-0e172f1f28d7","track_metadata":{"additional_info":{"artist_msid":null,"media_player":"BrainzPlayer","music_service":"youtube.com","music_service_name":"youtube","origin_url":"https://www.youtube.com/watch?v=EBP5vL3YWTI","recording_msid":"6a11ff4d-0623-4b2e-98e0-0e172f1f28d7","release_msid":null,"submission_client":"BrainzPlayer"},"artist_name":"Dio","brainzplayer_metadata":{"track_name":"Dio - Breathless"},"mbid_mapping":{"artist_mbids":["c55193fb-f5d2-4839-a263-4c044fca1456"],"recording_mbid":"92929526-21d7-4e75-b759-1072951664c4","release_mbid":"16cbf9ba-2e38-3893-9f23-f8567e26c18b"},"release_name":"The Last in Line","track_name":"Breathless"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; - EXPECT_EQ(result.listenCount, 1); - ASSERT_EQ(result.listens.size(), 1); - EXPECT_EQ(result.listens[0].trackName, "Breathless"); - EXPECT_EQ(result.listens[0].releaseName, "The Last in Line"); - EXPECT_EQ(result.listens[0].artistName, "Dio"); - EXPECT_FALSE(result.listens[0].recordingMBID.has_value()); - EXPECT_FALSE(result.listens[0].releaseMBID.has_value()); -} + TEST(Listenbrainz, parseListens_entryNotFromLms) + { + ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664105730,"listens":[{"inserted_at":1664105730,"listened_at":1664105730,"recording_msid":"6a11ff4d-0623-4b2e-98e0-0e172f1f28d7","track_metadata":{"additional_info":{"artist_msid":null,"media_player":"BrainzPlayer","music_service":"youtube.com","music_service_name":"youtube","origin_url":"https://www.youtube.com/watch?v=EBP5vL3YWTI","recording_msid":"6a11ff4d-0623-4b2e-98e0-0e172f1f28d7","release_msid":null,"submission_client":"BrainzPlayer"},"artist_name":"Dio","brainzplayer_metadata":{"track_name":"Dio - Breathless"},"mbid_mapping":{"artist_mbids":["c55193fb-f5d2-4839-a263-4c044fca1456"],"recording_mbid":"92929526-21d7-4e75-b759-1072951664c4","release_mbid":"16cbf9ba-2e38-3893-9f23-f8567e26c18b"},"release_name":"The Last in Line","track_name":"Breathless"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; + EXPECT_EQ(result.listenCount, 1); + ASSERT_EQ(result.listens.size(), 1); + EXPECT_EQ(result.listens[0].trackName, "Breathless"); + EXPECT_EQ(result.listens[0].releaseName, "The Last in Line"); + EXPECT_EQ(result.listens[0].artistName, "Dio"); + EXPECT_FALSE(result.listens[0].recordingMBID.has_value()); + EXPECT_FALSE(result.listens[0].releaseMBID.has_value()); + } -TEST(Listenbrainz, parseListens_multiArtists) -{ - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664106427,"listens":[{"inserted_at":1664106427,"listened_at":1664106427,"recording_msid":"b1dad0df-329b-443d-bacf-cdbebdddbfd0","track_metadata":{"additional_info":{"artist_mbids":["04ce0202-043d-4cbe-8f09-8abaf3b80c71","79311c51-9748-49df-baa1-d925fd29f4e8"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"a5f380bc-0a85-4a9f-88db-d41bb9aa2a4b","recording_msid":"b1dad0df-329b-443d-bacf-cdbebdddbfd0","release_mbid":"147b4669-3d20-43f8-89c0-ba1da8b87dd3","release_msid":null,"track_mbid":"a20dd067-29b6-3d38-a0be-eeb86b4671c1","tracknumber":1},"artist_name":"Gloom","release_name":"Demovibes 9: Party, people going","track_name":"Stargazer of Disgrace"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; - EXPECT_EQ(result.listenCount, 1); - ASSERT_EQ(result.listens.size(), 1); - EXPECT_EQ(result.listens[0].trackNumber, 1); -} + TEST(Listenbrainz, parseListens_multiArtists) + { + ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664106427,"listens":[{"inserted_at":1664106427,"listened_at":1664106427,"recording_msid":"b1dad0df-329b-443d-bacf-cdbebdddbfd0","track_metadata":{"additional_info":{"artist_mbids":["04ce0202-043d-4cbe-8f09-8abaf3b80c71","79311c51-9748-49df-baa1-d925fd29f4e8"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"a5f380bc-0a85-4a9f-88db-d41bb9aa2a4b","recording_msid":"b1dad0df-329b-443d-bacf-cdbebdddbfd0","release_mbid":"147b4669-3d20-43f8-89c0-ba1da8b87dd3","release_msid":null,"track_mbid":"a20dd067-29b6-3d38-a0be-eeb86b4671c1","tracknumber":1},"artist_name":"Gloom","release_name":"Demovibes 9: Party, people going","track_name":"Stargazer of Disgrace"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; + EXPECT_EQ(result.listenCount, 1); + ASSERT_EQ(result.listens.size(), 1); + EXPECT_EQ(result.listens[0].trackNumber, 1); + } -TEST(Listenbrainz, parseListens_minPayload) -{ - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664106427,"listens":[{"track_metadata":{"artist_name":"Gloom","track_name":"Stargazer of Disgrace"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; - EXPECT_EQ(result.listenCount, 1); - ASSERT_EQ(result.listens.size(), 1); - EXPECT_FALSE(result.listens[0].listenedAt.isValid()); - EXPECT_EQ(result.listens[0].trackName, "Stargazer of Disgrace"); - EXPECT_EQ(result.listens[0].artistName, "Gloom"); - EXPECT_EQ(result.listens[0].releaseName, ""); - EXPECT_FALSE(result.listens[0].recordingMBID.has_value()); - EXPECT_FALSE(result.listens[0].releaseMBID.has_value()); - EXPECT_EQ(result.listens[0].trackNumber, std::nullopt); -} + TEST(Listenbrainz, parseListens_minPayload) + { + ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664106427,"listens":[{"track_metadata":{"artist_name":"Gloom","track_name":"Stargazer of Disgrace"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; + EXPECT_EQ(result.listenCount, 1); + ASSERT_EQ(result.listens.size(), 1); + EXPECT_FALSE(result.listens[0].listenedAt.isValid()); + EXPECT_EQ(result.listens[0].trackName, "Stargazer of Disgrace"); + EXPECT_EQ(result.listens[0].artistName, "Gloom"); + EXPECT_EQ(result.listens[0].releaseName, ""); + EXPECT_FALSE(result.listens[0].recordingMBID.has_value()); + EXPECT_FALSE(result.listens[0].releaseMBID.has_value()); + EXPECT_EQ(result.listens[0].trackNumber, std::nullopt); + } -TEST(Listenbrainz, parseListens_stringInsteadOfInt) -{ - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1683298766,"listens":[{"inserted_at":1683299849,"listened_at":1683298766,"recording_msid":"3841ad1c-f674-491c-b855-f10a82ea38fe","track_metadata":{"additional_info":{"albumartist":"Various Artists","artist_mbids":["80ccfede-c258-4575-a7ad-c982e9932e0f"],"date":"2002-06-10","discnumber":"1","duration":190,"isrc":"USAM10200204","media_player":"foobar2000","media_player_version":"1.6.16","recording_msid":"3841ad1c-f674-491c-b855-f10a82ea38fe","release_group_mbid":"25f44677-2ecd-33fb-8454-9a9776496e3c","release_mbid":"bddad4c6-e9e1-4218-b9d2-b02a5a778a7d","submission_client":"foobar2000","submission_client_version":"1.3.2","totaldiscs":"1","totaltracks":"18","track_mbid":"08b1a5c1-c845-3d3b-9aa4-560a1af7f28c","tracknumber":"4"},"artist_name":"Sheryl Crow","mbid_mapping":{"artist_mbids":["80ccfede-c258-4575-a7ad-c982e9932e0f"],"artists":[{"artist_credit_name":"Sheryl Crow","artist_mbid":"80ccfede-c258-4575-a7ad-c982e9932e0f","join_phrase":""}],"caa_id":22039936498,"caa_release_mbid":"f10b4b40-4db1-4b9a-a223-06915fa06b3c","recording_mbid":"7ca21c82-1345-44df-b7ac-035a6e05d4ed","recording_name":"Strong Enough","release_mbid":"f10b4b40-4db1-4b9a-a223-06915fa06b3c"},"release_name":"The Very Best of MTV Unplugged","track_name":"Strong Enough"},"user_name":"awesomeuser"}],"user_id":"awesomeuser"}})")}; - - EXPECT_EQ(result.listenCount, 1); - ASSERT_EQ(result.listens.size(), 1); - EXPECT_EQ(result.listens[0].trackNumber, 4); -} + TEST(Listenbrainz, parseListens_stringInsteadOfInt) + { + ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1683298766,"listens":[{"inserted_at":1683299849,"listened_at":1683298766,"recording_msid":"3841ad1c-f674-491c-b855-f10a82ea38fe","track_metadata":{"additional_info":{"albumartist":"Various Artists","artist_mbids":["80ccfede-c258-4575-a7ad-c982e9932e0f"],"date":"2002-06-10","discnumber":"1","duration":190,"isrc":"USAM10200204","media_player":"foobar2000","media_player_version":"1.6.16","recording_msid":"3841ad1c-f674-491c-b855-f10a82ea38fe","release_group_mbid":"25f44677-2ecd-33fb-8454-9a9776496e3c","release_mbid":"bddad4c6-e9e1-4218-b9d2-b02a5a778a7d","submission_client":"foobar2000","submission_client_version":"1.3.2","totaldiscs":"1","totaltracks":"18","track_mbid":"08b1a5c1-c845-3d3b-9aa4-560a1af7f28c","tracknumber":"4"},"artist_name":"Sheryl Crow","mbid_mapping":{"artist_mbids":["80ccfede-c258-4575-a7ad-c982e9932e0f"],"artists":[{"artist_credit_name":"Sheryl Crow","artist_mbid":"80ccfede-c258-4575-a7ad-c982e9932e0f","join_phrase":""}],"caa_id":22039936498,"caa_release_mbid":"f10b4b40-4db1-4b9a-a223-06915fa06b3c","recording_mbid":"7ca21c82-1345-44df-b7ac-035a6e05d4ed","recording_name":"Strong Enough","release_mbid":"f10b4b40-4db1-4b9a-a223-06915fa06b3c"},"release_name":"The Very Best of MTV Unplugged","track_name":"Strong Enough"},"user_name":"awesomeuser"}],"user_id":"awesomeuser"}})")}; + EXPECT_EQ(result.listenCount, 1); + ASSERT_EQ(result.listens.size(), 1); + EXPECT_EQ(result.listens[0].trackNumber, 4); + } +} \ No newline at end of file diff --git a/src/libs/services/scrobbling/test/Scrobbling.cpp b/src/libs/services/scrobbling/test/Scrobbling.cpp index 88d0f430..5a4f6499 100644 --- a/src/libs/services/scrobbling/test/Scrobbling.cpp +++ b/src/libs/services/scrobbling/test/Scrobbling.cpp @@ -19,14 +19,15 @@ #include -#include "utils/ILogger.hpp" -#include "utils/Service.hpp" -#include "utils/StreamLogger.hpp" +#include "core/ILogger.hpp" +#include "core/Service.hpp" +#include "core/StreamLogger.hpp" int main(int argc, char** argv) { + using namespace lms; // log to stdout - Service logger{ std::make_unique(std::cout, EnumSet {Severity::FATAL, Severity::ERROR}) }; + core::Service logger{ std::make_unique(std::cout, core::EnumSet {core::logging::Severity::FATAL, core::logging::Severity::ERROR}) }; ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/src/libs/som/CMakeLists.txt b/src/libs/som/CMakeLists.txt index 87a2d53f..fbbc6f83 100644 --- a/src/libs/som/CMakeLists.txt +++ b/src/libs/som/CMakeLists.txt @@ -12,7 +12,7 @@ target_include_directories(lmssom PRIVATE ) target_link_libraries(lmssom PUBLIC - lmsutils + lmscore ) set_property(TARGET lmssom PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/src/libs/som/bench/SomBench.cpp b/src/libs/som/bench/SomBench.cpp index 6e1829bb..23eb763f 100644 --- a/src/libs/som/bench/SomBench.cpp +++ b/src/libs/som/bench/SomBench.cpp @@ -22,33 +22,34 @@ #include "som/Network.hpp" -using namespace SOM; - -// Benchmark function -static void BM_Matrix(benchmark::State& state) +namespace lms::som { - std::minstd_rand randomEngine{ 42 }; - std::uniform_int_distribution distrib{ 0, 1000 }; - - Matrix matrix{ static_cast(state.range(0)), static_cast(state.range(0)) }; - - for (Coordinate x {}; x < matrix.getWidth(); ++x ) + // Benchmark function + static void BM_Matrix(benchmark::State& state) { - for (Coordinate y {}; y < matrix.getHeight(); ++y ) - matrix.get({ x, y }) = distrib(randomEngine); + std::minstd_rand randomEngine{ 42 }; + std::uniform_int_distribution distrib{ 0, 1000 }; + + Matrix matrix{ static_cast(state.range(0)), static_cast(state.range(0)) }; + + for (Coordinate x{}; x < matrix.getWidth(); ++x) + { + for (Coordinate y{}; y < matrix.getHeight(); ++y) + matrix.get({ x, y }) = distrib(randomEngine); + } + + for (auto _ : state) + { + // Code inside this loop is measured repeatedly + const Position pos{ matrix.getPositionMinElement([](int a, int b) { return a < b; }) }; + benchmark::DoNotOptimize(pos); + } + + // Perform cleanup here if needed } - for (auto _ : state) - { - // Code inside this loop is measured repeatedly - const Position pos{ matrix.getPositionMinElement([](int a, int b) { return a < b; }) }; - benchmark::DoNotOptimize(pos); - } - - // Perform cleanup here if needed + // Register the benchmark with custom range + BENCHMARK(BM_Matrix)->Arg(3)->Arg(6)->Arg(12)->Arg(24); } -// Register the benchmark with custom range -BENCHMARK(BM_Matrix)->Arg(3)->Arg(6)->Arg(12)->Arg(24); - -BENCHMARK_MAIN(); \ No newline at end of file +BENCHMARK_MAIN(); diff --git a/src/libs/som/impl/DataNormalizer.cpp b/src/libs/som/impl/DataNormalizer.cpp index a4c39351..85cc32b3 100644 --- a/src/libs/som/impl/DataNormalizer.cpp +++ b/src/libs/som/impl/DataNormalizer.cpp @@ -23,7 +23,7 @@ #include #include -namespace SOM +namespace lms::som { template @@ -117,4 +117,4 @@ DataNormalizer::dump(std::ostream& os) const os << "(" << _minmax[i].min << ", " << _minmax[i].max << ")"; } -} // namespace SOM +} // namespace lms::som diff --git a/src/libs/som/impl/Network.cpp b/src/libs/som/impl/Network.cpp index b34a125b..65226342 100644 --- a/src/libs/som/impl/Network.cpp +++ b/src/libs/som/impl/Network.cpp @@ -26,10 +26,10 @@ #include #include -#include "utils/ILogger.hpp" -#include "utils/Random.hpp" +#include "core/ILogger.hpp" +#include "core/Random.hpp" -namespace SOM +namespace lms::som { void @@ -93,7 +93,7 @@ _neighbourhoodFunc {defaultNeighbourhoodFunc} for (Coordinate x {}; x < _refVectors.getWidth(); ++x) { for (InputVector::value_type& val : _refVectors.get({x,y})) - val = Random::getRealRandom(0, 1); + val = core::random::getRealRandom(0, 1); } } } @@ -298,7 +298,7 @@ Network::train(const std::vector& inputData, std::size_t nbIteratio if (progressCallback) progressCallback(curIter); - Random::shuffleContainer(inputDataShuffled); + core::random::shuffleContainer(inputDataShuffled); const LearningFactor learningFactor {_learningFactorFunc(curIter)}; @@ -325,6 +325,6 @@ Network::getRefVector(const Position& position) const } -} // namespace SOM +} // namespace lms::som diff --git a/src/libs/som/include/som/DataNormalizer.hpp b/src/libs/som/include/som/DataNormalizer.hpp index ef0de5fc..0ba14ace 100644 --- a/src/libs/som/include/som/DataNormalizer.hpp +++ b/src/libs/som/include/som/DataNormalizer.hpp @@ -24,7 +24,7 @@ #include "Network.hpp" -namespace SOM +namespace lms::som { class DataNormalizer @@ -57,4 +57,4 @@ class DataNormalizer std::vector _minmax; // Indexed min/max used to normalize data }; -} // namespace SOM +} // namespace lms::som diff --git a/src/libs/som/include/som/InputVector.hpp b/src/libs/som/include/som/InputVector.hpp index f31c215e..cc0f920a 100644 --- a/src/libs/som/include/som/InputVector.hpp +++ b/src/libs/som/include/som/InputVector.hpp @@ -23,12 +23,12 @@ #include #include -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -namespace SOM +namespace lms::som { -class Exception : public LmsException +class Exception : public core::LmsException { public: using LmsException::LmsException; diff --git a/src/libs/som/include/som/Matrix.hpp b/src/libs/som/include/som/Matrix.hpp index ceb0ed70..9192a678 100644 --- a/src/libs/som/include/som/Matrix.hpp +++ b/src/libs/som/include/som/Matrix.hpp @@ -24,7 +24,7 @@ #include #include -namespace SOM +namespace lms::som { using Coordinate = unsigned; @@ -110,18 +110,18 @@ namespace SOM std::vector _values; }; -} // ns SOM +} // ns lms::som namespace std { template<> - class hash + class hash { public: - size_t operator()(const SOM::Position& s) const + size_t operator()(const lms::som::Position& s) const { - size_t h1 = std::hash()(s.x); - size_t h2 = std::hash()(s.y); + size_t h1 = std::hash()(s.x); + size_t h2 = std::hash()(s.y); return h1 ^ (h2 << 1); } }; diff --git a/src/libs/som/include/som/Network.hpp b/src/libs/som/include/som/Network.hpp index bd0cc032..b951c9f8 100644 --- a/src/libs/som/include/som/Network.hpp +++ b/src/libs/som/include/som/Network.hpp @@ -27,7 +27,7 @@ #include "InputVector.hpp" #include "Matrix.hpp" -namespace SOM +namespace lms::som { using LearningFactor = InputVector::value_type; @@ -104,4 +104,4 @@ namespace SOM NeighbourhoodFunc _neighbourhoodFunc; }; -} // namespace SOM +} // namespace lms::som diff --git a/src/libs/som/test/SomTest.cpp b/src/libs/som/test/SomTest.cpp index 68a71285..38bee392 100644 --- a/src/libs/som/test/SomTest.cpp +++ b/src/libs/som/test/SomTest.cpp @@ -22,111 +22,112 @@ #include "som/DataNormalizer.hpp" #include "som/Network.hpp" -using namespace SOM; - -static constexpr InputVector::value_type EPSILON = 0.01; - -TEST(som, Matrix) +namespace lms::som { + static constexpr InputVector::value_type EPSILON = 0.01; + + TEST(som, Matrix) { - Matrix testMatrix {2, 2, 123}; { - const Position pos {0, 0}; - EXPECT_EQ(testMatrix[pos], 123); + Matrix testMatrix{ 2, 2, 123 }; + { + const Position pos{ 0, 0 }; + EXPECT_EQ(testMatrix[pos], 123); + } + { + const Position pos{ 0, 1 }; + EXPECT_EQ(testMatrix[pos], 123); + } + { + const Position pos{ 1, 0 }; + EXPECT_EQ(testMatrix[pos], 123); + } + { + const Position pos{ 1, 1 }; + EXPECT_EQ(testMatrix[pos], 123); + } } + } + + TEST(som, InputVector) + { { - const Position pos {0, 1}; - EXPECT_EQ(testMatrix[pos], 123); + InputVector test1{ 2 }; + test1[0] = 0; + test1[1] = 1; + + InputVector test2{ 2 }; + test2[0] = 1; + test2[1] = 0; + + InputVector test3{ test1 }; + test3 += test2; + EXPECT_LT(std::abs(test3[0] - 1), EPSILON); + EXPECT_LT(std::abs(test3[1] - 1), EPSILON); } + } + + TEST(som, Network) + { + Network network{ 2, 2, 1 }; + + const InputVector weights{ 1, 1 }; + std::vector trainData { - const Position pos {1, 0}; - EXPECT_EQ(testMatrix[pos], 123); + { 1, 50 }, + { 1, 100 }, + { 1, 150 }, + { 1, 200 }, + }; + + DataNormalizer normalizer{ 1 }; + normalizer.computeNormalizationFactors(trainData); + for (auto& data : trainData) + normalizer.normalizeData(data); + + network.dump(std::cout); + network.train(trainData, 20); + network.dump(std::cout); + + auto distFunc{ network.getDistanceFunc() }; + + EXPECT_LT((std::abs(distFunc({ 1, 0 }, { 1, 1 }, weights) - 1)), EPSILON); + EXPECT_LT((std::abs(distFunc({ 1, 0 }, { 1, 2 }, weights) - 4)), EPSILON); + EXPECT_LT(std::abs(distFunc({ 1, 0 }, { 1, 0.33 }, weights) - distFunc({ 1, 0.66 }, { 1, 1. }, weights)), EPSILON); + + { + std::unordered_set positions; + for (const InputVector& data : trainData) + positions.insert(network.getClosestRefVectorPosition(data)); + + EXPECT_EQ(positions.size(), 4); } + { - const Position pos {1, 1}; - EXPECT_EQ(testMatrix[pos], 123); + Position pos{ network.getClosestRefVectorPosition(InputVector{1, 0.66}) }; + for (std::size_t i{}; i < 40; ++i) + { + InputVector input{ 1, 130 + static_cast(i) }; + normalizer.normalizeData(input); + + EXPECT_EQ(network.getClosestRefVectorPosition(input), pos); + } + } + + { + Position pos{ network.getClosestRefVectorPosition(InputVector{1, 1}) }; + for (std::size_t i{}; i < 40; ++i) + { + InputVector input{ 1, 180 + static_cast(i) }; + normalizer.normalizeData(input); + + EXPECT_EQ(network.getClosestRefVectorPosition(input), pos); + } } } } -TEST(som, InputVector) -{ - { - InputVector test1 {2}; - test1[0] = 0; - test1[1] = 1; - - InputVector test2 {2}; - test2[0] = 1; - test2[1] = 0; - - InputVector test3 {test1}; - test3 += test2; - EXPECT_LT(std::abs(test3[0] - 1), EPSILON); - EXPECT_LT(std::abs(test3[1] - 1), EPSILON); - } -} - -TEST(som, Network) -{ - Network network {2, 2, 1}; - - const InputVector weights {1, 1}; - std::vector trainData - { - { 1, 50 }, - { 1, 100 }, - { 1, 150 }, - { 1, 200 }, - }; - - DataNormalizer normalizer {1}; - normalizer.computeNormalizationFactors(trainData); - for (auto& data: trainData) - normalizer.normalizeData(data); - - network.dump(std::cout); - network.train(trainData, 20); - network.dump(std::cout); - - auto distFunc {network.getDistanceFunc()}; - - EXPECT_LT((std::abs(distFunc({1, 0}, {1, 1}, weights) - 1)), EPSILON); - EXPECT_LT((std::abs(distFunc({1, 0}, {1, 2}, weights) - 4)), EPSILON); - EXPECT_LT(std::abs(distFunc({1, 0}, {1, 0.33}, weights) - distFunc({1, 0.66}, {1, 1.}, weights)), EPSILON); - - { - std::unordered_set positions; - for (const InputVector& data : trainData) - positions.insert(network.getClosestRefVectorPosition(data)); - - EXPECT_EQ(positions.size(), 4); - } - - { - Position pos {network.getClosestRefVectorPosition(InputVector{1, 0.66})}; - for (std::size_t i {}; i < 40; ++i) - { - InputVector input {1, 130 + static_cast(i) }; - normalizer.normalizeData(input); - - EXPECT_EQ(network.getClosestRefVectorPosition(input), pos); - } - } - - { - Position pos {network.getClosestRefVectorPosition(InputVector{1, 1})}; - for (std::size_t i {}; i < 40; ++i) - { - InputVector input {1, 180 + static_cast(i) }; - normalizer.normalizeData(input); - - EXPECT_EQ(network.getClosestRefVectorPosition(input), pos); - } - } -} - -int main(int argc, char **argv) +int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); diff --git a/src/libs/subsonic/CMakeLists.txt b/src/libs/subsonic/CMakeLists.txt index 07394e94..dedd0314 100644 --- a/src/libs/subsonic/CMakeLists.txt +++ b/src/libs/subsonic/CMakeLists.txt @@ -48,7 +48,7 @@ target_link_libraries(lmssubsonic PRIVATE lmsscanner lmsscrobbling lmsservice-cover - lmsutils + lmscore std::filesystem ) diff --git a/src/libs/subsonic/impl/ClientInfo.hpp b/src/libs/subsonic/impl/ClientInfo.hpp index ed25a18b..beec15ba 100644 --- a/src/libs/subsonic/impl/ClientInfo.hpp +++ b/src/libs/subsonic/impl/ClientInfo.hpp @@ -22,7 +22,7 @@ #include #include "ProtocolVersion.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { struct ClientInfo { diff --git a/src/libs/subsonic/impl/ParameterParsing.cpp b/src/libs/subsonic/impl/ParameterParsing.cpp index 4e474c03..6811c690 100644 --- a/src/libs/subsonic/impl/ParameterParsing.cpp +++ b/src/libs/subsonic/impl/ParameterParsing.cpp @@ -19,7 +19,7 @@ #include "ParameterParsing.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { bool hasParameter(const Wt::Http::ParameterMap& parameterMap, const std::string& param) { @@ -30,7 +30,7 @@ namespace API::Subsonic { if (password.find("enc:") == 0) { - auto decodedPassword{ StringUtils::stringFromHex(password.substr(4)) }; + auto decodedPassword{ core::stringUtils::stringFromHex(password.substr(4)) }; if (!decodedPassword) return password; // fallback on plain password diff --git a/src/libs/subsonic/impl/ParameterParsing.hpp b/src/libs/subsonic/impl/ParameterParsing.hpp index bf4a678f..99b96e59 100644 --- a/src/libs/subsonic/impl/ParameterParsing.hpp +++ b/src/libs/subsonic/impl/ParameterParsing.hpp @@ -26,10 +26,10 @@ #include #include "database/Types.hpp" -#include "utils/String.hpp" +#include "core/String.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { template @@ -43,7 +43,7 @@ namespace API::Subsonic for (const std::string& param : it->second) { - auto value{ StringUtils::readAs(param) }; + auto value{ core::stringUtils::readAs(param) }; if (value) res.emplace_back(std::move(*value)); } diff --git a/src/libs/subsonic/impl/ProtocolVersion.cpp b/src/libs/subsonic/impl/ProtocolVersion.cpp index 8864ae19..94b5027e 100644 --- a/src/libs/subsonic/impl/ProtocolVersion.cpp +++ b/src/libs/subsonic/impl/ProtocolVersion.cpp @@ -19,31 +19,31 @@ #include "ProtocolVersion.hpp" -namespace StringUtils +namespace lms::core::stringUtils { template<> - std::optional readAs(std::string_view str) + std::optional readAs(std::string_view str) { // Expects "X.Y.Z" - const auto numbers{ StringUtils::splitString(str, '.') }; + const auto numbers{ core::stringUtils::splitString(str, '.') }; if (numbers.size() < 2 || numbers.size() > 3) return std::nullopt; - API::Subsonic::ProtocolVersion version; + api::subsonic::ProtocolVersion version; - auto number{ StringUtils::readAs(numbers[0]) }; + auto number{ core::stringUtils::readAs(numbers[0]) }; if (!number) return std::nullopt; version.major = *number; - number = { StringUtils::readAs(numbers[1]) }; + number = { core::stringUtils::readAs(numbers[1]) }; if (!number) return std::nullopt; version.minor = *number; if (numbers.size() == 3) { - number = { StringUtils::readAs(numbers[2]) }; + number = { core::stringUtils::readAs(numbers[2]) }; if (!number) return std::nullopt; version.patch = *number; diff --git a/src/libs/subsonic/impl/ProtocolVersion.hpp b/src/libs/subsonic/impl/ProtocolVersion.hpp index c527592d..7e54a304 100644 --- a/src/libs/subsonic/impl/ProtocolVersion.hpp +++ b/src/libs/subsonic/impl/ProtocolVersion.hpp @@ -19,9 +19,9 @@ #pragma once -#include "utils/String.hpp" +#include "core/String.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { struct ProtocolVersion { @@ -34,9 +34,9 @@ namespace API::Subsonic static inline constexpr std::string_view serverVersion{ "6" }; } -namespace StringUtils +namespace lms::core::stringUtils { template<> - std::optional readAs(std::string_view str); + std::optional readAs(std::string_view str); } diff --git a/src/libs/subsonic/impl/RequestContext.hpp b/src/libs/subsonic/impl/RequestContext.hpp index 83ec90d6..d751111c 100644 --- a/src/libs/subsonic/impl/RequestContext.hpp +++ b/src/libs/subsonic/impl/RequestContext.hpp @@ -27,18 +27,18 @@ #include "ClientInfo.hpp" #include "ProtocolVersion.hpp" -namespace Database +namespace lms::db { class Session; } -namespace API::Subsonic +namespace lms::api::subsonic { struct RequestContext { const Wt::Http::ParameterMap& parameters; - Database::Session& dbSession; - Database::UserId userId; + db::Session& dbSession; + db::UserId userId; ClientInfo clientInfo; ProtocolVersion serverProtocolVersion; bool enableOpenSubsonic{ true }; diff --git a/src/libs/subsonic/impl/SubsonicId.cpp b/src/libs/subsonic/impl/SubsonicId.cpp index 6e00b340..7d0f3ad4 100644 --- a/src/libs/subsonic/impl/SubsonicId.cpp +++ b/src/libs/subsonic/impl/SubsonicId.cpp @@ -21,23 +21,23 @@ #include "SubsonicResponse.hpp" -#include "utils/ILogger.hpp" -#include "utils/String.hpp" +#include "core/ILogger.hpp" +#include "core/String.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - std::string idToString(Database::ArtistId id) + std::string idToString(db::ArtistId id) { return "ar-" + id.toString(); } - std::string idToString(Database::MediaLibraryId id) + std::string idToString(db::MediaLibraryId id) { // No need to prefix as this is only used at well known places return id.toString(); } - std::string idToString(Database::ReleaseId id) + std::string idToString(db::ReleaseId id) { return "al-" + id.toString(); } @@ -47,97 +47,97 @@ namespace API::Subsonic return "root"; } - std::string idToString(Database::TrackId id) + std::string idToString(db::TrackId id) { return "tr-" + id.toString(); } - std::string idToString(Database::TrackListId id) + std::string idToString(db::TrackListId id) { return "pl-" + id.toString(); } -} // namespace API::Subsonic +} // namespace lms::api::subsonic -namespace StringUtils +namespace lms::core::stringUtils { template<> - std::optional readAs(std::string_view str) + std::optional readAs(std::string_view str) { - std::vector values{ StringUtils::splitString(str, '-') }; + std::vector values{ core::stringUtils::splitString(str, '-') }; if (values.size() != 2) return std::nullopt; if (values[0] != "ar") return std::nullopt; - if (const auto value{ StringUtils::readAs(values[1]) }) - return Database::ArtistId{ *value }; + if (const auto value{ core::stringUtils::readAs(values[1]) }) + return db::ArtistId{ *value }; return std::nullopt; } template<> - std::optional readAs(std::string_view str) + std::optional readAs(std::string_view str) { - if (const auto value{ StringUtils::readAs(str) }) - return Database::MediaLibraryId{ *value }; + if (const auto value{ core::stringUtils::readAs(str) }) + return db::MediaLibraryId{ *value }; return std::nullopt; } template<> - std::optional readAs(std::string_view str) + std::optional readAs(std::string_view str) { - std::vector values{ StringUtils::splitString(str, '-') }; + std::vector values{ core::stringUtils::splitString(str, '-') }; if (values.size() != 2) return std::nullopt; if (values[0] != "al") return std::nullopt; - if (const auto value{ StringUtils::readAs(values[1]) }) - return Database::ReleaseId{ *value }; + if (const auto value{ core::stringUtils::readAs(values[1]) }) + return db::ReleaseId{ *value }; return std::nullopt; } template<> - std::optional readAs(std::string_view str) + std::optional readAs(std::string_view str) { if (str == "root") - return API::Subsonic::RootId{}; + return api::subsonic::RootId{}; return std::nullopt; } template<> - std::optional readAs(std::string_view str) + std::optional readAs(std::string_view str) { - std::vector values{ StringUtils::splitString(str, '-') }; + std::vector values{ core::stringUtils::splitString(str, '-') }; if (values.size() != 2) return std::nullopt; if (values[0] != "tr") return std::nullopt; - if (const auto value{ StringUtils::readAs(values[1]) }) - return Database::TrackId{ *value }; + if (const auto value{ core::stringUtils::readAs(values[1]) }) + return db::TrackId{ *value }; return std::nullopt; } template<> - std::optional readAs(std::string_view str) + std::optional readAs(std::string_view str) { - std::vector values{ StringUtils::splitString(str, '-') }; + std::vector values{ core::stringUtils::splitString(str, '-') }; if (values.size() != 2) return std::nullopt; if (values[0] != "pl") return std::nullopt; - if (const auto value{ StringUtils::readAs(values[1]) }) - return Database::TrackListId{ *value }; + if (const auto value{ core::stringUtils::readAs(values[1]) }) + return db::TrackListId{ *value }; return std::nullopt; } diff --git a/src/libs/subsonic/impl/SubsonicId.hpp b/src/libs/subsonic/impl/SubsonicId.hpp index 6f356084..770d3177 100644 --- a/src/libs/subsonic/impl/SubsonicId.hpp +++ b/src/libs/subsonic/impl/SubsonicId.hpp @@ -24,39 +24,39 @@ #include "database/ReleaseId.hpp" #include "database/TrackId.hpp" #include "database/TrackListId.hpp" -#include "utils/String.hpp" +#include "core/String.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { struct RootId {}; - std::string idToString(Database::ArtistId id); - std::string idToString(Database::MediaLibraryId id); - std::string idToString(Database::ReleaseId id); - std::string idToString(Database::TrackId id); - std::string idToString(Database::TrackListId id); + std::string idToString(db::ArtistId id); + std::string idToString(db::MediaLibraryId id); + std::string idToString(db::ReleaseId id); + std::string idToString(db::TrackId id); + std::string idToString(db::TrackListId id); std::string idToString(RootId); -} // namespace API::Subsonic +} // namespace lms::api::subsonic // Used to parse parameters -namespace StringUtils +namespace lms::core::stringUtils { template<> - std::optional readAs(std::string_view str); + std::optional readAs(std::string_view str); template<> - std::optional readAs(std::string_view str); + std::optional readAs(std::string_view str); template<> - std::optional readAs(std::string_view str); + std::optional readAs(std::string_view str); template<> - std::optional readAs(std::string_view str); + std::optional readAs(std::string_view str); template<> - std::optional readAs(std::string_view str); + std::optional readAs(std::string_view str); template<> - std::optional readAs(std::string_view str); + std::optional readAs(std::string_view str); } diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index 0f2e23b2..e1c232b8 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -27,14 +27,14 @@ #include "database/Db.hpp" #include "database/Session.hpp" #include "database/User.hpp" -#include "utils/EnumSet.hpp" -#include "utils/LiteralString.hpp" -#include "utils/IConfig.hpp" -#include "utils/ILogger.hpp" -#include "utils/ITraceLogger.hpp" -#include "utils/Service.hpp" -#include "utils/String.hpp" -#include "utils/Utils.hpp" +#include "core/EnumSet.hpp" +#include "core/LiteralString.hpp" +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" +#include "core/Utils.hpp" #include "entrypoints/AlbumSongLists.hpp" #include "entrypoints/Browsing.hpp" @@ -53,11 +53,9 @@ #include "SubsonicResponse.hpp" #include "Utils.hpp" -using namespace Database; - -namespace API::Subsonic +namespace lms::api::subsonic { - std::unique_ptr createSubsonicResource(Database::Db& db) + std::unique_ptr createSubsonicResource(db::Db& db) { return std::make_unique(db); } @@ -68,7 +66,7 @@ namespace API::Subsonic { std::unordered_map res; - Service::get()->visitStrings("api-subsonic-old-server-protocol-clients", + core::Service::get()->visitStrings("api-subsonic-old-server-protocol-clients", [&](std::string_view client) { res.emplace(std::string{ client }, ProtocolVersion{ 1, 12, 0 }); @@ -81,7 +79,7 @@ namespace API::Subsonic { std::unordered_set res; - Service::get()->visitStrings("api-open-subsonic-disabled-clients", + core::Service::get()->visitStrings("api-open-subsonic-disabled-clients", [&](std::string_view client) { res.emplace(std::string{ client }); @@ -94,7 +92,7 @@ namespace API::Subsonic { std::unordered_set res; - Service::get()->visitStrings("api-subsonic-default-cover-clients", + core::Service::get()->visitStrings("api-subsonic-default-cover-clients", [&](std::string_view client) { res.emplace(std::string{ client }); @@ -135,11 +133,11 @@ namespace API::Subsonic return res; } - void checkUserTypeIsAllowed(RequestContext& context, EnumSet allowedUserTypes) + void checkUserTypeIsAllowed(RequestContext& context, core::EnumSet allowedUserTypes) { auto transaction{ context.dbSession.createReadTransaction() }; - User::pointer currentUser{ User::find(context.dbSession, context.userId) }; + db::User::pointer currentUser{ db::User::find(context.dbSession, context.userId) }; if (!currentUser) throw RequestedDataNotFoundError{}; @@ -157,11 +155,11 @@ namespace API::Subsonic struct RequestEntryPointInfo { RequestHandlerFunc func; - EnumSet allowedUserTypes{ UserType::DEMO, UserType::REGULAR, UserType::ADMIN }; + core::EnumSet allowedUserTypes{ db::UserType::DEMO, db::UserType::REGULAR, db::UserType::ADMIN }; CheckImplementedFunc checkFunc{}; }; - static const std::unordered_map requestEntryPoints + static const std::unordered_map requestEntryPoints { // System {"/ping", {handlePingRequest}}, @@ -249,11 +247,11 @@ namespace API::Subsonic // User management {"/getUser", {handleGetUserRequest}}, - {"/getUsers", {handleGetUsersRequest, {UserType::ADMIN}}}, - {"/createUser", {handleCreateUserRequest, {UserType::ADMIN}, &Utils::checkSetPasswordImplemented}}, - {"/updateUser", {handleUpdateUserRequest, {UserType::ADMIN}}}, - {"/deleteUser", {handleDeleteUserRequest, {UserType::ADMIN}}}, - {"/changePassword", {handleChangePassword, {UserType::REGULAR, UserType::ADMIN}, &Utils::checkSetPasswordImplemented}}, + {"/getUsers", {handleGetUsersRequest, {db::UserType::ADMIN}}}, + {"/createUser", {handleCreateUserRequest, {db::UserType::ADMIN}, &utils::checkSetPasswordImplemented}}, + {"/updateUser", {handleUpdateUserRequest, {db::UserType::ADMIN}}}, + {"/deleteUser", {handleDeleteUserRequest, {db::UserType::ADMIN}}}, + {"/changePassword", {handleChangePassword, {db::UserType::REGULAR, db::UserType::ADMIN}, &utils::checkSetPasswordImplemented}}, // Bookmarks {"/getBookmarks", {handleGetBookmarks}}, @@ -263,12 +261,12 @@ namespace API::Subsonic {"/savePlayQueue", {handleNotImplemented}}, // Media library scanning - {"/getScanStatus", {Scan::handleGetScanStatus, {UserType::ADMIN}}}, - {"/startScan", {Scan::handleStartScan, {UserType::ADMIN}}}, + {"/getScanStatus", {Scan::handleGetScanStatus, {db::UserType::ADMIN}}}, + {"/startScan", {Scan::handleStartScan, {db::UserType::ADMIN}}}, }; using MediaRetrievalHandlerFunc = std::function; - static std::unordered_map mediaRetrievalHandlers + static std::unordered_map mediaRetrievalHandlers { // Media retrieval {"/download", handleDownload}, @@ -277,7 +275,7 @@ namespace API::Subsonic }; } - SubsonicResource::SubsonicResource(Db& db) + SubsonicResource::SubsonicResource(db::Db& db) : _serverProtocolVersionsByClient{ readConfigProtocolVersions() } , _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() } , _defaultCoverClients{ readDefaultCoverClients() } @@ -294,7 +292,7 @@ namespace API::Subsonic LMS_LOG(API_SUBSONIC, DEBUG, "Handling request " << requestId << " '" << request.pathInfo() << "', continuation = " << (request.continuation() ? "true" : "false") << ", params = " << parameterMapToDebugString(request.getParameterMap())); std::string requestPath{ request.pathInfo() }; - if (StringUtils::stringEndsWith(requestPath, ".view")) + if (core::stringUtils::stringEndsWith(requestPath, ".view")) requestPath.resize(requestPath.length() - 5); // Optional parameters @@ -402,48 +400,48 @@ namespace API::Subsonic { const Wt::Http::ParameterMap& parameters{ request.getParameterMap() }; const ClientInfo clientInfo{ getClientInfo(parameters) }; - const Database::UserId userId{ authenticateUser(request, clientInfo) }; + const db::UserId userId{ authenticateUser(request, clientInfo) }; bool enableOpenSubsonic{ _openSubsonicDisabledClients.find(clientInfo.name) == std::cend(_openSubsonicDisabledClients) }; bool enableDefaultCover{ _defaultCoverClients.find(clientInfo.name) != std::cend(_openSubsonicDisabledClients) }; return { parameters, _db.getTLSSession(), userId, clientInfo, getServerProtocolVersion(clientInfo.name), enableOpenSubsonic, enableDefaultCover }; } - Database::UserId SubsonicResource::authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo) + db::UserId SubsonicResource::authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo) { // if the request if a continuation, the user is already authenticated if (request.continuation()) { - Database::Session& session{ _db.getTLSSession() }; + db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - const auto user{ Database::User::find(session, clientInfo.user) }; + const auto user{ db::User::find(session, clientInfo.user) }; if (!user) throw UserNotAuthorizedError{}; return user->getId(); } - if (auto * authEnvService{ Service<::Auth::IEnvService>::get() }) + if (auto * authEnvService{ core::Service::get() }) { const auto checkResult{ authEnvService->processRequest(request) }; - if (checkResult.state != ::Auth::IEnvService::CheckResult::State::Granted) + if (checkResult.state != auth::IEnvService::CheckResult::State::Granted) throw UserNotAuthorizedError{}; return *checkResult.userId; } - else if (auto * authPasswordService{ Service<::Auth::IPasswordService>::get() }) + else if (auto * authPasswordService{ core::Service::get() }) { const auto checkResult{ authPasswordService->checkUserPassword(boost::asio::ip::address::from_string(request.clientAddress()), clientInfo.user, clientInfo.password) }; switch (checkResult.state) { - case Auth::IPasswordService::CheckResult::State::Granted: + case auth::IPasswordService::CheckResult::State::Granted: return *checkResult.userId; break; - case Auth::IPasswordService::CheckResult::State::Denied: + case auth::IPasswordService::CheckResult::State::Denied: throw WrongUsernameOrPasswordError{}; - case Auth::IPasswordService::CheckResult::State::Throttled: + case auth::IPasswordService::CheckResult::State::Throttled: throw LoginThrottledGenericError{}; } } @@ -451,5 +449,5 @@ namespace API::Subsonic throw InternalErrorGenericError{ "No service available to authenticate user" }; } -} // namespace api::subsonic +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/SubsonicResource.hpp b/src/libs/subsonic/impl/SubsonicResource.hpp index 31377725..eda68edf 100644 --- a/src/libs/subsonic/impl/SubsonicResource.hpp +++ b/src/libs/subsonic/impl/SubsonicResource.hpp @@ -29,18 +29,18 @@ #include "ClientInfo.hpp" #include "RequestContext.hpp" -namespace Database +namespace lms::db { class Db; } -namespace API::Subsonic +namespace lms::api::subsonic { class SubsonicResource final : public Wt::WResource { public: - SubsonicResource(Database::Db& db); + SubsonicResource(db::Db& db); private: void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) override; @@ -49,13 +49,13 @@ namespace API::Subsonic static void checkProtocolVersion(ProtocolVersion client, ProtocolVersion server); ClientInfo getClientInfo(const Wt::Http::ParameterMap& parameters); RequestContext buildRequestContext(const Wt::Http::Request& request); - Database::UserId authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo); + db::UserId authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo); const std::unordered_map _serverProtocolVersionsByClient; const std::unordered_set _openSubsonicDisabledClients; const std::unordered_set _defaultCoverClients; - Database::Db& _db; + db::Db& _db; }; } // namespace diff --git a/src/libs/subsonic/impl/SubsonicResponse.cpp b/src/libs/subsonic/impl/SubsonicResponse.cpp index 70153e2c..ea1cc831 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.cpp +++ b/src/libs/subsonic/impl/SubsonicResponse.cpp @@ -24,11 +24,11 @@ #include #include -#include "utils/Exception.hpp" -#include "utils/String.hpp" +#include "core/Exception.hpp" +#include "core/String.hpp" #include "ProtocolVersion.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { std::string_view ResponseFormatToMimeType(ResponseFormat format) { @@ -366,7 +366,7 @@ namespace API::Subsonic void Response::JsonSerializer::serializeEscapedString(std::ostream& os, std::string_view str) { os << '\"'; - StringUtils::writeJsonEscapedString(os, str); + core::stringUtils::writeJsonEscapedString(os, str); os << '\"'; } diff --git a/src/libs/subsonic/impl/SubsonicResponse.hpp b/src/libs/subsonic/impl/SubsonicResponse.hpp index 5ff50686..cd80fe1c 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.hpp +++ b/src/libs/subsonic/impl/SubsonicResponse.hpp @@ -25,10 +25,10 @@ #include #include -#include "utils/LiteralString.hpp" +#include "core/LiteralString.hpp" #include "RequestContext.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { // Max count expected from all API methods that expose a count static inline constexpr std::size_t defaultMaxCountSize{ 1000 }; @@ -206,7 +206,7 @@ namespace API::Subsonic class Node { public: - using Key = LiteralString; + using Key = core::LiteralString; void setAttribute(Key key, std::string_view value); diff --git a/src/libs/subsonic/impl/Utils.cpp b/src/libs/subsonic/impl/Utils.cpp index 2bef51be..8bf38dbe 100644 --- a/src/libs/subsonic/impl/Utils.cpp +++ b/src/libs/subsonic/impl/Utils.cpp @@ -19,23 +19,23 @@ #include "Utils.hpp" -#include "utils/Service.hpp" -#include "utils/String.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "services/auth/IPasswordService.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic::Utils +namespace lms::api::subsonic::utils { void checkSetPasswordImplemented() { - Auth::IPasswordService* passwordService{ Service::get() }; + auth::IPasswordService* passwordService{ core::Service::get() }; if (!passwordService || !passwordService->canSetPasswords()) throw NotImplementedGenericError{}; } std::string makeNameFilesystemCompatible(std::string_view name) { - return StringUtils::replaceInString(name, "/", "_"); + return core::stringUtils::replaceInString(name, "/", "_"); } } \ No newline at end of file diff --git a/src/libs/subsonic/impl/Utils.hpp b/src/libs/subsonic/impl/Utils.hpp index 0d85bce4..95b0d3f3 100644 --- a/src/libs/subsonic/impl/Utils.hpp +++ b/src/libs/subsonic/impl/Utils.hpp @@ -22,7 +22,7 @@ #include #include -namespace API::Subsonic::Utils +namespace lms::api::subsonic::utils { void checkSetPasswordImplemented(); std::string makeNameFilesystemCompatible(std::string_view name); diff --git a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp index 44b2f506..3aecebcd 100644 --- a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp +++ b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp @@ -30,13 +30,13 @@ #include "responses/Album.hpp" #include "responses/Artist.hpp" #include "responses/Song.hpp" -#include "utils/Service.hpp" +#include "core/Service.hpp" #include "ParameterParsing.hpp" #include "SubsonicId.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; namespace { @@ -55,8 +55,8 @@ namespace API::Subsonic const Range range{ offset, size }; RangeResults releases; - Scrobbling::IScrobblingService& scrobblingService{ *Service::get() }; - Feedback::IFeedbackService& feedbackService{ *Service::get() }; + scrobbling::IScrobblingService& scrobblingService{ *core::Service::get() }; + feedback::IFeedbackService& feedbackService{ *core::Service::get() }; auto transaction{ context.dbSession.createReadTransaction() }; @@ -116,7 +116,7 @@ namespace API::Subsonic } else if (type == "frequent") { - Scrobbling::IScrobblingService::FindParameters params; + scrobbling::IScrobblingService::FindParameters params; params.setUser(context.userId); params.setRange(range); params.setMediaLibrary(mediaLibraryId); @@ -145,7 +145,7 @@ namespace API::Subsonic } else if (type == "recent") { - Scrobbling::IScrobblingService::FindParameters params; + scrobbling::IScrobblingService::FindParameters params; params.setUser(context.userId); params.setRange(range); params.setMediaLibrary(mediaLibraryId); @@ -154,7 +154,7 @@ namespace API::Subsonic } else if (type == "starred") { - Feedback::IFeedbackService::FindParameters params; + feedback::IFeedbackService::FindParameters params; params.setUser(context.userId); params.setRange(range); params.setMediaLibrary(mediaLibraryId); @@ -192,10 +192,10 @@ namespace API::Subsonic Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response::Node& starredNode{ response.createNode(id3 ? Response::Node::Key{ "starred2" } : Response::Node::Key{ "starred" }) }; - Feedback::IFeedbackService& feedbackService{ *Service::get() }; + feedback::IFeedbackService& feedbackService{ *core::Service::get() }; { - Feedback::IFeedbackService::ArtistFindParameters artistFindParams; + feedback::IFeedbackService::ArtistFindParameters artistFindParams; artistFindParams.setUser(context.userId); artistFindParams.setSortMethod(ArtistSortMethod::BySortName); for (const ArtistId artistId : feedbackService.findStarredArtists(artistFindParams).results) @@ -205,7 +205,7 @@ namespace API::Subsonic } } - Feedback::IFeedbackService::FindParameters findParameters; + feedback::IFeedbackService::FindParameters findParameters; findParameters.setUser(context.userId); findParameters.setMediaLibrary(mediaLibrary); diff --git a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.hpp b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.hpp index 3968a78f..0d704602 100644 --- a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.hpp +++ b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.hpp @@ -22,7 +22,7 @@ #include "RequestContext.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response handleGetAlbumListRequest(RequestContext& context); Response handleGetAlbumList2Request(RequestContext& context); diff --git a/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp b/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp index 47e2a93d..91785a0e 100644 --- a/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp +++ b/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp @@ -28,9 +28,9 @@ #include "ParameterParsing.hpp" #include "SubsonicId.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; Response handleGetBookmarks(RequestContext& context) { diff --git a/src/libs/subsonic/impl/entrypoints/Bookmarks.hpp b/src/libs/subsonic/impl/entrypoints/Bookmarks.hpp index e1b6d5cd..bcbb7921 100644 --- a/src/libs/subsonic/impl/entrypoints/Bookmarks.hpp +++ b/src/libs/subsonic/impl/entrypoints/Bookmarks.hpp @@ -22,7 +22,7 @@ #include "RequestContext.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response handleGetBookmarks(RequestContext& context); Response handleCreateBookmark(RequestContext& context); diff --git a/src/libs/subsonic/impl/entrypoints/Browsing.cpp b/src/libs/subsonic/impl/entrypoints/Browsing.cpp index 9d496a3d..ed956466 100644 --- a/src/libs/subsonic/impl/entrypoints/Browsing.cpp +++ b/src/libs/subsonic/impl/entrypoints/Browsing.cpp @@ -28,9 +28,9 @@ #include "database/User.hpp" #include "services/recommendation/IRecommendationService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "utils/ILogger.hpp" -#include "utils/Random.hpp" -#include "utils/Service.hpp" +#include "core/ILogger.hpp" +#include "core/Random.hpp" +#include "core/Service.hpp" #include "responses/Album.hpp" #include "responses/Artist.hpp" #include "responses/Genre.hpp" @@ -39,9 +39,9 @@ #include "SubsonicId.hpp" #include "Utils.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; static const std::string_view reportedDummyDate{ "2000-01-01T00:00:00" }; static const unsigned long long reportedDummyDateULong{ 946684800000ULL }; // 2000-01-01T00:00:00 UTC @@ -66,12 +66,12 @@ namespace API::Subsonic if (!artist) throw RequestedDataNotFoundError{}; - std::optional artistMBID{ artist->getMBID() }; + std::optional artistMBID{ artist->getMBID() }; if (artistMBID) artistInfoNode.createChild("musicBrainzId").setValue(artistMBID->getAsString()); } - auto similarArtistsId{ Service::get()->getSimilarArtists(id, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, count) }; + auto similarArtistsId{ core::Service::get()->getSimilarArtists(id, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, count) }; { auto transaction{ context.dbSession.createReadTransaction() }; @@ -182,7 +182,7 @@ namespace API::Subsonic { // API says: "Returns a random collection of songs from the given artist and similar artists" const std::size_t similarArtistCount{ count / 5 }; - std::vector artistIds{ Service::get()->getSimilarArtists(artistId, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, similarArtistCount) }; + std::vector artistIds{ core::Service::get()->getSimilarArtists(artistId, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, similarArtistCount) }; artistIds.push_back(artistId); const std::size_t meanTrackCountPerArtist{ (count / artistIds.size()) + 1 }; @@ -213,7 +213,7 @@ namespace API::Subsonic // API says: "Returns a random collection of songs from the given artist and similar artists" // so let's extend this for release const std::size_t similarReleaseCount{ count / 5 }; - std::vector releaseIds{ Service::get()->getSimilarReleases(releaseId, similarReleaseCount) }; + std::vector releaseIds{ core::Service::get()->getSimilarReleases(releaseId, similarReleaseCount) }; releaseIds.push_back(releaseId); const std::size_t meanTrackCountPerRelease{ (count / releaseIds.size()) + 1 }; @@ -241,7 +241,7 @@ namespace API::Subsonic std::vector findSimilarSongs(RequestContext&, TrackId trackId, std::size_t count) { - return Service::get()->findSimilarTracks({ trackId }, count); + return core::Service::get()->findSimilarTracks({ trackId }, count); } Response handleGetSimilarSongsRequestCommon(RequestContext& context, bool id3) @@ -262,7 +262,7 @@ namespace API::Subsonic else throw BadParameterGenericError{ "id" }; - Random::shuffleContainer(tracks); + core::random::shuffleContainer(tracks); auto transaction{ context.dbSession.createReadTransaction() }; @@ -343,7 +343,7 @@ namespace API::Subsonic if (!artist) throw RequestedDataNotFoundError{}; - directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(artist->getName())); + directoryNode.setAttribute("name", utils::makeNameFilesystemCompatible(artist->getName())); Release::find(context.dbSession, Release::FindParameters{}.setArtist(*artistId), [&](const Release::pointer& release) { @@ -358,7 +358,7 @@ namespace API::Subsonic if (!release) throw RequestedDataNotFoundError{}; - directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(release->getName())); + directoryNode.setAttribute("name", utils::makeNameFilesystemCompatible(release->getName())); Track::find(context.dbSession, Track::FindParameters{}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release), [&](const Track::pointer& track) { @@ -512,12 +512,12 @@ namespace API::Subsonic Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response::Node& topSongs{ response.createNode("topSongs") }; - Scrobbling::IScrobblingService::FindParameters params; + scrobbling::IScrobblingService::FindParameters params; params.setUser(context.userId); - params.setRange(Database::Range{ 0, count }); + params.setRange(db::Range{ 0, count }); params.setArtist(artists.front()->getId()); - const auto trackIds{ Service::get()->getTopTracks(params) }; + const auto trackIds{ core::Service::get()->getTopTracks(params) }; for (const TrackId trackId : trackIds.results) { if (Track::pointer track{ Track::find(context.dbSession, trackId) }) diff --git a/src/libs/subsonic/impl/entrypoints/Browsing.hpp b/src/libs/subsonic/impl/entrypoints/Browsing.hpp index d87c787e..bfb169e2 100644 --- a/src/libs/subsonic/impl/entrypoints/Browsing.hpp +++ b/src/libs/subsonic/impl/entrypoints/Browsing.hpp @@ -22,7 +22,7 @@ #include "RequestContext.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response handleGetMusicFoldersRequest(RequestContext& context); Response handleGetIndexesRequest(RequestContext& context); diff --git a/src/libs/subsonic/impl/entrypoints/MediaAnnotation.cpp b/src/libs/subsonic/impl/entrypoints/MediaAnnotation.cpp index 198594a7..b7563a89 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaAnnotation.cpp +++ b/src/libs/subsonic/impl/entrypoints/MediaAnnotation.cpp @@ -26,13 +26,13 @@ #include "database/TrackId.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "utils/Service.hpp" +#include "core/Service.hpp" #include "ParameterParsing.hpp" #include "SubsonicId.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; namespace { @@ -61,13 +61,13 @@ namespace API::Subsonic StarParameters params{ getStarParameters(context.parameters) }; for (const ArtistId id : params.artistIds) - Service::get()->star(context.userId, id); + core::Service::get()->star(context.userId, id); for (const ReleaseId id : params.releaseIds) - Service::get()->star(context.userId, id); + core::Service::get()->star(context.userId, id); for (const TrackId id : params.trackIds) - Service::get()->star(context.userId, id); + core::Service::get()->star(context.userId, id); return Response::createOkResponse(context.serverProtocolVersion); } @@ -77,13 +77,13 @@ namespace API::Subsonic StarParameters params{ getStarParameters(context.parameters) }; for (const ArtistId id : params.artistIds) - Service::get()->unstar(context.userId, id); + core::Service::get()->unstar(context.userId, id); for (const ReleaseId id : params.releaseIds) - Service::get()->unstar(context.userId, id); + core::Service::get()->unstar(context.userId, id); for (const TrackId id : params.trackIds) - Service::get()->unstar(context.userId, id); + core::Service::get()->unstar(context.userId, id); return Response::createOkResponse(context.serverProtocolVersion); } @@ -108,13 +108,13 @@ namespace API::Subsonic if (!submission) { - Service::get()->listenStarted({ context.userId, ids.front() }); + core::Service::get()->listenStarted({ context.userId, ids.front() }); } else { if (times.empty()) { - Service::get()->listenFinished({ context.userId, ids.front() }); + core::Service::get()->listenFinished({ context.userId, ids.front() }); } else { @@ -122,7 +122,7 @@ namespace API::Subsonic { const TrackId trackId{ ids[i] }; const unsigned long time{ times[i] }; - Service::get()->addTimedListen({ {context.userId, trackId}, Wt::WDateTime::fromTime_t(static_cast(time / 1000)) }); + core::Service::get()->addTimedListen({ {context.userId, trackId}, Wt::WDateTime::fromTime_t(static_cast(time / 1000)) }); } } } diff --git a/src/libs/subsonic/impl/entrypoints/MediaAnnotation.hpp b/src/libs/subsonic/impl/entrypoints/MediaAnnotation.hpp index 593d3bd3..0197afb3 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaAnnotation.hpp +++ b/src/libs/subsonic/impl/entrypoints/MediaAnnotation.hpp @@ -22,7 +22,7 @@ #include "RequestContext.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response handleStarRequest(RequestContext& context); Response handleUnstarRequest(RequestContext& context); diff --git a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp index 78f2ea25..38beb278 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp +++ b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp @@ -20,11 +20,11 @@ #include "MediaLibraryScanning.hpp" #include "services/scanner/IScannerService.hpp" -#include "utils/Service.hpp" +#include "core/Service.hpp" -namespace API::Subsonic::Scan +namespace lms::api::subsonic::Scan { - using namespace Scanner; + using namespace scanner; namespace { @@ -32,7 +32,7 @@ namespace API::Subsonic::Scan { Response::Node statusResponse; - const IScannerService::Status scanStatus{ Service::get()->getStatus() }; + const IScannerService::Status scanStatus{ core::Service::get()->getStatus() }; statusResponse.setAttribute("scanning", scanStatus.currentState == IScannerService::State::InProgress); if (scanStatus.currentState == IScannerService::State::InProgress) @@ -59,7 +59,7 @@ namespace API::Subsonic::Scan Response handleStartScan(RequestContext& context) { - Service::get()->requestImmediateScan(false); + core::Service::get()->requestImmediateScan(false); Response response{ Response::createOkResponse(context.serverProtocolVersion) }; response.addNode("scanStatus", createStatusResponseNode()); diff --git a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp index 3fd5329c..9cdf3406 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp +++ b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp @@ -22,7 +22,7 @@ #include "RequestContext.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic::Scan +namespace lms::api::subsonic::Scan { Response handleGetScanStatus(RequestContext& context); Response handleStartScan(RequestContext& context); diff --git a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp index eed28f47..2c729447 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp +++ b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp @@ -28,61 +28,61 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "utils/IResourceHandler.hpp" -#include "utils/ILogger.hpp" -#include "utils/FileResourceHandlerCreator.hpp" -#include "utils/Utils.hpp" -#include "utils/String.hpp" +#include "core/IResourceHandler.hpp" +#include "core/ILogger.hpp" +#include "core/FileResourceHandlerCreator.hpp" +#include "core/Utils.hpp" +#include "core/String.hpp" #include "ParameterParsing.hpp" #include "SubsonicId.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; namespace { - std::optional subsonicStreamFormatToAvOutputFormat(std::string_view format) + std::optional subsonicStreamFormatToAvOutputFormat(std::string_view format) { - for (const auto& [str, avFormat] : std::initializer_list>{ - {"mp3", Av::Transcoding::OutputFormat::MP3}, - {"opus", Av::Transcoding::OutputFormat::OGG_OPUS}, - {"vorbis", Av::Transcoding::OutputFormat::OGG_VORBIS}, + for (const auto& [str, avFormat] : std::initializer_list>{ + {"mp3", av::transcoding::OutputFormat::MP3}, + {"opus", av::transcoding::OutputFormat::OGG_OPUS}, + {"vorbis", av::transcoding::OutputFormat::OGG_VORBIS}, }) { - if (StringUtils::stringCaseInsensitiveEqual(str, format)) + if (core::stringUtils::stringCaseInsensitiveEqual(str, format)) return avFormat; } return std::nullopt; } - Av::Transcoding::OutputFormat userTranscodeFormatToAvFormat(Database::TranscodingOutputFormat format) + av::transcoding::OutputFormat userTranscodeFormatToAvFormat(db::TranscodingOutputFormat format) { switch (format) { - case Database::TranscodingOutputFormat::MP3: return Av::Transcoding::OutputFormat::MP3; - case Database::TranscodingOutputFormat::OGG_OPUS: return Av::Transcoding::OutputFormat::OGG_OPUS; - case Database::TranscodingOutputFormat::MATROSKA_OPUS: return Av::Transcoding::OutputFormat::MATROSKA_OPUS; - case Database::TranscodingOutputFormat::OGG_VORBIS: return Av::Transcoding::OutputFormat::OGG_VORBIS; - case Database::TranscodingOutputFormat::WEBM_VORBIS: return Av::Transcoding::OutputFormat::WEBM_VORBIS; + case db::TranscodingOutputFormat::MP3: return av::transcoding::OutputFormat::MP3; + case db::TranscodingOutputFormat::OGG_OPUS: return av::transcoding::OutputFormat::OGG_OPUS; + case db::TranscodingOutputFormat::MATROSKA_OPUS: return av::transcoding::OutputFormat::MATROSKA_OPUS; + case db::TranscodingOutputFormat::OGG_VORBIS: return av::transcoding::OutputFormat::OGG_VORBIS; + case db::TranscodingOutputFormat::WEBM_VORBIS: return av::transcoding::OutputFormat::WEBM_VORBIS; } - return Av::Transcoding::OutputFormat::OGG_OPUS; + return av::transcoding::OutputFormat::OGG_OPUS; } - bool isCodecCompatibleWithOutputFormat(Av::DecodingCodec codec, Av::Transcoding::OutputFormat outputFormat) + bool isCodecCompatibleWithOutputFormat(av::DecodingCodec codec, av::transcoding::OutputFormat outputFormat) { switch (outputFormat) { - case Av::Transcoding::OutputFormat::MP3: - return codec == Av::DecodingCodec::MP3; + case av::transcoding::OutputFormat::MP3: + return codec == av::DecodingCodec::MP3; - case Av::Transcoding::OutputFormat::OGG_OPUS: - case Av::Transcoding::OutputFormat::MATROSKA_OPUS: - return codec == Av::DecodingCodec::OPUS; + case av::transcoding::OutputFormat::OGG_OPUS: + case av::transcoding::OutputFormat::MATROSKA_OPUS: + return codec == av::DecodingCodec::OPUS; - case Av::Transcoding::OutputFormat::OGG_VORBIS: - case Av::Transcoding::OutputFormat::WEBM_VORBIS: - return codec == Av::DecodingCodec::VORBIS; + case av::transcoding::OutputFormat::OGG_VORBIS: + case av::transcoding::OutputFormat::WEBM_VORBIS: + return codec == av::DecodingCodec::VORBIS; } return true; @@ -90,16 +90,16 @@ namespace API::Subsonic struct StreamParameters { - Av::Transcoding::InputParameters inputParameters; - std::optional outputParameters; + av::transcoding::InputParameters inputParameters; + std::optional outputParameters; bool estimateContentLength{}; }; - bool isOutputFormatCompatible(const std::filesystem::path& trackPath, Av::Transcoding::OutputFormat outputFormat) + bool isOutputFormatCompatible(const std::filesystem::path& trackPath, av::transcoding::OutputFormat outputFormat) { try { - const auto audioFile{ Av::parseAudioFile(trackPath) }; + const auto audioFile{ av::parseAudioFile(trackPath) }; const auto streamInfo{ audioFile->getBestStreamInfo() }; if (!streamInfo) @@ -107,7 +107,7 @@ namespace API::Subsonic return isCodecCompatibleWithOutputFormat(streamInfo->codec, outputFormat); } - catch (const Av::Exception& e) + catch (const av::Exception& e) { // TODO 404? throw RequestedDataNotFoundError{}; @@ -144,7 +144,7 @@ namespace API::Subsonic if (format == "raw") // raw => no transcoding return parameters; - std::optional requestedFormat{ subsonicStreamFormatToAvOutputFormat(format) }; + std::optional requestedFormat{ subsonicStreamFormatToAvOutputFormat(format) }; if (!requestedFormat) { if (user->getSubsonicEnableTranscodingByDefault()) @@ -176,7 +176,7 @@ namespace API::Subsonic if (!bitrate) bitrate = std::min(user->getSubsonicDefaultTranscodingOutputBitrate(), maxBitRate); - Av::Transcoding::OutputParameters& outputParameters{ parameters.outputParameters.emplace() }; + av::transcoding::OutputParameters& outputParameters{ parameters.outputParameters.emplace() }; outputParameters.stripMetadata = false; // We want clients to use metadata (offline use, replay gain, etc.) outputParameters.offset = std::chrono::seconds{ timeOffset }; @@ -195,7 +195,7 @@ namespace API::Subsonic if (!continuation) { // Mandatory params - Database::TrackId id{ getMandatoryParameterAs(context.parameters, "id") }; + db::TrackId id{ getMandatoryParameterAs(context.parameters, "id") }; std::filesystem::path trackPath; { @@ -208,7 +208,7 @@ namespace API::Subsonic trackPath = track->getPath(); } - resourceHandler = Av::createRawResourceHandler(trackPath); + resourceHandler = av::createRawResourceHandler(trackPath); } else { @@ -231,9 +231,9 @@ namespace API::Subsonic { StreamParameters streamParameters{ getStreamParameters(context) }; if (streamParameters.outputParameters) - resourceHandler = Av::Transcoding::createResourceHandler(streamParameters.inputParameters, *streamParameters.outputParameters, streamParameters.estimateContentLength); + resourceHandler = av::transcoding::createResourceHandler(streamParameters.inputParameters, *streamParameters.outputParameters, streamParameters.estimateContentLength); else - resourceHandler = Av::createRawResourceHandler(streamParameters.inputParameters.trackPath); + resourceHandler = av::createRawResourceHandler(streamParameters.inputParameters.trackPath); } else { @@ -244,7 +244,7 @@ namespace API::Subsonic if (continuation) continuation->setData(resourceHandler); } - catch (const Av::Exception& e) + catch (const av::Exception& e) { LMS_LOG(API_SUBSONIC, ERROR, "Caught Av exception: " << e.what()); } @@ -261,18 +261,18 @@ namespace API::Subsonic throw BadParameterGenericError{ "id" }; std::size_t size{ getParameterAs(context.parameters, "size").value_or(1024) }; - size = ::Utils::clamp(size, std::size_t{ 32 }, std::size_t{ 2048 }); + size = core::utils::clamp(size, std::size_t{ 32 }, std::size_t{ 2048 }); - std::shared_ptr cover; + std::shared_ptr cover; if (trackId) - cover = Service::get()->getFromTrack(*trackId, size); + cover = core::Service::get()->getFromTrack(*trackId, size); else if (releaseId) - cover = Service::get()->getFromRelease(*releaseId, size); + cover = core::Service::get()->getFromRelease(*releaseId, size); else if (artistId) - cover = Service::get()->getFromArtist(*artistId, size); + cover = core::Service::get()->getFromArtist(*artistId, size); if (!cover && context.enableDefaultCover && !artistId) - cover = Service::get()->getDefault(size); + cover = core::Service::get()->getDefault(size); if (!cover) { @@ -284,4 +284,4 @@ namespace API::Subsonic response.setMimeType(std::string{ cover->getMimeType() }); } -} // namespace API::Subsonic +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp index 4d2944a4..81b854a9 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp +++ b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp @@ -24,7 +24,7 @@ #include "RequestContext.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { void handleDownload(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response); void handleStream(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response); diff --git a/src/libs/subsonic/impl/entrypoints/Playlists.cpp b/src/libs/subsonic/impl/entrypoints/Playlists.cpp index 75d1488e..57f20d0e 100644 --- a/src/libs/subsonic/impl/entrypoints/Playlists.cpp +++ b/src/libs/subsonic/impl/entrypoints/Playlists.cpp @@ -28,9 +28,9 @@ #include "ParameterParsing.hpp" #include "SubsonicId.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; Response handleGetPlaylistsRequest(RequestContext& context) { diff --git a/src/libs/subsonic/impl/entrypoints/Playlists.hpp b/src/libs/subsonic/impl/entrypoints/Playlists.hpp index a9d44093..01c39d20 100644 --- a/src/libs/subsonic/impl/entrypoints/Playlists.hpp +++ b/src/libs/subsonic/impl/entrypoints/Playlists.hpp @@ -22,7 +22,7 @@ #include "RequestContext.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response handleGetPlaylistsRequest(RequestContext& context); Response handleGetPlaylistRequest(RequestContext& context); diff --git a/src/libs/subsonic/impl/entrypoints/Searching.cpp b/src/libs/subsonic/impl/entrypoints/Searching.cpp index e9f2d20a..ae876f6b 100644 --- a/src/libs/subsonic/impl/entrypoints/Searching.cpp +++ b/src/libs/subsonic/impl/entrypoints/Searching.cpp @@ -30,9 +30,9 @@ #include "ParameterParsing.hpp" #include "SubsonicId.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; namespace { @@ -47,9 +47,9 @@ namespace API::Subsonic // Symfonium adds extra "" if (context.clientInfo.name == "Symfonium") - query = StringUtils::stringTrim(query, "\""); + query = core::stringUtils::stringTrim(query, "\""); - std::vector keywords{ StringUtils::splitString(query, ' ') }; + std::vector keywords{ core::stringUtils::splitString(query, ' ') }; // Optional params std::size_t artistCount{ getParameterAs(context.parameters, "artistCount").value_or(20) }; diff --git a/src/libs/subsonic/impl/entrypoints/Searching.hpp b/src/libs/subsonic/impl/entrypoints/Searching.hpp index 1c8b00ac..db433a13 100644 --- a/src/libs/subsonic/impl/entrypoints/Searching.hpp +++ b/src/libs/subsonic/impl/entrypoints/Searching.hpp @@ -22,7 +22,7 @@ #include "RequestContext.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response handleSearch2Request(RequestContext& context); Response handleSearch3Request(RequestContext& context); diff --git a/src/libs/subsonic/impl/entrypoints/System.cpp b/src/libs/subsonic/impl/entrypoints/System.cpp index 2378034b..45ca2d91 100644 --- a/src/libs/subsonic/impl/entrypoints/System.cpp +++ b/src/libs/subsonic/impl/entrypoints/System.cpp @@ -1,6 +1,6 @@ #include "entrypoints/System.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response handlePingRequest(RequestContext& context) { diff --git a/src/libs/subsonic/impl/entrypoints/System.hpp b/src/libs/subsonic/impl/entrypoints/System.hpp index 81a394c8..83e48c33 100644 --- a/src/libs/subsonic/impl/entrypoints/System.hpp +++ b/src/libs/subsonic/impl/entrypoints/System.hpp @@ -22,7 +22,7 @@ #include "RequestContext.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response handlePingRequest(RequestContext& context); Response handleGetLicenseRequest(RequestContext& context); diff --git a/src/libs/subsonic/impl/entrypoints/UserManagement.cpp b/src/libs/subsonic/impl/entrypoints/UserManagement.cpp index 4547d9d0..fb079ce4 100644 --- a/src/libs/subsonic/impl/entrypoints/UserManagement.cpp +++ b/src/libs/subsonic/impl/entrypoints/UserManagement.cpp @@ -3,14 +3,14 @@ #include "database/Session.hpp" #include "database/User.hpp" #include "services/auth/IPasswordService.hpp" -#include "utils/Service.hpp" +#include "core/Service.hpp" #include "responses/User.hpp" #include "ParameterParsing.hpp" #include "Utils.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; namespace { void checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username) @@ -65,7 +65,7 @@ namespace API::Subsonic std::string password{ decodePasswordIfNeeded(getMandatoryParameterAs(context.parameters, "password")) }; // Just ignore all the other fields as we don't handle them - Database::UserId userId; + db::UserId userId; { auto transaction{ context.dbSession.createWriteTransaction() }; @@ -87,19 +87,19 @@ namespace API::Subsonic try { - Service::get()->setPassword(userId, password); + core::Service::get()->setPassword(userId, password); } - catch (const Auth::PasswordMustMatchLoginNameException&) + catch (const auth::PasswordMustMatchLoginNameException&) { removeCreatedUser(); throw PasswordMustMatchLoginNameGenericError{}; } - catch (const Auth::PasswordTooWeakException&) + catch (const auth::PasswordTooWeakException&) { removeCreatedUser(); throw PasswordTooWeakGenericError{}; } - catch (const Auth::Exception& exception) + catch (const auth::Exception& exception) { removeCreatedUser(); throw UserNotAuthorizedError{}; @@ -145,21 +145,21 @@ namespace API::Subsonic if (password) { - Utils::checkSetPasswordImplemented(); + utils::checkSetPasswordImplemented(); try { - Service<::Auth::IPasswordService>()->setPassword(userId, decodePasswordIfNeeded(*password)); + core::Service()->setPassword(userId, decodePasswordIfNeeded(*password)); } - catch (const Auth::PasswordMustMatchLoginNameException&) + catch (const auth::PasswordMustMatchLoginNameException&) { throw PasswordMustMatchLoginNameGenericError{}; } - catch (const Auth::PasswordTooWeakException&) + catch (const auth::PasswordTooWeakException&) { throw PasswordTooWeakGenericError{}; } - catch (const Auth::Exception&) + catch (const auth::Exception&) { throw UserNotAuthorizedError{}; } @@ -175,7 +175,7 @@ namespace API::Subsonic try { - Database::UserId userId; + db::UserId userId; { auto transaction{ context.dbSession.createReadTransaction() }; @@ -188,17 +188,17 @@ namespace API::Subsonic userId = user->getId(); } - Service::get()->setPassword(userId, password); + core::Service::get()->setPassword(userId, password); } - catch (const Auth::PasswordMustMatchLoginNameException&) + catch (const auth::PasswordMustMatchLoginNameException&) { throw PasswordMustMatchLoginNameGenericError{}; } - catch (const Auth::PasswordTooWeakException&) + catch (const auth::PasswordTooWeakException&) { throw PasswordTooWeakGenericError{}; } - catch (const Auth::Exception& authException) + catch (const auth::Exception& authException) { throw UserNotAuthorizedError{}; } diff --git a/src/libs/subsonic/impl/entrypoints/UserManagement.hpp b/src/libs/subsonic/impl/entrypoints/UserManagement.hpp index e4fac6f4..e44203d6 100644 --- a/src/libs/subsonic/impl/entrypoints/UserManagement.hpp +++ b/src/libs/subsonic/impl/entrypoints/UserManagement.hpp @@ -22,7 +22,7 @@ #include "RequestContext.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response handleGetUserRequest(RequestContext& context); Response handleGetUsersRequest(RequestContext& context); diff --git a/src/libs/subsonic/impl/responses/Album.cpp b/src/libs/subsonic/impl/responses/Album.cpp index fc97fb47..8e16b33a 100644 --- a/src/libs/subsonic/impl/responses/Album.cpp +++ b/src/libs/subsonic/impl/responses/Album.cpp @@ -25,9 +25,9 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "utils/ITraceLogger.hpp" -#include "utils/Service.hpp" -#include "utils/String.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "responses/Artist.hpp" #include "responses/DiscTitle.hpp" @@ -35,9 +35,9 @@ #include "responses/ItemGenre.hpp" #include "SubsonicId.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; Response::Node createAlbumNode(RequestContext& context, const Release::pointer& release, const User::pointer& user, bool id3) { @@ -59,7 +59,7 @@ namespace API::Subsonic albumNode.setAttribute("isDir", true); } - albumNode.setAttribute("created", StringUtils::toISO8601String(release->getLastWritten())); + albumNode.setAttribute("created", core::stringUtils::toISO8601String(release->getLastWritten())); albumNode.setAttribute("id", idToString(release->getId())); albumNode.setAttribute("coverArt", idToString(release->getId())); if (const auto year{ release->getYear() }) @@ -78,7 +78,7 @@ namespace API::Subsonic if (!release->getArtistDisplayName().empty()) albumNode.setAttribute("artist", release->getArtistDisplayName()); else - albumNode.setAttribute("artist", Utils::joinArtistNames(artists)); + albumNode.setAttribute("artist", utils::joinArtistNames(artists)); if (artists.size() == 1) { @@ -91,7 +91,7 @@ namespace API::Subsonic } } - albumNode.setAttribute("playCount", Service::get()->getCount(user->getId(), release->getId())); + albumNode.setAttribute("playCount", core::Service::get()->getCount(user->getId(), release->getId())); // Report the first GENRE for this track const ClusterType::pointer genreClusterType{ ClusterType::find(context.dbSession, "GENRE") }; @@ -102,8 +102,8 @@ namespace API::Subsonic albumNode.setAttribute("genre", clusters.front().front()->getName()); } - if (const Wt::WDateTime dateTime{ Service::get()->getStarredDateTime(user->getId(), release->getId()) }; dateTime.isValid()) - albumNode.setAttribute("starred", StringUtils::toISO8601String(dateTime)); + if (const Wt::WDateTime dateTime{ core::Service::get()->getStarredDateTime(user->getId(), release->getId()) }; dateTime.isValid()) + albumNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime)); if (!context.enableOpenSubsonic) return albumNode; @@ -115,12 +115,12 @@ namespace API::Subsonic albumNode.setAttribute("mediaType", "album"); { - const Wt::WDateTime dateTime{ Service::get()->getLastListenDateTime(user->getId(), release->getId()) }; - albumNode.setAttribute("played", dateTime.isValid() ? StringUtils::toISO8601String(dateTime) : std::string{ "" }); + const Wt::WDateTime dateTime{ core::Service::get()->getLastListenDateTime(user->getId(), release->getId()) }; + albumNode.setAttribute("played", dateTime.isValid() ? core::stringUtils::toISO8601String(dateTime) : std::string{ "" }); } { - std::optional mbid{ release->getMBID() }; + std::optional mbid{ release->getMBID() }; albumNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); } @@ -162,7 +162,7 @@ namespace API::Subsonic albumNode.createEmptyArrayValue("releaseTypes"); for (std::string_view releaseType : release->getReleaseTypeNames()) { - if (StringUtils::stringCaseInsensitiveEqual(releaseType, "compilation")) + if (core::stringUtils::stringCaseInsensitiveEqual(releaseType, "compilation")) isCompilation = true; albumNode.addArrayValue("releaseTypes", releaseType); diff --git a/src/libs/subsonic/impl/responses/Album.hpp b/src/libs/subsonic/impl/responses/Album.hpp index 51d92184..b6780b71 100644 --- a/src/libs/subsonic/impl/responses/Album.hpp +++ b/src/libs/subsonic/impl/responses/Album.hpp @@ -22,14 +22,14 @@ #include "database/Object.hpp" #include "SubsonicResponse.hpp" -namespace Database +namespace lms::db { class Release; class User; class Session; } -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createAlbumNode(RequestContext& context, const Database::ObjectPtr& release, const Database::ObjectPtr& user, bool id3); + Response::Node createAlbumNode(RequestContext& context, const db::ObjectPtr& release, const db::ObjectPtr& user, bool id3); } \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Artist.cpp b/src/libs/subsonic/impl/responses/Artist.cpp index 68cbfea3..bbc40f31 100644 --- a/src/libs/subsonic/impl/responses/Artist.cpp +++ b/src/libs/subsonic/impl/responses/Artist.cpp @@ -24,17 +24,17 @@ #include "database/TrackArtistLink.hpp" #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" -#include "utils/ITraceLogger.hpp" -#include "utils/Service.hpp" -#include "utils/String.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "SubsonicId.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; - namespace Utils + namespace utils { std::string joinArtistNames(const std::vector& artists) { @@ -50,7 +50,7 @@ namespace API::Subsonic return artist->getName(); }); - return StringUtils::joinStrings(names, ", "); + return core::stringUtils::joinStrings(names, ", "); } std::string_view toString(TrackArtistLinkType type) @@ -90,8 +90,8 @@ namespace API::Subsonic artistNode.setAttribute("albumCount", count); } - if (const Wt::WDateTime dateTime{ Service::get()->getStarredDateTime(user->getId(), artist->getId()) }; dateTime.isValid()) - artistNode.setAttribute("starred", StringUtils::toISO8601String(dateTime)); + if (const Wt::WDateTime dateTime{ core::Service::get()->getStarredDateTime(user->getId(), artist->getId()) }; dateTime.isValid()) + artistNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime)); // OpenSubsonic specific fields (must always be set) if (context.enableOpenSubsonic) @@ -100,7 +100,7 @@ namespace API::Subsonic artistNode.setAttribute("mediaType", "artist"); { - std::optional mbid{ artist->getMBID() }; + std::optional mbid{ artist->getMBID() }; artistNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); } @@ -110,7 +110,7 @@ namespace API::Subsonic Response::Node roles; artistNode.createEmptyArrayValue("roles"); for (const TrackArtistLinkType linkType : TrackArtistLink::findUsedTypes(context.dbSession, artist->getId())) - artistNode.addArrayValue("roles", Utils::toString(linkType)); + artistNode.addArrayValue("roles", utils::toString(linkType)); } return artistNode; diff --git a/src/libs/subsonic/impl/responses/Artist.hpp b/src/libs/subsonic/impl/responses/Artist.hpp index dd33a208..fe0e2f4b 100644 --- a/src/libs/subsonic/impl/responses/Artist.hpp +++ b/src/libs/subsonic/impl/responses/Artist.hpp @@ -25,20 +25,20 @@ #include "database/Types.hpp" #include "SubsonicResponse.hpp" -namespace Database +namespace lms::db { class Artist; class User; class Session; } -namespace API::Subsonic +namespace lms::api::subsonic { - namespace Utils + namespace utils { - std::string joinArtistNames(const std::vector>& artists); - std::string_view toString(Database::TrackArtistLinkType type); + std::string joinArtistNames(const std::vector>& artists); + std::string_view toString(db::TrackArtistLinkType type); } - Response::Node createArtistNode(RequestContext& context, const Database::ObjectPtr& artist, const Database::ObjectPtr& user, bool id3); - Response::Node createArtistNode(const Database::ObjectPtr& artist); // only minimal info + Response::Node createArtistNode(RequestContext& context, const db::ObjectPtr& artist, const db::ObjectPtr& user, bool id3); + Response::Node createArtistNode(const db::ObjectPtr& artist); // only minimal info } \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Bookmark.cpp b/src/libs/subsonic/impl/responses/Bookmark.cpp index 22394763..206b21e5 100644 --- a/src/libs/subsonic/impl/responses/Bookmark.cpp +++ b/src/libs/subsonic/impl/responses/Bookmark.cpp @@ -22,11 +22,11 @@ #include "database/TrackBookmark.hpp" #include "database/User.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { static const std::string_view reportedDummyDate{ "2000-01-01T00:00:00" }; - Response::Node createBookmarkNode(const Database::ObjectPtr& trackBookmark) + Response::Node createBookmarkNode(const db::ObjectPtr& trackBookmark) { Response::Node trackBookmarkNode; diff --git a/src/libs/subsonic/impl/responses/Bookmark.hpp b/src/libs/subsonic/impl/responses/Bookmark.hpp index 89f8ad63..fc8073ad 100644 --- a/src/libs/subsonic/impl/responses/Bookmark.hpp +++ b/src/libs/subsonic/impl/responses/Bookmark.hpp @@ -22,12 +22,12 @@ #include "database/Object.hpp" #include "SubsonicResponse.hpp" -namespace Database +namespace lms::db { class TrackBookmark; } -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createBookmarkNode(const Database::ObjectPtr& bookmark); + Response::Node createBookmarkNode(const db::ObjectPtr& bookmark); } \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Contributor.cpp b/src/libs/subsonic/impl/responses/Contributor.cpp index c3072cd6..576e99b2 100644 --- a/src/libs/subsonic/impl/responses/Contributor.cpp +++ b/src/libs/subsonic/impl/responses/Contributor.cpp @@ -24,13 +24,13 @@ #include "SubsonicResponse.hpp" #include "responses/Artist.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createContributorNode(const Database::ObjectPtr& trackArtistLink) + Response::Node createContributorNode(const db::ObjectPtr& trackArtistLink) { Response::Node contributorNode; - contributorNode.setAttribute("role", Utils::toString(trackArtistLink->getType())); + contributorNode.setAttribute("role", utils::toString(trackArtistLink->getType())); if (!trackArtistLink->getSubType().empty()) contributorNode.setAttribute("subRole", trackArtistLink->getSubType()); contributorNode.addChild("artist", createArtistNode(trackArtistLink->getArtist())); diff --git a/src/libs/subsonic/impl/responses/Contributor.hpp b/src/libs/subsonic/impl/responses/Contributor.hpp index 7ceac94a..14b54dde 100644 --- a/src/libs/subsonic/impl/responses/Contributor.hpp +++ b/src/libs/subsonic/impl/responses/Contributor.hpp @@ -22,12 +22,12 @@ #include "database/Object.hpp" #include "SubsonicResponse.hpp" -namespace Database +namespace lms::db { class TrackArtistLink; } -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createContributorNode(const Database::ObjectPtr& trackArtistLink); + Response::Node createContributorNode(const db::ObjectPtr& trackArtistLink); } \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/DiscTitle.cpp b/src/libs/subsonic/impl/responses/DiscTitle.cpp index 53ec3215..1ba79f2f 100644 --- a/src/libs/subsonic/impl/responses/DiscTitle.cpp +++ b/src/libs/subsonic/impl/responses/DiscTitle.cpp @@ -19,9 +19,9 @@ #include "responses/DiscTitle.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createDiscTitle(const Database::DiscInfo& discInfo) + Response::Node createDiscTitle(const db::DiscInfo& discInfo) { Response::Node discTitleNode; diff --git a/src/libs/subsonic/impl/responses/DiscTitle.hpp b/src/libs/subsonic/impl/responses/DiscTitle.hpp index 748ed4ba..7881ee38 100644 --- a/src/libs/subsonic/impl/responses/DiscTitle.hpp +++ b/src/libs/subsonic/impl/responses/DiscTitle.hpp @@ -22,7 +22,7 @@ #include "database/Types.hpp" #include "SubsonicResponse.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createDiscTitle(const Database::DiscInfo& discInfo); + Response::Node createDiscTitle(const db::DiscInfo& discInfo); } \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Genre.cpp b/src/libs/subsonic/impl/responses/Genre.cpp index d9bdcedc..9f761032 100644 --- a/src/libs/subsonic/impl/responses/Genre.cpp +++ b/src/libs/subsonic/impl/responses/Genre.cpp @@ -21,9 +21,9 @@ #include "database/Cluster.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createGenreNode(const Database::Cluster::pointer& cluster) + Response::Node createGenreNode(const db::Cluster::pointer& cluster) { Response::Node clusterNode; diff --git a/src/libs/subsonic/impl/responses/Genre.hpp b/src/libs/subsonic/impl/responses/Genre.hpp index 44dece5a..15890c1e 100644 --- a/src/libs/subsonic/impl/responses/Genre.hpp +++ b/src/libs/subsonic/impl/responses/Genre.hpp @@ -22,12 +22,12 @@ #include "database/Object.hpp" #include "SubsonicResponse.hpp" -namespace Database +namespace lms::db { class Cluster; } -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createGenreNode(const Database::ObjectPtr& cluster); + Response::Node createGenreNode(const db::ObjectPtr& cluster); } diff --git a/src/libs/subsonic/impl/responses/ItemDate.cpp b/src/libs/subsonic/impl/responses/ItemDate.cpp index b8f41e7b..1f6c7592 100644 --- a/src/libs/subsonic/impl/responses/ItemDate.cpp +++ b/src/libs/subsonic/impl/responses/ItemDate.cpp @@ -21,7 +21,7 @@ #include "database/Cluster.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response::Node createItemDateNode(const Wt::WDate& date, std::optional year) { diff --git a/src/libs/subsonic/impl/responses/ItemDate.hpp b/src/libs/subsonic/impl/responses/ItemDate.hpp index e9af4a33..6a9a4320 100644 --- a/src/libs/subsonic/impl/responses/ItemDate.hpp +++ b/src/libs/subsonic/impl/responses/ItemDate.hpp @@ -22,7 +22,7 @@ #include #include "SubsonicResponse.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response::Node createItemDateNode(const Wt::WDate& date, std::optional year); } diff --git a/src/libs/subsonic/impl/responses/ItemGenre.cpp b/src/libs/subsonic/impl/responses/ItemGenre.cpp index 7b87a33e..c746af27 100644 --- a/src/libs/subsonic/impl/responses/ItemGenre.cpp +++ b/src/libs/subsonic/impl/responses/ItemGenre.cpp @@ -21,7 +21,7 @@ #include "database/Cluster.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { Response::Node createItemGenreNode(std::string_view name) { diff --git a/src/libs/subsonic/impl/responses/ItemGenre.hpp b/src/libs/subsonic/impl/responses/ItemGenre.hpp index c15a4674..0e7038ff 100644 --- a/src/libs/subsonic/impl/responses/ItemGenre.hpp +++ b/src/libs/subsonic/impl/responses/ItemGenre.hpp @@ -22,12 +22,12 @@ #include #include "SubsonicResponse.hpp" -namespace Database +namespace lms::db { class Cluster; } -namespace API::Subsonic +namespace lms::api::subsonic { Response::Node createItemGenreNode(std::string_view name); } diff --git a/src/libs/subsonic/impl/responses/Playlist.cpp b/src/libs/subsonic/impl/responses/Playlist.cpp index db8cd626..80583363 100644 --- a/src/libs/subsonic/impl/responses/Playlist.cpp +++ b/src/libs/subsonic/impl/responses/Playlist.cpp @@ -24,9 +24,9 @@ #include "database/User.hpp" #include "SubsonicId.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; static const std::string_view reportedDummyDate{ "2000-01-01T00:00:00" }; diff --git a/src/libs/subsonic/impl/responses/Playlist.hpp b/src/libs/subsonic/impl/responses/Playlist.hpp index 22d29767..0d62bc0e 100644 --- a/src/libs/subsonic/impl/responses/Playlist.hpp +++ b/src/libs/subsonic/impl/responses/Playlist.hpp @@ -22,13 +22,13 @@ #include "database/Object.hpp" #include "SubsonicResponse.hpp" -namespace Database +namespace lms::db { class TrackList; class Session; } -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createPlaylistNode(const Database::ObjectPtr& tracklist, Database::Session& session); + Response::Node createPlaylistNode(const db::ObjectPtr& tracklist, db::Session& session); } \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/ReplayGain.cpp b/src/libs/subsonic/impl/responses/ReplayGain.cpp index 21324495..2fb90305 100644 --- a/src/libs/subsonic/impl/responses/ReplayGain.cpp +++ b/src/libs/subsonic/impl/responses/ReplayGain.cpp @@ -21,9 +21,9 @@ #include "database/Track.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createReplayGainNode(const Database::ObjectPtr& track) + Response::Node createReplayGainNode(const db::ObjectPtr& track) { Response::Node replayGainNode; diff --git a/src/libs/subsonic/impl/responses/ReplayGain.hpp b/src/libs/subsonic/impl/responses/ReplayGain.hpp index 7a3af898..a19f00ff 100644 --- a/src/libs/subsonic/impl/responses/ReplayGain.hpp +++ b/src/libs/subsonic/impl/responses/ReplayGain.hpp @@ -22,12 +22,12 @@ #include "database/Object.hpp" #include "SubsonicResponse.hpp" -namespace Database +namespace lms::db { class Track; } -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createReplayGainNode(const Database::ObjectPtr& track); + Response::Node createReplayGainNode(const db::ObjectPtr& track); } \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index 807d9130..68c8bbc2 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -30,9 +30,9 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "utils/ITraceLogger.hpp" -#include "utils/Service.hpp" -#include "utils/String.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "responses/Artist.hpp" #include "responses/Contributor.hpp" #include "responses/ItemGenre.hpp" @@ -40,9 +40,9 @@ #include "SubsonicId.hpp" #include "Utils.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; namespace { @@ -76,9 +76,9 @@ namespace API::Subsonic if (artists.size() > 1) path = "Various Artists/"; else if (artists.size() == 1) - path = Utils::makeNameFilesystemCompatible(artists.front()->getName()) + "/"; + path = utils::makeNameFilesystemCompatible(artists.front()->getName()) + "/"; - path += Utils::makeNameFilesystemCompatible(track->getRelease()->getName()) + "/"; + path += utils::makeNameFilesystemCompatible(track->getRelease()->getName()) + "/"; } if (track->getDiscNumber()) @@ -86,7 +86,7 @@ namespace API::Subsonic if (track->getTrackNumber()) path += std::to_string(*track->getTrackNumber()) + "-"; - path += Utils::makeNameFilesystemCompatible(track->getName()); + path += utils::makeNameFilesystemCompatible(track->getName()); if (track->getPath().has_extension()) path += track->getPath().extension(); @@ -110,7 +110,7 @@ namespace API::Subsonic trackResponse.setAttribute("discNumber", *track->getDiscNumber()); if (track->getYear()) trackResponse.setAttribute("year", *track->getYear()); - trackResponse.setAttribute("playCount", Service::get()->getCount(user->getId(), track->getId())); + trackResponse.setAttribute("playCount", core::Service::get()->getCount(user->getId(), track->getId())); trackResponse.setAttribute("path", getTrackPath(track)); { // TODO, store this in DB @@ -129,7 +129,7 @@ namespace API::Subsonic { const std::string fileSuffix{ formatToSuffix(user->getSubsonicDefaultTranscodingOutputFormat()) }; trackResponse.setAttribute("transcodedSuffix", fileSuffix); - trackResponse.setAttribute("transcodedContentType", Av::getMimeType(std::filesystem::path{ "." + fileSuffix })); + trackResponse.setAttribute("transcodedContentType", av::getMimeType(std::filesystem::path{ "." + fileSuffix })); } trackResponse.setAttribute("coverArt", idToString(track->getId())); @@ -140,7 +140,7 @@ namespace API::Subsonic if (!track->getArtistDisplayName().empty()) trackResponse.setAttribute("artist", track->getArtistDisplayName()); else - trackResponse.setAttribute("artist", Utils::joinArtistNames(artists)); + trackResponse.setAttribute("artist", utils::joinArtistNames(artists)); if (artists.size() == 1) trackResponse.setAttribute("artistId", idToString(artists.front()->getId())); @@ -157,11 +157,11 @@ namespace API::Subsonic trackResponse.setAttribute("duration", std::chrono::duration_cast(track->getDuration()).count()); trackResponse.setAttribute("bitRate", (track->getBitrate() / 1000)); trackResponse.setAttribute("type", "music"); - trackResponse.setAttribute("created", StringUtils::toISO8601String(track->getLastWritten())); - trackResponse.setAttribute("contentType", Av::getMimeType(track->getPath().extension())); + trackResponse.setAttribute("created", core::stringUtils::toISO8601String(track->getLastWritten())); + trackResponse.setAttribute("contentType", av::getMimeType(track->getPath().extension())); - if (const Wt::WDateTime dateTime{ Service::get()->getStarredDateTime(user->getId(), track->getId()) }; dateTime.isValid()) - trackResponse.setAttribute("starred", StringUtils::toISO8601String(dateTime)); + if (const Wt::WDateTime dateTime{ core::Service::get()->getStarredDateTime(user->getId(), track->getId()) }; dateTime.isValid()) + trackResponse.setAttribute("starred", core::stringUtils::toISO8601String(dateTime)); // Report the first GENRE for this track std::vector genres; @@ -182,12 +182,12 @@ namespace API::Subsonic trackResponse.setAttribute("mediaType", "song"); { - const Wt::WDateTime dateTime{ Service::get()->getLastListenDateTime(user->getId(), track->getId()) }; - trackResponse.setAttribute("played", dateTime.isValid() ? StringUtils::toISO8601String(dateTime) : ""); + const Wt::WDateTime dateTime{ core::Service::get()->getLastListenDateTime(user->getId(), track->getId()) }; + trackResponse.setAttribute("played", dateTime.isValid() ? core::stringUtils::toISO8601String(dateTime) : ""); } { - std::optional mbid{ track->getRecordingMBID() }; + std::optional mbid{ track->getRecordingMBID() }; trackResponse.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); } diff --git a/src/libs/subsonic/impl/responses/Song.hpp b/src/libs/subsonic/impl/responses/Song.hpp index dfaf8af3..4b5101b0 100644 --- a/src/libs/subsonic/impl/responses/Song.hpp +++ b/src/libs/subsonic/impl/responses/Song.hpp @@ -22,14 +22,14 @@ #include "database/Object.hpp" #include "SubsonicResponse.hpp" -namespace Database +namespace lms::db { class Track; class User; class Session; } -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createSongNode(RequestContext& context, const Database::ObjectPtr& track, const Database::ObjectPtr& user); + Response::Node createSongNode(RequestContext& context, const db::ObjectPtr& track, const db::ObjectPtr& user); } \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/User.cpp b/src/libs/subsonic/impl/responses/User.cpp index 91453fe2..596f7c20 100644 --- a/src/libs/subsonic/impl/responses/User.cpp +++ b/src/libs/subsonic/impl/responses/User.cpp @@ -21,9 +21,9 @@ #include "database/User.hpp" -namespace API::Subsonic +namespace lms::api::subsonic { - using namespace Database; + using namespace db; Response::Node createUserNode(const User::pointer& user) { diff --git a/src/libs/subsonic/impl/responses/User.hpp b/src/libs/subsonic/impl/responses/User.hpp index 4241d2aa..b6f23ed2 100644 --- a/src/libs/subsonic/impl/responses/User.hpp +++ b/src/libs/subsonic/impl/responses/User.hpp @@ -22,12 +22,12 @@ #include "database/Object.hpp" #include "SubsonicResponse.hpp" -namespace Database +namespace lms::db { class User; } -namespace API::Subsonic +namespace lms::api::subsonic { - Response::Node createUserNode(const Database::ObjectPtr& user); + Response::Node createUserNode(const db::ObjectPtr& user); } \ No newline at end of file diff --git a/src/libs/subsonic/include/subsonic/SubsonicResource.hpp b/src/libs/subsonic/include/subsonic/SubsonicResource.hpp index 2354f787..c2e89074 100644 --- a/src/libs/subsonic/include/subsonic/SubsonicResource.hpp +++ b/src/libs/subsonic/include/subsonic/SubsonicResource.hpp @@ -22,12 +22,12 @@ #include -namespace Database +namespace lms::db { class Db; } -namespace API::Subsonic +namespace lms::api::subsonic { - std::unique_ptr createSubsonicResource(Database::Db& db); + std::unique_ptr createSubsonicResource(db::Db& db); } // namespace diff --git a/src/libs/utils/bench/CMakeLists.txt b/src/libs/utils/bench/CMakeLists.txt deleted file mode 100644 index f8326be4..00000000 --- a/src/libs/utils/bench/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ - -add_executable(bench-utils - TraceLoggerBench.cpp - ) - -target_link_libraries(bench-utils PRIVATE - lmsutils - benchmark - ) diff --git a/src/libs/utils/bench/TraceLoggerBench.cpp b/src/libs/utils/bench/TraceLoggerBench.cpp deleted file mode 100644 index 68cf20f3..00000000 --- a/src/libs/utils/bench/TraceLoggerBench.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2024 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 . - */ - -#include -#include -#include - -#include "utils/ILogger.hpp" -#include "utils/ITraceLogger.hpp" -#include "utils/StreamLogger.hpp" - - -// The trace logger is meant to built/destroyed once -Service logger{ std::make_unique(std::cout, StreamLogger::allSeverities) }; -Service traceLogger{ tracing::createTraceLogger(::tracing::Level::Overview) }; - -static void BM_TraceLogger_Overview(benchmark::State& state) -{ - for (auto _ : state) - { - LMS_SCOPED_TRACE_OVERVIEW("Cat", "Test"); - } -} - -static void BM_TraceLogger_Detailed(benchmark::State& state) -{ - for (auto _ : state) - { - // Should do nothing - LMS_SCOPED_TRACE_DETAILED("Cat", "Test"); - } -} - -BENCHMARK(BM_TraceLogger_Overview)->Threads(1)->Threads(std::thread::hardware_concurrency()); -BENCHMARK(BM_TraceLogger_Detailed)->Threads(1)->Threads(std::thread::hardware_concurrency()); - -BENCHMARK_MAIN(); \ No newline at end of file diff --git a/src/libs/utils/impl/ArchiveZipper.cpp b/src/libs/utils/impl/ArchiveZipper.cpp deleted file mode 100644 index f90b1e58..00000000 --- a/src/libs/utils/impl/ArchiveZipper.cpp +++ /dev/null @@ -1,309 +0,0 @@ -/* - * Copyright (C) 2020 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 . - */ - -#include "ArchiveZipper.hpp" - -#include -#include -#include // strerror -#include -#include -#include - -#include "utils/ILogger.hpp" - -namespace Zip -{ - std::unique_ptr - createArchiveZipper(const EntryContainer& entries) - { - return std::make_unique(entries); - } - - class FileException : public Exception - { - public: - FileException(const std::filesystem::path& p, std::string_view message) - : Exception {"File '" + p.string() + "': " + std::string {message}} - {} - - FileException(const std::filesystem::path& p, std::string_view message, int err) - : Exception {"File '" + p.string() + "': " + std::string {message} + ": " + ::strerror(err)} - {} - }; - - class ArchiveException : public Exception - { - public: - ArchiveException(struct ::archive* arch) - : Exception {getError(arch)} - {} - - static std::string_view getError(struct ::archive* arch) - { - const char* str {archive_error_string(arch)}; - if (!str) - { - static std::string unknownError {"Unknown archive error"}; - return unknownError; - } - - return str; - } - }; - - void - ArchiveZipper::ArchiveDeleter::operator()(struct ::archive* arch) - { - const int res {::archive_write_free(arch)}; - if (res != ARCHIVE_OK) - LMS_LOG(UTILS, ERROR, "Failure while freeing archive control struct: " << std::string {::strerror(res)}); - } - - void - ArchiveZipper::ArchiveEntryDeleter::operator()(struct ::archive_entry* archEntry) - { - ::archive_entry_free(archEntry); - } - - ArchiveZipper::ArchiveZipper(const EntryContainer& entries) - : _entries {entries} - , _readBuffer(_readBufferSize, {}) - , _currentEntry {std::cbegin(_entries)} - { - _archive = ArchivePtr {::archive_write_new()}; - if (!_archive) - throw Exception {"Cannot create archive control struct"}; - - auto archiveOpen {[](struct ::archive*, void*) - { - return ARCHIVE_OK; - }}; - - auto archiveWrite {[](struct ::archive*, void* clientData, const void* buff, ::size_t n) -> la_ssize_t - { - ArchiveZipper* zipper {static_cast(clientData)}; - return zipper->onWriteCallback(static_cast(buff), n); - }}; - - auto archiveClose {[](struct ::archive*, void*) - { - return ARCHIVE_OK; - }}; - - if (::archive_write_set_bytes_per_block(_archive.get(), _writeBlockSize) != ARCHIVE_OK) - throw ArchiveException {_archive.get()}; - - // 1 => no padding for last block - if (::archive_write_set_bytes_in_last_block(_archive.get(), 1) != ARCHIVE_OK) - throw ArchiveException {_archive.get()}; - - if (::archive_write_set_format_zip(_archive.get()) != ARCHIVE_OK) - throw ArchiveException {_archive.get()}; - - if (::archive_write_set_option(_archive.get(), "zip", "compression", "deflate") != ARCHIVE_OK) - throw ArchiveException {_archive.get()}; - - int res {::archive_write_open(_archive.get(), this, archiveOpen, archiveWrite, archiveClose)}; - if (res != ARCHIVE_OK) - throw ArchiveException {_archive.get()}; - } - - std::uint64_t - ArchiveZipper::writeSome(std::ostream& output) - { - assert(!_currentOutputStream); - - _currentOutputStream = &output; - _bytesWrittenInCurrentOutputStream = 0; - - while (_bytesWrittenInCurrentOutputStream == 0) - { - if (!_currentArchiveEntry) - { - if (_currentEntry == std::cend(_entries)) - { - if (::archive_write_close(_archive.get()) != ARCHIVE_OK) - throw ArchiveException {_archive.get()}; - - _archive.reset(); - break; - } - - _currentArchiveEntry = createArchiveEntry(*_currentEntry); - _currentEntryOffset = 0; - if (::archive_write_header(_archive.get(), _currentArchiveEntry.get()) != ARCHIVE_OK) - throw ArchiveException {_archive.get()}; - } - - if (writeSomeCurrentFileData()) - { - // entry complete - if (::archive_write_finish_entry(_archive.get()) != ARCHIVE_OK) - throw ArchiveException {_archive.get()}; - - _currentArchiveEntry.reset(); - _currentEntry++; - } - } - - _currentOutputStream = nullptr; - return _bytesWrittenInCurrentOutputStream; - } - - bool - ArchiveZipper::isComplete() const - { - return !_archive; - } - - void - ArchiveZipper::abort() - { - LMS_LOG(UTILS, DEBUG, "Aborting zip creation"); - if (_archive) - { - ::archive_write_fail(_archive.get()); - _archive.reset(); - } - } - - static - ::mode_t - permsToMode(const std::filesystem::perms p) - { - using std::filesystem::perms; - ::mode_t mode{}; - - auto testPerm {[](perms p, perms permToTest) - { - return (p & permToTest) == permToTest; - }}; - - if (testPerm(p, perms::owner_read)) - mode |= S_IRUSR; - if (testPerm(p, perms::owner_write)) - mode |= S_IWUSR; - if (testPerm(p, perms::owner_exec)) - mode |= S_IXUSR; - if (testPerm(p, perms::group_read)) - mode |= S_IRGRP; - if (testPerm(p, perms::group_write)) - mode |= S_IWGRP; - if (testPerm(p, perms::group_exec)) - mode |= S_IXGRP; - if (testPerm(p, perms::others_read)) - mode |= S_IROTH; - if (testPerm(p, perms::others_write)) - mode |= S_IWOTH; - if (testPerm(p, perms::others_exec)) - mode |= S_IXOTH; - - return mode; - } - - ArchiveZipper::ArchiveEntryPtr - ArchiveZipper::createArchiveEntry(const Entry& entry) - { - try - { - if (!std::filesystem::is_regular_file(entry.filePath)) - throw FileException {entry.filePath, "not a regular file"}; - - ArchiveEntryPtr archiveEntry {archive_entry_new()}; - if (!archiveEntry) - throw Exception {"Cannot create archive entry control struct"}; - - archive_entry_set_pathname(archiveEntry.get(), entry.fileName.c_str()); - archive_entry_set_size(archiveEntry.get(), std::filesystem::file_size(entry.filePath)); - archive_entry_set_mode(archiveEntry.get(), permsToMode(std::filesystem::status(entry.filePath).permissions())); - archive_entry_set_filetype(archiveEntry.get(), AE_IFREG); - - return archiveEntry; - } - catch (const std::filesystem::filesystem_error& error) - { - throw FileException {entry.filePath, error.what()}; - } - } - - bool - ArchiveZipper::writeSomeCurrentFileData() - { - assert(_currentEntry != std::cend(_entries)); - - std::ifstream ifs {_currentEntry->filePath.c_str(), std::ios_base::binary}; - if (!ifs) - throw FileException {_currentEntry->filePath, "cannot open file", errno}; - - ifs.seekg(0, std::ios::end); - const std::uint64_t fileSize {static_cast(ifs.tellg())}; - ifs.seekg(0, std::ios::beg); - - // TODO: store file size? - if (fileSize < _currentEntryOffset) - throw FileException {_currentEntry->filePath, "size changed?"}; - - const std::uint64_t bytesToRead {std::min(fileSize - _currentEntryOffset, static_cast(_readBufferSize))}; - - // read from file - if (!ifs.seekg(_currentEntryOffset, std::ios::beg)) - throw FileException {_currentEntry->filePath, "seek failed", errno}; - - if (!ifs.read(reinterpret_cast(&_readBuffer[0]), bytesToRead)) - throw FileException {_currentEntry->filePath, "read failed", errno}; - - const std::uint64_t actualBytesRead {static_cast(ifs.gcount())}; - - // write to archive - { - std::uint64_t remainingBytesToWrite {actualBytesRead}; - while (remainingBytesToWrite > 0) - { - const auto writtenBytes {archive_write_data(_archive.get(), &_readBuffer[actualBytesRead - remainingBytesToWrite], remainingBytesToWrite)}; - if (writtenBytes < 0) - throw ArchiveException {_archive.get()}; - - assert(static_cast(writtenBytes) <= remainingBytesToWrite); - remainingBytesToWrite -= writtenBytes; - } - } - - _currentEntryOffset += actualBytesRead; - return (_currentEntryOffset >= fileSize); - } - - std::int64_t - ArchiveZipper::onWriteCallback(const std::byte* buffer, std::size_t bufferSize) - { - if (!_currentOutputStream) - { - archive_set_error(_archive.get(), EIO, "IO error: operation cancelled"); - return -1; - } - - _currentOutputStream->write(reinterpret_cast(buffer), bufferSize); - if (!*_currentOutputStream) - throw Exception {"Failed to write " + std::to_string(bufferSize) + " bytes in final archive output!"}; - - _bytesWrittenInCurrentOutputStream += bufferSize; - - return bufferSize; - } -} // namespace Zip diff --git a/src/libs/utils/impl/ArchiveZipper.hpp b/src/libs/utils/impl/ArchiveZipper.hpp deleted file mode 100644 index 15b8b7af..00000000 --- a/src/libs/utils/impl/ArchiveZipper.hpp +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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 . - */ - -#pragma once - -#include -#include -#include "utils/IZipper.hpp" - -extern "C" -{ - struct archive; - struct archive_entry; -} - -namespace Zip -{ - class ArchiveZipper : public IZipper - { - public: - ArchiveZipper(const EntryContainer& files); - ArchiveZipper(const ArchiveZipper&) = delete; - ArchiveZipper& operator=(const ArchiveZipper&) = delete; - - private: - std::uint64_t writeSome(std::ostream& output) override; - bool isComplete() const override; - void abort() override; - - class ArchiveDeleter - { - public: - void operator()(struct ::archive* arch); - }; - using ArchivePtr = std::unique_ptr; - - class ArchiveEntryDeleter - { - public: - void operator()(struct ::archive_entry* archEntry); - }; - using ArchiveEntryPtr = std::unique_ptr; - - void prepareCurrentEntry(); - static ArchiveEntryPtr createArchiveEntry(const Entry& entry); - bool writeSomeCurrentFileData(); - std::int64_t onWriteCallback(const std::byte* buff, std::size_t size); - - const EntryContainer _entries; - ArchivePtr _archive; - - static inline constexpr std::size_t _writeBlockSize {65536}; - static inline constexpr std::size_t _readBufferSize {65536}; - std::vector _readBuffer; - - EntryContainer::const_iterator _currentEntry; - ArchiveEntryPtr _currentArchiveEntry; - - std::uint64_t _currentEntryOffset {}; - std::ostream* _currentOutputStream {}; - std::uint64_t _bytesWrittenInCurrentOutputStream {}; - }; - -} // namespace Zip - diff --git a/src/libs/utils/impl/ChildProcess.cpp b/src/libs/utils/impl/ChildProcess.cpp deleted file mode 100644 index d9d1b614..00000000 --- a/src/libs/utils/impl/ChildProcess.cpp +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (C) 2020 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 . - */ - -#include "ChildProcess.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include - -#include "utils/Exception.hpp" -#include "utils/ILogger.hpp" - -namespace -{ - class SystemException : public ChildProcessException - { - public: - SystemException(int err, const std::string& errMsg) - : ChildProcessException{ errMsg + ": " + ::strerror(err) } - {} - - SystemException(boost::system::error_code ec, const std::string& errMsg) - : ChildProcessException{ errMsg + ": " + ec.message() } - {} - }; -} - -ChildProcess::ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args) - : _ioContext{ ioContext } - , _childStdout{ _ioContext } - -{ - // make sure only one thread is executing this part of code - static std::mutex mutex; - std::unique_lock lock{ mutex }; - - int pipe[2]; - - int res{ pipe2(pipe, O_NONBLOCK | O_CLOEXEC) }; - if (res < 0) - throw SystemException{ errno, "pipe2 failed!" }; - - { -#if defined(__linux__) && defined(F_SETPIPE_SZ) - // Just a hint here to prevent the writer from writing too many bytes ahead of the reader - constexpr std::size_t pipeSize{ 65536 * 4 }; - - if (fcntl(pipe[0], F_SETPIPE_SZ, pipeSize) == -1) - throw SystemException{ errno, "fcntl failed!" }; - if (fcntl(pipe[1], F_SETPIPE_SZ, pipeSize) == -1) - throw SystemException{ errno, "fcntl failed!" }; -#endif - } - - res = fork(); - if (res == -1) - throw SystemException{ errno, "fork failed!" }; - - if (res == 0) // CHILD - { - close(pipe[0]); - close(STDIN_FILENO); - close(STDERR_FILENO); - - // Replace stdout with pipe write - if (dup2(pipe[1], STDOUT_FILENO) == -1) - exit(-1); - - std::vector execArgs; - std::transform(std::cbegin(args), std::cend(args), std::back_inserter(execArgs), [](const std::string& arg) { return arg.c_str(); }); - execArgs.push_back(nullptr); - - res = execv(path.string().c_str(), (char* const*)&execArgs[0]); - if (res == -1) - exit(-1); - } - else // PARENT - { - close(pipe[1]); - { - boost::system::error_code assignError; - _childStdout.assign(pipe[0], assignError); - if (assignError) - throw SystemException{ assignError, "fork failed!" }; - } - _childPID = res; - } -} - -ChildProcess::~ChildProcess() -{ - LMS_LOG(CHILDPROCESS, DEBUG, "Closing child process..."); - { - boost::system::error_code closeError; - _childStdout.close(closeError); - if (closeError) - LMS_LOG(CHILDPROCESS, ERROR, "Closed failed: " << closeError.message()); - } - - if (!_finished) - kill(); - - wait(true); -} - -void ChildProcess::kill() -{ - // process may already have finished - LMS_LOG(CHILDPROCESS, DEBUG, "Killing child process..."); - if (::kill(_childPID, SIGKILL) == -1) - LMS_LOG(CHILDPROCESS, DEBUG, "Kill failed: " << ::strerror(errno)); -} - -bool ChildProcess::wait(bool block) -{ - assert(!_waited); - - int wstatus{}; - const pid_t pid{ waitpid(_childPID, &wstatus, block ? 0 : WNOHANG) }; - - if (pid == -1) - throw SystemException{ errno, "waitpid failed!" }; - else if (pid == 0) - return false; - - if (WIFEXITED(wstatus)) - { - _exitCode = WEXITSTATUS(wstatus); - LMS_LOG(CHILDPROCESS, DEBUG, "Exit code = " << *_exitCode); - } - - _waited = true; - return true; -} - -void ChildProcess::asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) -{ - assert(!finished()); - - LMS_LOG(CHILDPROCESS, DEBUG, "Async read, bufferSize = " << bufferSize); - - boost::asio::async_read(_childStdout, boost::asio::buffer(data, bufferSize), - [this, callback{ std::move(callback) }](const boost::system::error_code& error, std::size_t bytesTransferred) - { - LMS_LOG(CHILDPROCESS, DEBUG, "Async read cb - ec = '" << error.message() << "' (" << error.value() << "), bytesTransferred = " << bytesTransferred); - - ReadResult readResult{ ReadResult::Success }; - if (error) - { - if (error != boost::asio::error::eof) - { - // forbidden to read any captured param here as the ChildProcess instance may already have been killed - return; - } - - readResult = ReadResult::EndOfFile; - _finished = true; - } - - callback(readResult, bytesTransferred); - }); -} - -std::size_t ChildProcess::readSome(std::byte* data, std::size_t bufferSize) -{ - boost::system::error_code ec; - const std::size_t res{ _childStdout.read_some(boost::asio::buffer(data, bufferSize), ec) }; - LMS_LOG(CHILDPROCESS, DEBUG, "read some " << res << " bytes, ec = " << ec.message()); - if (ec) - _childStdout.close(ec); - - return res; -} - -bool ChildProcess::finished() const -{ - return _finished; -} - diff --git a/src/libs/utils/impl/Config.cpp b/src/libs/utils/impl/Config.cpp deleted file mode 100644 index c21f0aaa..00000000 --- a/src/libs/utils/impl/Config.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (C) 2016 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 . - */ - -#include "Config.hpp" - -#include "utils/Exception.hpp" -#include "utils/ILogger.hpp" - -std::unique_ptr createConfig(const std::filesystem::path& p) -{ - return std::make_unique(p); -} - -Config::Config(const std::filesystem::path& p) -{ - try - { - _config.readFile(p.string().c_str()); - } - catch( libconfig::FileIOException& e) - { - throw LmsException {"Cannot open config file '" + p.string() + "'"}; - } - catch( libconfig::ParseException& e) - { - throw LmsException {"Cannot parse config file '" + p.string() + "', line = " + std::to_string(e.getLine()) + ", error = '" + e.getError() + "'"}; - } - catch (libconfig::ConfigException& e) - { - throw LmsException {"Cannot open config file '" + p.string() + "': " + e.what()}; - } -} - -std::string_view -Config::getString(std::string_view setting, std::string_view def) -{ - try - { - return static_cast(_config.lookup(std::string {setting})); - } - catch (libconfig::ConfigException&) - { - return def; - } -} - -void -Config::visitStrings(std::string_view setting, std::function _func, std::initializer_list defs) -{ - try - { - const libconfig::Setting& values {_config.lookup(std::string {setting})}; - for (int i {}; i < values.getLength(); ++i) - _func(static_cast(values[i])); - } - catch (const libconfig::SettingNotFoundException&) - { - for (std::string_view def : defs) - _func(def); - } - catch (libconfig::ConfigException&) - { - } -} - -std::filesystem::path -Config::getPath(std::string_view setting, const std::filesystem::path& path) -{ - try - { - const char* res {_config.lookup(std::string {setting})}; - return std::filesystem::path {std::string(res)}; - } - catch (libconfig::ConfigException&) - { - return path; - } -} - -unsigned long -Config::getULong(std::string_view setting, unsigned long def) -{ - try - { - return static_cast(_config.lookup(std::string {setting})); - } - catch (libconfig::ConfigException&) - { - return def; - } -} - -long -Config::getLong(std::string_view setting, long def) -{ - try - { - return _config.lookup(std::string {setting}); - } - catch (libconfig::ConfigException&) - { - return def; - } -} - -bool -Config::getBool(std::string_view setting, bool def) -{ - try - { - return _config.lookup(std::string {setting}); - } - catch (libconfig::ConfigException&) - { - return def; - } -} - diff --git a/src/libs/utils/impl/Config.hpp b/src/libs/utils/impl/Config.hpp deleted file mode 100644 index cbbb2542..00000000 --- a/src/libs/utils/impl/Config.hpp +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2016 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 . - */ -#pragma once - -#include "utils/IConfig.hpp" - -#include - -// Used to get config values from configuration files -class Config final : public IConfig -{ - public: - Config(const std::filesystem::path& p); - ~Config() = default; - - Config(const Config&) = delete; - Config& operator=(const Config&) = delete; - Config(Config&&) = delete; - Config& operator=(Config&&) = delete; - - // Default values are returned in case of setting not found - std::string_view getString(std::string_view setting, std::string_view def = "") override; - void visitStrings(std::string_view setting, std::function _func, std::initializer_list defs) override; - std::filesystem::path getPath(std::string_view setting, const std::filesystem::path& def = std::filesystem::path()) override; - unsigned long getULong(std::string_view setting, unsigned long def = 0) override; - long getLong(std::string_view setting, long def = 0) override; - bool getBool(std::string_view setting, bool def = false) override; - - private: - - libconfig::Config _config; -}; - diff --git a/src/libs/utils/impl/FileResourceHandler.cpp b/src/libs/utils/impl/FileResourceHandler.cpp deleted file mode 100644 index 026befd1..00000000 --- a/src/libs/utils/impl/FileResourceHandler.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (C) 2020 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 . - */ - -#include "FileResourceHandler.hpp" - -#include - -#include "utils/ILogger.hpp" - -std::unique_ptr -createFileResourceHandler(const std::filesystem::path& path, std::string_view mimeType) -{ - return std::make_unique(path, mimeType); -} - -FileResourceHandler::FileResourceHandler(const std::filesystem::path& path, std::string_view mimeType) - : _path{ path } - , _mimeType{ mimeType } -{ -} - -Wt::Http::ResponseContinuation* -FileResourceHandler::processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) -{ - ::uint64_t startByte{ _offset }; - std::ifstream ifs{ _path.string().c_str(), std::ios::in | std::ios::binary }; - - if (startByte == 0) - { - if (!ifs) - { - LMS_LOG(UTILS, ERROR, "Cannot open file stream for '" << _path.string() << "'"); - response.setStatus(404); - return {}; - } - - ifs.seekg(0, std::ios::end); - const ::uint64_t fileSize{ static_cast<::uint64_t>(ifs.tellg()) }; - ifs.seekg(0, std::ios::beg); - - LMS_LOG(UTILS, DEBUG, "File '" << _path.string() << "', fileSize = " << fileSize); - - response.addHeader("Accept-Ranges", "bytes"); - - const Wt::Http::Request::ByteRangeSpecifier ranges{ request.getRanges(fileSize) }; - if (!ranges.isSatisfiable()) - { - std::ostringstream contentRange; - contentRange << "bytes */" << fileSize; - response.setStatus(416); // Requested range not satisfiable - response.addHeader("Content-Range", contentRange.str()); - - LMS_LOG(UTILS, DEBUG, "Range not satisfiable"); - return {}; - } - - if (ranges.size() == 1) - { - LMS_LOG(UTILS, DEBUG, "Range requested = " << ranges[0].firstByte() << "-" << ranges[0].lastByte()); - - response.setStatus(206); - startByte = ranges[0].firstByte(); - _beyondLastByte = ranges[0].lastByte() + 1; - - std::ostringstream contentRange; - contentRange << "bytes " << startByte << "-" - << _beyondLastByte - 1 << "/" << fileSize; - - response.addHeader("Content-Range", contentRange.str()); - response.setContentLength(_beyondLastByte - startByte); - } - else - { - LMS_LOG(UTILS, DEBUG, "No range requested"); - - response.setStatus(200); - _beyondLastByte = fileSize; - response.setContentLength(_beyondLastByte); - } - - LMS_LOG(UTILS, DEBUG, "Mimetype set to '" << _mimeType << "'"); - response.setMimeType(_mimeType); - } - else if (!ifs) - { - LMS_LOG(UTILS, ERROR, "Cannot reopen file stream for '" << _path.string() << "'"); - return {}; - } - - ifs.seekg(static_cast(startByte)); - - std::vector buf; - buf.resize(_chunkSize); - - ::uint64_t restSize = _beyondLastByte - startByte; - ::uint64_t pieceSize = buf.size() > restSize ? restSize : buf.size(); - - ifs.read(&buf[0], pieceSize); - const ::uint64_t actualPieceSize{ static_cast<::uint64_t>(ifs.gcount()) }; - if (actualPieceSize > 0) - { - response.out().write(&buf[0], actualPieceSize); - LMS_LOG(UTILS, DEBUG, "Written " << actualPieceSize << " bytes, range = " << startByte << "-" << startByte + actualPieceSize - 1 << ""); - } - else - { - LMS_LOG(UTILS, DEBUG, "Written 0 byte"); - } - - if (ifs.good() && actualPieceSize < restSize) - { - _offset = startByte + actualPieceSize; - LMS_LOG(UTILS, DEBUG, "Job not complete! Remaining range: " << _offset << "-" << _beyondLastByte - 1); - - return response.createContinuation(); - } - - LMS_LOG(UTILS, DEBUG, "Job complete!"); - return nullptr; -} diff --git a/src/libs/utils/impl/IOContextRunner.cpp b/src/libs/utils/impl/IOContextRunner.cpp deleted file mode 100644 index 15199bee..00000000 --- a/src/libs/utils/impl/IOContextRunner.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (C) 2021 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 . - */ - -#include "utils/IOContextRunner.hpp" - -#include - -#include "utils/ILogger.hpp" -#include "utils/ITraceLogger.hpp" - -IOContextRunner::IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name) - : _ioService{ ioService } - , _work{ ioService } -{ - LMS_LOG(UTILS, INFO, "Starting IO context with " << threadCount << " threads..."); - - for (std::size_t i{}; i < threadCount; ++i) - { - std::string threadName{ name }; - if (!threadName.empty()) - { - threadName += "Thread_"; - threadName += std::to_string(i); - } - - _threads.emplace_back([this, threadName] - { - if (!threadName.empty()) - { - if (auto * traceLogger{ Service::get() }) - traceLogger->setThreadName(std::this_thread::get_id(), threadName); - } - - try - { - _ioService.run(); - } - catch (const std::exception& e) - { - LMS_LOG(UTILS, FATAL, "Exception caught in IO context: " << e.what()); - std::abort(); - } - }); - } -} - -void IOContextRunner::stop() -{ - LMS_LOG(UTILS, DEBUG, "Stopping IO context..."); - _work.reset(); - _ioService.stop(); - LMS_LOG(UTILS, DEBUG, "IO context stopped!"); -} - -std::size_t IOContextRunner::getThreadCount() const -{ - return _threads.size(); -} - -IOContextRunner::~IOContextRunner() -{ - stop(); - - for (std::thread& t : _threads) - t.join(); -} diff --git a/src/libs/utils/impl/Logger.cpp b/src/libs/utils/impl/Logger.cpp deleted file mode 100644 index a0a9e988..00000000 --- a/src/libs/utils/impl/Logger.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2013 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 . - */ - -#include "utils/ILogger.hpp" - -const char* getModuleName(Module mod) -{ - switch (mod) - { - case Module::API_SUBSONIC: return "API_SUBSONIC"; - case Module::AUTH: return "AUTH"; - case Module::AV: return "AV"; - case Module::CHILDPROCESS: return "CHILDPROC"; - case Module::COVER: return "COVER"; - case Module::DB: return "DB"; - case Module::DBUPDATER: return "DB UPDATER"; - case Module::FEATURE: return "FEATURE"; - case Module::FEEDBACK: return "FEEDBACK"; - case Module::HTTP: return "HTTP"; - case Module::MAIN: return "MAIN"; - case Module::METADATA: return "METADATA"; - case Module::REMOTE: return "REMOTE"; - case Module::SCROBBLING: return "SCROBBLING"; - case Module::SERVICE: return "SERVICE"; - case Module::RECOMMENDATION: return "RECOMMENDATION"; - case Module::TRANSCODING: return "TRANSCODING"; - case Module::UI: return "UI"; - case Module::UTILS: return "UTILS"; - } - return ""; -} - -const char* getSeverityName(Severity sev) -{ - switch (sev) - { - case Severity::FATAL: return "fatal"; - case Severity::ERROR: return "error"; - case Severity::WARNING: return "warning"; - case Severity::INFO: return "info"; - case Severity::DEBUG: return "debug"; - } - return ""; -} - -Log::Log(ILogger& logger, Module module, Severity severity) - : _logger{ logger } - , _module{ module } - , _severity{ severity } - -{} - -Log::~Log() -{ - assert(_logger.isSeverityActive(_severity)); - _logger.processLog(*this); -} - -std::string Log::getMessage() const -{ - return _oss.str(); -} - diff --git a/src/libs/utils/impl/RecursiveSharedMutex.cpp b/src/libs/utils/impl/RecursiveSharedMutex.cpp deleted file mode 100644 index a763f4b3..00000000 --- a/src/libs/utils/impl/RecursiveSharedMutex.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2021 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 . - */ - -#include "utils/RecursiveSharedMutex.hpp" - -#include - -void -RecursiveSharedMutex::lock() -{ - const auto thisThreadId {std::this_thread::get_id()}; - - if (_uniqueOwner == thisThreadId) - { - // already locked - _uniqueCount++; - } - else - { - _mutex.lock(); - _uniqueOwner = thisThreadId; - assert(_uniqueCount == 0); - _uniqueCount = 1; - } -} - -void -RecursiveSharedMutex::unlock() -{ - assert(_uniqueCount > 0); - - if (--_uniqueCount == 0) - { - _uniqueOwner = {}; - _mutex.unlock(); - } -} - -void -RecursiveSharedMutex::lock_shared() -{ - const auto thisThreadId {std::this_thread::get_id()}; - - if (_uniqueOwner == thisThreadId ) - { - // alone here, no need to lock - _sharedCounts[thisThreadId]++; - - return; - } - - bool needLock {}; - - { - std::scoped_lock lock {_sharedCountMutex}; - - auto& sharedCount {_sharedCounts[thisThreadId]}; - if (sharedCount == 0) - needLock = true; - else - ++sharedCount; - } - - if (needLock) - { - _mutex.lock_shared(); - - assert(_uniqueOwner == std::thread::id {}); - - std::scoped_lock lock {_sharedCountMutex}; - _sharedCounts[thisThreadId]++; - } -} - -void -RecursiveSharedMutex::unlock_shared() -{ - const auto thisThreadId {std::this_thread::get_id()}; - - if (_uniqueOwner == thisThreadId ) - { - // alone here, no need to lock - auto& sharedCount {_sharedCounts[thisThreadId ]}; - assert(sharedCount > 0); - --sharedCount; - - return; - } - - bool needUnlock {}; - - { - std::scoped_lock lock {_sharedCountMutex}; - - auto& sharedCount {_sharedCounts[thisThreadId]}; - assert(sharedCount > 0); - needUnlock = (--sharedCount == 0); - } - - if (needUnlock) - _mutex.unlock_shared(); -} - -#ifndef NDEBUG -bool -RecursiveSharedMutex::isUniqueLocked() -{ - return _uniqueOwner == std::this_thread::get_id(); -} - -bool -RecursiveSharedMutex::isSharedLocked() -{ - const auto thisThreadId {std::this_thread::get_id()}; - if (_uniqueOwner == thisThreadId ) - return true; - - std::scoped_lock lock {_sharedCountMutex}; - return _sharedCounts[thisThreadId] > 0; -} -#endif diff --git a/src/libs/utils/impl/UUID.cpp b/src/libs/utils/impl/UUID.cpp deleted file mode 100644 index 096ca0e5..00000000 --- a/src/libs/utils/impl/UUID.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2020 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 . - */ - -#include "utils/UUID.hpp" - -#include -#include -#include -#include - -#include "utils/Random.hpp" -#include "utils/String.hpp" - -namespace StringUtils -{ - template <> - std::optional - readAs(std::string_view str) - { - return UUID::fromString(str); - } -} -namespace -{ - bool stringIsUUID(std::string_view str) - { - static const std::regex re{ R"([0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12})" }; - - return std::regex_match(std::cbegin(str), std::cend(str), re); - } -} - -UUID::UUID(std::string_view str) - : _value{ StringUtils::stringToLower(str) } -{ -} - -std::optional UUID::fromString(std::string_view str) -{ - if (!stringIsUUID(str)) - return std::nullopt; - - return UUID{ str }; -} - -UUID UUID::generate() -{ - // Form is "123e4567-e89b-12d3-a456-426614174000" - // TODO: store 128 bits and only convert to string when necessary - - std::ostringstream oss; - - auto concatRandomBytes{ [](std::ostream& os, std::size_t byteCount) - { - for (std::size_t i {}; i < byteCount; ++i) - os << std::hex << std::setfill('0') << std::setw(2) << static_cast(Random::getRandom(0, 255)); - } }; - - concatRandomBytes(oss, 4); - oss << "-"; - concatRandomBytes(oss, 2); - oss << "-"; - concatRandomBytes(oss, 2); - oss << "-"; - concatRandomBytes(oss, 2); - oss << "-"; - concatRandomBytes(oss, 6); - - const auto uuid{fromString(oss.str())}; - assert(uuid); - return uuid.value(); -} - diff --git a/src/libs/utils/impl/WtLogger.cpp b/src/libs/utils/impl/WtLogger.cpp deleted file mode 100644 index f5f34660..00000000 --- a/src/libs/utils/impl/WtLogger.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * 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 . - */ - -#include "utils/WtLogger.hpp" - -#include -#include -#include -#include - -#include "utils/Exception.hpp" - -namespace -{ - std::string to_string(std::thread::id id) - { - std::ostringstream oss; - oss << id; - return oss.str(); - } -} - -WtLogger::WtLogger(Severity minSeverity) - : _minSeverity{ minSeverity } -{ -} - -std::string WtLogger::computeLogConfig(Severity minSeverity) -{ - switch (minSeverity) - { - case Severity::DEBUG: return "*"; - case Severity::INFO: return "* -debug"; - case Severity::WARNING: return "* -debug -info"; - case Severity::ERROR: return "* -debug -info -warning"; - case Severity::FATAL: return "* -debug -info -warning -error"; - } - - throw LmsException{ "Unhandled severity" }; -} - -bool WtLogger::isSeverityActive(Severity severity) const -{ - return static_cast(severity) <= static_cast(_minSeverity); -} - -void WtLogger::processLog(const Log& log) -{ - Wt::log(getSeverityName(log.getSeverity())) << Wt::WLogger::sep << to_string(std::this_thread::get_id()) << Wt::WLogger::sep << "[" << getModuleName(log.getModule()) << "]" << Wt::WLogger::sep << log.getMessage(); -} - diff --git a/src/libs/utils/impl/http/ClientRequest.hpp b/src/libs/utils/impl/http/ClientRequest.hpp deleted file mode 100644 index 0b9f028d..00000000 --- a/src/libs/utils/impl/http/ClientRequest.hpp +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (C) 2021 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 . - */ - -#pragma once - -#include -#include -#include "utils/http/ClientRequestParameters.hpp" - -namespace Http -{ - class ClientRequest - { - public: - ClientRequest(ClientGETRequestParameters&& GETParams) : _parameters {std::move(GETParams)} {} - ClientRequest(ClientPOSTRequestParameters&& POSTParams) : _parameters {std::move(POSTParams)} {} - - std::size_t retryCount {}; - - const ClientRequestParameters& getParameters() const - { - const ClientRequestParameters* res; - - std::visit([&](const auto& parameters) - { - res = &static_cast(parameters); - }, _parameters); - - return *res; - } - - - enum class Type - { - GET, - POST - }; - Type getType() const - { - if (std::holds_alternative(_parameters)) - return Type::GET; - else - return Type::POST; - } - - const ClientGETRequestParameters& getGETParameters() const - { - return std::get(_parameters); - } - - const ClientPOSTRequestParameters& getPOSTParameters() const - { - return std::get(_parameters); - } - - private: - std::variant _parameters; - }; -} diff --git a/src/libs/utils/impl/http/SendQueue.cpp b/src/libs/utils/impl/http/SendQueue.cpp deleted file mode 100644 index 4c5ccee2..00000000 --- a/src/libs/utils/impl/http/SendQueue.cpp +++ /dev/null @@ -1,249 +0,0 @@ -/* - * Copyright (C) 2021 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 . - */ - -#include "SendQueue.hpp" - -#include -#include - -#include "utils/Exception.hpp" -#include "utils/ILogger.hpp" -#include "utils/ITraceLogger.hpp" -#include "utils/String.hpp" - -#define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[Http SendQueue] - " << message) - -namespace StringUtils -{ - template<> - std::optional - readAs(std::string_view str) - { - std::optional res; - - if (const std::optional value {StringUtils::readAs(str)}) - res = std::chrono::seconds {*value}; - - return res; - } -} - -namespace -{ - template - std::optional - headerReadAs(const Wt::Http::Message& msg, std::string_view headerName) - { - std::optional res; - - if (const std::string* headerValue {msg.getHeader(std::string {headerName})}) - res = StringUtils::readAs(*headerValue); - - return res; - } -} - -namespace Http -{ - SendQueue::SendQueue(boost::asio::io_context& ioContext, std::string_view baseUrl) - : _ioContext {ioContext} - , _baseUrl {baseUrl} - { - _client.done().connect([this](Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) - { - _strand.dispatch([this, ec, msg = std::move(msg)] - { - onClientDone(ec, msg); - }); - }); - } - - SendQueue::~SendQueue() - { - _client.abort(); - } - - void - SendQueue::sendRequest(std::unique_ptr request) - { - boost::asio::dispatch(_strand, [this, request = std::move(request)]() mutable - { - _sendQueue[request->getParameters().priority].emplace_back(std::move(request)); - - if (_state == State::Idle) - sendNextQueuedRequest(); - }); - } - - void - SendQueue::sendNextQueuedRequest() - { - assert(_state == State::Idle); - assert(!_currentRequest); - - for (auto& [prio, requests] : _sendQueue) - { - LOG(DEBUG, "Processing prio " << static_cast(prio) << ", request count = " << requests.size()); - while (!requests.empty()) - { - std::unique_ptr request {std::move(requests.front())}; - requests.pop_front(); - - if (!sendRequest(*request)) - continue; - - _state = State::Sending; - _currentRequest = std::move(request); - return; - } - } - } - - bool - SendQueue::sendRequest(const ClientRequest& request) - { - LMS_SCOPED_TRACE_DETAILED("SendQueue", "SendRequest"); - - std::string url {_baseUrl + request.getParameters().relativeUrl}; - LOG(DEBUG, "Sending request to url '" << url << "'"); - - bool res {}; - switch (request.getType()) - { - case ClientRequest::Type::GET: - res = _client.get(url, request.getGETParameters().headers); - break; - - case ClientRequest::Type::POST: - res = _client.post(url, request.getPOSTParameters().message); - break; - } - - if (!res) - LOG(ERROR, "Send failed, bad url or unsupported scheme?"); - - return res; - } - - void - SendQueue::onClientDone(Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) - { - LMS_SCOPED_TRACE_DETAILED("SendQueue", "OnClientDone"); - - if (ec == boost::asio::error::operation_aborted) - { - LOG(DEBUG, "Client aborted"); - return; - } - - assert(_currentRequest); - _state = State::Idle; - - LOG(DEBUG, "Client done. status = " << msg.status()); - if (ec) - onClientDoneError(std::move(_currentRequest), ec); - else - onClientDoneSuccess(std::move(_currentRequest), msg); - } - - void - SendQueue::onClientDoneError(std::unique_ptr request, Wt::AsioWrapper::error_code ec) - { - LOG(ERROR, "Retry " << request->retryCount << ", client error: '" << ec.message() << "'"); - - // may be a network error, try again later - throttle(_defaultRetryWaitDuration); - - if (request->retryCount++ < _maxRetryCount) - { - _sendQueue[request->getParameters().priority].emplace_front(std::move(request)); - } - else - { - LOG(ERROR, "Too many retries, giving up operation and throttle"); - if (request->getParameters().onFailureFunc) - request->getParameters().onFailureFunc(); - } - } - - void - SendQueue::onClientDoneSuccess(std::unique_ptr request, const Wt::Http::Message& msg) - { - const ClientRequestParameters& requestParameters {request->getParameters()}; - bool mustThrottle{}; - if (msg.status() == 429) - { - _sendQueue[requestParameters.priority].emplace_front(std::move(request)); - mustThrottle = true; - } - - const auto remainingCount {headerReadAs(msg, "X-RateLimit-Remaining")}; - LOG(DEBUG, "Remaining messages = " << (remainingCount ? *remainingCount : 0)); - if (mustThrottle || (remainingCount && *remainingCount == 0)) - { - const auto waitDuration {headerReadAs(msg, "X-RateLimit-Reset-In")}; - throttle(waitDuration.value_or(_defaultRetryWaitDuration)); - } - - if (!mustThrottle) - { - if (msg.status() == 200) - { - if (requestParameters.onSuccessFunc) - requestParameters.onSuccessFunc(msg.body()); - } - else - { - LOG(ERROR, "Send error: '" << msg.body() << "'"); - if (requestParameters.onFailureFunc) - requestParameters.onFailureFunc(); - } - } - - if (_state == State::Idle) - sendNextQueuedRequest(); - } - - void - SendQueue::throttle(std::chrono::seconds requestedDuration) - { - assert(_state == State::Idle); - - const std::chrono::seconds duration {clamp(requestedDuration, _minRetryWaitDuration, _maxRetryWaitDuration)}; - LOG(DEBUG, "Throttling for " << duration.count() << " seconds"); - - _throttleTimer.expires_after(duration); - _throttleTimer.async_wait([this](const boost::system::error_code& ec) - { - if (ec == boost::asio::error::operation_aborted) - { - LOG(DEBUG, "Throttle aborted"); - return; - } - else if (ec) - { - throw LmsException {"Throttle timer failure: " + std::string {ec.message()} }; - } - - _state = State::Idle; - sendNextQueuedRequest(); - }); - _state = State::Throttled; - } -} // namespace Scrobbling::ListenBrainz diff --git a/src/libs/utils/impl/http/SendQueue.hpp b/src/libs/utils/impl/http/SendQueue.hpp deleted file mode 100644 index e9eb4180..00000000 --- a/src/libs/utils/impl/http/SendQueue.hpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (C) 2021 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 . - */ - -#pragma once - -#include -#include -#include - -#include -#include -#include - -#include -#include "ClientRequest.hpp" - -namespace Http -{ - class SendQueue - { - public: - SendQueue(boost::asio::io_context& ioContext, std::string_view baseUrl); - ~SendQueue(); - - SendQueue(const SendQueue&) = delete; - SendQueue(const SendQueue&&) = delete; - SendQueue& operator=(const SendQueue&) = delete; - SendQueue& operator=(const SendQueue&&) = delete; - - void sendRequest(std::unique_ptr request); - - private: - void sendNextQueuedRequest(); - bool sendRequest(const ClientRequest& request); - void onClientDone(Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg); - void onClientDoneError(std::unique_ptr request, Wt::AsioWrapper::error_code ec); - void onClientDoneSuccess(std::unique_ptr request, const Wt::Http::Message& msg); - void throttle(std::chrono::seconds duration); - - const std::size_t _maxRetryCount {2}; - const std::chrono::seconds _defaultRetryWaitDuration {30}; - const std::chrono::seconds _minRetryWaitDuration {1}; - const std::chrono::seconds _maxRetryWaitDuration {300}; - - boost::asio::io_context& _ioContext; - boost::asio::io_context::strand _strand {_ioContext}; - boost::asio::steady_timer _throttleTimer {_ioContext}; - std::string _baseUrl; - - enum class State - { - Idle, - Throttled, - Sending, - }; - State _state {State::Idle}; - Wt::Http::Client _client {_ioContext}; - std::map>> _sendQueue; - std::unique_ptr _currentRequest; - }; - -} // namespace Scrobbling::ListenBrainz - diff --git a/src/libs/utils/include/utils/EnumSet.hpp b/src/libs/utils/include/utils/EnumSet.hpp deleted file mode 100644 index 997d81bd..00000000 --- a/src/libs/utils/include/utils/EnumSet.hpp +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright (C) 2020 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 . - */ - -#pragma once - -#include -#include -#include -#include -#include - -template -class EnumSet -{ - static_assert(std::is_enum::value); - static_assert(std::is_same::value || std::is_same::value); - - using IndexType = std::uint_fast8_t; - - public: - using ValueType = underlying_type; - - EnumSet() = default; - constexpr EnumSet(std::initializer_list values) - { - for (T value : values) - insert(value); - } - - template - constexpr EnumSet(It begin, It end) - { - assign(begin, end); - } - - template - constexpr void assign(It begin, It end) - { - clear(); - for (It it {begin}; it != end; ++it) - insert(*it); - } - - constexpr void insert(T value) - { - assert(static_cast(value) < sizeof(_bitfield) * 8); - _bitfield |= (underlying_type{ 1 } << static_cast(value)); - } - - constexpr void erase(T value) - { - assert(static_cast(value) < sizeof(_bitfield) * 8); - _bitfield &= ~(underlying_type{ 1 } << static_cast(value)); - } - - constexpr bool empty() const - { - return _bitfield == 0; - } - - constexpr bool contains(T value) const - { - assert(static_cast(value) < sizeof(_bitfield) * 8); - return _bitfield & (underlying_type{ 1 } << static_cast(value)); - } - - constexpr void clear() - { - _bitfield = 0; - } - - class iterator - { - public: - using value_type = T; - - constexpr value_type operator*() const - { - return static_cast(_index); - } - - constexpr bool operator==(const iterator& _other) const - { - return &_container == &_other._container && _index == _other._index; - } - - constexpr bool operator!=(const iterator& _other) const - { - return !(*this == _other); - } - - constexpr iterator& operator++() - { - _index = _container.getFirstBitSetIndex(_index + 1); - return *this; - } - - private: - friend class EnumSet; - - constexpr iterator(const EnumSet& _container, IndexType _index) - : _container {_container} - , _index {_index} - { - } - - const EnumSet& _container; - IndexType _index; - }; - - constexpr iterator begin() const - { - return iterator {*this, getFirstBitSetIndex()}; - } - - constexpr iterator end() const - { - return iterator {*this, npos}; - } - - constexpr underlying_type getBitfield() const - { - return _bitfield; - } - - 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::max() >= sizeof(underlying_type) * 8); - enum : IndexType { npos = sizeof(underlying_type) * 8 }; - - constexpr IndexType getFirstBitSetIndex(IndexType start = {}) const - { - assert(start < npos); - - // return npos if no bit found - IndexType res {countTrailingZero(_bitfield >> start)}; - if (res == npos) - return res; - - return res + start; - } - - static constexpr IndexType countTrailingZero(underlying_type bitField) - { - IndexType res {}; - - while (res < (sizeof(underlying_type) * 8) && (bitField & 1) == 0) - { - ++res; - bitField >>= 1; - } - - if (res == sizeof(underlying_type) * 8) - res = npos; - - return res; - } - - underlying_type _bitfield{}; -}; diff --git a/src/libs/utils/include/utils/IConfig.hpp b/src/libs/utils/include/utils/IConfig.hpp deleted file mode 100644 index e795b579..00000000 --- a/src/libs/utils/include/utils/IConfig.hpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2016 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 . - */ -#pragma once - -#include -#include -#include - -// Used to get config values from configuration files -class IConfig -{ - public: - virtual ~IConfig() = default; - - // Default values are returned in case of setting not found - virtual std::string_view getString(std::string_view setting, std::string_view def = "") = 0; - virtual void visitStrings(std::string_view setting, std::function _func, std::initializer_list def = {}) = 0; - virtual std::filesystem::path getPath(std::string_view setting, const std::filesystem::path& def = std::filesystem::path()) = 0; - virtual unsigned long getULong(std::string_view setting, unsigned long def = 0) = 0; - virtual long getLong(std::string_view setting, long def = 0) = 0; - virtual bool getBool(std::string_view setting, bool def = false) = 0; -}; - - -std::unique_ptr createConfig(const std::filesystem::path& p); - diff --git a/src/libs/utils/include/utils/ILogger.hpp b/src/libs/utils/include/utils/ILogger.hpp deleted file mode 100644 index 2955d0fa..00000000 --- a/src/libs/utils/include/utils/ILogger.hpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2013 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 . - */ - -#pragma once - -#include -#include - -#include "utils/String.hpp" -#include "Service.hpp" - -enum class Severity -{ - FATAL, - ERROR, - WARNING, - INFO, - DEBUG, -}; - -enum class Module -{ - API_SUBSONIC, - AUTH, - AV, - CHILDPROCESS, - COVER, - DB, - DBUPDATER, - FEATURE, - FEEDBACK, - HTTP, - MAIN, - METADATA, - REMOTE, - SCROBBLING, - SERVICE, - RECOMMENDATION, - TRANSCODING, - UI, - UTILS, -}; - -const char* getModuleName(Module mod); -const char* getSeverityName(Severity sev); - -class ILogger; -class Log -{ -public: - Log(ILogger& logger, Module module, Severity severity); - ~Log(); - - Module getModule() const { return _module; } - Severity getSeverity() const { return _severity; } - std::string getMessage() const; - - std::ostringstream& getOstream() { return _oss; } - -private: - Log(const Log&) = delete; - Log& operator=(const Log&) = delete; - - ILogger& _logger; - Module _module; - Severity _severity; - std::ostringstream _oss; -}; - -class ILogger -{ -public: - virtual ~ILogger() = default; - - virtual bool isSeverityActive(Severity severity) const = 0; - virtual void processLog(const Log& log) = 0; -}; - -#define LMS_LOG(module, severity, message) \ - do \ - { \ - if (auto* logger_ {::Service<::ILogger>::get()}; logger_ && logger_->isSeverityActive(::Severity::severity)) \ - ::Log{ *logger_, ::Module::module, ::Severity::severity }.getOstream() << message; \ - } while(0) diff --git a/src/libs/utils/include/utils/LiteralString.hpp b/src/libs/utils/include/utils/LiteralString.hpp deleted file mode 100644 index 9bcf16bd..00000000 --- a/src/libs/utils/include/utils/LiteralString.hpp +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Copyright (C) 2024 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 . - */ - -#pragma once - -#include -#include -#include - -class LiteralString -{ -public: - constexpr LiteralString() noexcept = default; - template - constexpr LiteralString(const char(&str)[N]) noexcept : _str{ str, N - 1 } { static_assert(N > 0); } - - constexpr const char* c_str() const noexcept { return _str.data(); } - constexpr std::size_t length() const noexcept { return _str.length(); } - constexpr std::string_view str() const noexcept { return _str; } - constexpr auto operator<=>(const LiteralString& other) const = default; - -private: - std::string_view _str; -}; - -namespace std -{ - template<> - struct hash - { - size_t operator()(const LiteralString& str) const - { - return hash{}(str.str()); - } - }; -} - -struct LiteralStringHash -{ - using hash_type = std::hash; - using is_transparent = void; - - [[nodiscard]] size_t operator()(const LiteralString& str) const { - return hash_type{}(str.str()); - } - - [[nodiscard]] size_t operator()(std::string_view str) const { - return hash_type{}(str); - } - - [[nodiscard]] size_t operator()(const std::string& str) const { - return hash_type{}(str); - } -}; - -struct LiteralStringEqual -{ - using is_transparent = void; - - [[nodiscard]] bool operator()(const LiteralString& lhs, const LiteralString& rhs) const { - return lhs == rhs; - } - - [[nodiscard]] bool operator()(const LiteralString& lhs, const std::string& rhs) const { - return lhs.str() == rhs; - } - - [[nodiscard]] bool operator()(const LiteralString& lhs, std::string_view rhs) const { - return lhs.str() == rhs; - } - - [[nodiscard]] bool operator()(const std::string_view& lhs, LiteralString rhs) const { - return lhs == rhs.str(); - } - - [[nodiscard]] bool operator()(const std::string& lhs, const LiteralString& rhs) const { - return lhs == rhs.str(); - } -}; diff --git a/src/libs/utils/include/utils/Random.hpp b/src/libs/utils/include/utils/Random.hpp deleted file mode 100644 index b69505c7..00000000 --- a/src/libs/utils/include/utils/Random.hpp +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2020 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 . - */ - -#pragma once - -#include -#include - -namespace Random { - -using RandGenerator = std::mt19937; -RandGenerator& getRandGenerator(); - -RandGenerator createSeededGenerator(uint_fast32_t seed); - -template -T -getRandom(T min, T max) -{ - std::uniform_int_distribution<> dist {min, max}; - return dist (getRandGenerator()); -} - -template -T -getRealRandom(T min, T max) -{ - std::uniform_real_distribution<> dist {min, max}; - return dist (getRandGenerator()); -} - -template -void -shuffleContainer(Container& container) -{ - std::shuffle(std::begin(container), std::end(container), getRandGenerator()); -} - -template -typename Container::const_iterator -pickRandom(const Container& container) -{ - if (container.empty()) - return std::end(container); - - return std::next(std::begin(container), getRandom(0, static_cast(container.size() - 1))); -} - -} - diff --git a/src/libs/utils/include/utils/Service.hpp b/src/libs/utils/include/utils/Service.hpp deleted file mode 100644 index 4aebe5e6..00000000 --- a/src/libs/utils/include/utils/Service.hpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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 . - */ - -#pragma once - -#include -#include - -template -class Service -{ - public: - Service() = default; - Service(std::unique_ptr service) - { - assign(std::move(service)); - } - - ~Service() - { - clear(); - } - - Service(const Service&) = delete; - Service(Service&&) = delete; - Service& operator=(const Service&) = delete; - Service& operator=(Service&&) = delete; - - Class* operator->() const - { - return Service::get(); - } - - Class& operator*() const - { - return *Service::get(); - } - - static Class* get() { return _service.get(); } - static bool exists() { return _service.get(); } - - template - static Class& assign(std::unique_ptr service) - { - assert(!_service); - _service = std::move(service); - return *get(); - } - - private: - static void clear() { _service.reset(); } - - static inline std::unique_ptr _service; -}; - diff --git a/src/libs/utils/include/utils/StreamLogger.hpp b/src/libs/utils/include/utils/StreamLogger.hpp deleted file mode 100644 index ae68c036..00000000 --- a/src/libs/utils/include/utils/StreamLogger.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 . - */ - -#pragma once - -#include "utils/EnumSet.hpp" -#include "utils/ILogger.hpp" - -class StreamLogger final : public ILogger -{ -public: - static constexpr EnumSet allSeverities{ Severity::FATAL, Severity::ERROR, Severity::WARNING, Severity::INFO, Severity::DEBUG }; - static constexpr EnumSet defaultSeverities{ Severity::FATAL, Severity::ERROR, Severity::WARNING, Severity::INFO }; - - StreamLogger(std::ostream& oss, EnumSet severities = defaultSeverities); - - bool isSeverityActive(Severity severity) const override { return _severities.contains(severity); } - void processLog(const Log& log) override; - -private: - std::ostream& _os; - const EnumSet _severities; -}; - diff --git a/src/libs/utils/test/EnumSet.cpp b/src/libs/utils/test/EnumSet.cpp deleted file mode 100644 index b5902aa8..00000000 --- a/src/libs/utils/test/EnumSet.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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 . - */ - -#include - -#include "utils/EnumSet.hpp" - -TEST(EnumSet, ctr) -{ - enum class Foo - { - One, - Two, - }; - - { - constexpr EnumSet 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 test {Foo::One, Foo::Two}; - constexpr auto bitfield {test.getBitfield()}; - - EnumSet 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); - } -} diff --git a/src/libs/utils/test/LiteralString.cpp b/src/libs/utils/test/LiteralString.cpp deleted file mode 100644 index b191f7af..00000000 --- a/src/libs/utils/test/LiteralString.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 . - */ - -#include -#include - -#include "utils/LiteralString.hpp" - -TEST(LiteralString, ctr) -{ - { - constexpr LiteralString foo{ "abc" }; - static_assert(foo.length() == 3); - static_assert(foo == "abc"); - static_assert(foo == LiteralString{ "abc" }); - static_assert(foo < LiteralString{ "abcd" }); - static_assert(foo > LiteralString{ "aac" }); - - EXPECT_EQ(::strlen(foo.c_str()), 3); - EXPECT_EQ(foo, LiteralString{ "abc" }); - EXPECT_GT(foo, LiteralString{ "abb" }); - EXPECT_LT(foo, LiteralString{ "abcd" }); - } - - { - constexpr LiteralString foo{ "" }; - static_assert(foo.length() == 0); - static_assert(foo == ""); - static_assert(foo == LiteralString{ "" }); - static_assert(foo < LiteralString{"a"}); - EXPECT_EQ(::strlen(foo.c_str()), 0); - } -} - -TEST(LiteralString, unordered_map) -{ - { - std::unordered_map myMap{ {"abc", 42} }; - - EXPECT_TRUE(myMap.contains("abc")); - EXPECT_TRUE(myMap.contains(LiteralString{ "abc" })); - EXPECT_FALSE(myMap.contains("abcd")); - } - - { - std::unordered_map myMap{ {"abc", 42} }; - EXPECT_TRUE(myMap.contains(std::string{ "abc" })); - EXPECT_TRUE(myMap.contains(std::string_view{ "abc" })); - EXPECT_FALSE(myMap.contains(std::string{ "abcd" })); - EXPECT_FALSE(myMap.contains(std::string_view{ "abcd" })); - } - -} diff --git a/src/libs/utils/test/Path.cpp b/src/libs/utils/test/Path.cpp deleted file mode 100644 index 23ecd894..00000000 --- a/src/libs/utils/test/Path.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 . - */ - -#include - -#include "utils/Path.hpp" - -TEST(Path, getLongestCommonPath) -{ - using namespace PathUtils; - - struct TestCase - { - std::filesystem::path path1; - std::filesystem::path path2; - std::filesystem::path expectedCommonPath; - }; - - TestCase tests[] - { - {"foo.txt", "/foo/foo.txt", ""}, - {"/", "/file.txt", "/"}, - {"/foo/bar/file1.txt", "/foo/bar/file2.txt", "/foo/bar"}, - {"/foo/bar/file.txt", "/foo/bar/file.txt", "/foo/bar/file.txt"}, - {"/dir1/file.txt", "/dir2/file.txt", "/"}, - {"/prefix/folder/file.txt", "/prefix/folder/subfolder/file.txt", "/prefix/folder"}, - }; - - for (const TestCase& test : tests) - { - EXPECT_EQ(PathUtils::getLongestCommonPath(test.path1, test.path2), test.expectedCommonPath); - } -} - - -TEST(Path, getLongestCommonPathIterator) -{ - using namespace PathUtils; - - struct TestCase - { - std::vector paths; - std::filesystem::path expectedCommonPath; - }; - - TestCase tests[] - { - {{}, ""}, - {{"/"}, "/"}, - {{"/foo", "/bar"}, "/"}, - {{"/foo/bar/file1.txt", "/foo/bar/file2.txt"}, "/foo/bar"}, - {{"/foo", "/foo/"}, "/foo"}, - {{"/foo", "/foo"}, "/foo"}, - {{"/foo/", "/foo/"}, "/foo/"}, - {{"/foo/", "/foo/", "/bar"}, "/"}, - {{"/foo/", "/foo/", "/foo/bar"}, "/foo"}, - }; - - for (const TestCase& test : tests) - { - EXPECT_EQ(PathUtils::getLongestCommonPath(std::cbegin(test.paths), std::cend(test.paths)), test.expectedCommonPath); - } -} - -TEST(Path, isPathInRootPath) -{ - using namespace PathUtils; - - struct TestCase - { - std::filesystem::path path; - std::filesystem::path rootPath; - bool expectedResult; - }; - - TestCase tests[] - { - {"/file.txt", "/", true}, - {"/root/folder/file.txt", "/root", true}, - {"/root/file.txt", "/root", true}, - {"/root/file.txt", "/root/", true}, - {"/root", "/root", true}, - {"/root", "/root/", true}, - {"/root/", "/root", true}, - {"/root/", "/root/", true}, - {"/folder/file.txt", "/root", false}, - {"/folder/file.txt", "/root/", false}, - {"", "/root", false}, - }; - - for (const TestCase& test : tests) - { - EXPECT_EQ(PathUtils::isPathInRootPath(test.path, test.rootPath), test.expectedResult) << "Failed: path = " << test.path << ", rootPath = " << test.rootPath; - } -} \ No newline at end of file diff --git a/src/libs/utils/test/RecursiveSharedMutex.cpp b/src/libs/utils/test/RecursiveSharedMutex.cpp deleted file mode 100644 index 0728b91f..00000000 --- a/src/libs/utils/test/RecursiveSharedMutex.cpp +++ /dev/null @@ -1,106 +0,0 @@ -/* - * 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 . - */ - -#include -#include -#include -#include - -#include - -#include "utils/RecursiveSharedMutex.hpp" - -TEST(RecursiveSharedMutex, SingleThreaded) -{ - { - RecursiveSharedMutex mutex; - - { - std::unique_lock lock {mutex}; - } - - { - std::shared_lock lock {mutex}; - } - - { - std::unique_lock lock1 {mutex}; - std::unique_lock lock2 {mutex}; - } - - { - std::shared_lock lock1 {mutex}; - std::shared_lock lock2 {mutex}; - } - - { - std::unique_lock lock1 {mutex}; - std::shared_lock lock2 {mutex}; - } - } -} - -TEST(RecursiveSharedMutex, MultiThreaded) -{ - { - constexpr std::size_t nbThreads {10}; - std::vector threads; - - RecursiveSharedMutex mutex; - std::atomic nbUnique {}; - std::atomic nbShared {}; - for (std::size_t i {}; i < nbThreads; ++i) - { - threads.emplace_back([&] - { - { - std::unique_lock lock {mutex}; - - std::shared_lock lock2 {mutex}; - - assert(nbUnique == 0); - assert(nbShared == 0); - nbUnique++; - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - assert(nbUnique == 1); - assert(nbShared == 0); - nbUnique--; - } - - { - std::shared_lock lock {mutex}; - std::shared_lock lock2 {mutex}; - - assert(nbUnique == 0); - nbShared++; - - std::this_thread::sleep_for(std::chrono::milliseconds(15)); - - assert(nbShared > 0); - assert(nbShared <= nbThreads); - assert(nbUnique == 0); - nbShared--; - } - }); - } - - for (std::thread& t : threads) - t.join(); - } -} diff --git a/src/libs/utils/test/String.cpp b/src/libs/utils/test/String.cpp deleted file mode 100644 index 9087f54d..00000000 --- a/src/libs/utils/test/String.cpp +++ /dev/null @@ -1,295 +0,0 @@ -/* - * 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 . - */ - -#include - -#include -#include -#include -#include "utils/String.hpp" - - -TEST(StringUtils, splitString_charDelim) -{ - struct TestCase - { - std::string_view input; - char delimiter; - std::vector expectedOutput; - }; - - TestCase tests[] - { - {"abc", '-', {"abc"}}, - {"a", '-', {"a"}}, - {"", '-', {""}}, - {"a-b-c", '-', {"a", "b", "c"}}, - {"a|b|c", '|', {"a", "b", "c"}}, - {"a;b;c", ';', {"a", "b", "c"}}, - {";b;c", ';', {"", "b", "c"}}, - {" ;b;c", ';', {" ", "b", "c"}}, - {" ;;c", ';', {" ", "", "c"}}, - {" ; ;c", ';', {" ", " ", "c"}}, - {"a;b; ", ';', {"a", "b", " "}}, - {"a;b", ';', {"a", "b"}}, - {";b", ';', {"", "b"}}, - {";", ';', {"", ""}}, - {";;", ';', {"", "", ""}}, - {";;;", ';', {"", "", "", ""}}, - {";;a;;b;;", ';', {"", "", "a", "", "b", "", ""}}, - {"a b", ' ', {"a", "b"}}, - {"", ' ', {""}}, - {"a-b|c", '-', {"a","b|c"}}, - {"a|b-c", '-', {"a|b", "c"}}, - {"test=foo bar", '=', {"test", "foo bar"}}, - }; - - for (const TestCase& test : tests) - { - const std::vector res{ StringUtils::splitString(test.input, test.delimiter) }; - EXPECT_EQ(res, test.expectedOutput) << "Input = '" << test.input << "', delims = '" << test.delimiter << "'"; - } -} - -TEST(StringUtils, splitString_stringDelim) -{ - struct TestCase - { - std::string_view input; - std::string_view delimiter; - std::vector expectedOutput; - }; - - TestCase tests[] - { - {"abc", "", {"abc"}}, - {"abc", "-", {"abc"}}, - {"abc", "b", {"a", "c"}}, - {"ab/cd", "/", {"ab", "cd"}}, - {"ab/cd", "/ ", {"ab/cd"}}, - {"ab/cd", " /", {"ab/cd"}}, - {"ab /cd", " /", {"ab", "cd"}}, - {"ab/ cd", "/ ", {"ab", "cd"}}, - {"ab / cd", " / ", {"ab", "cd"}}, - {"ab/cd", " / ", {"ab/cd"}}, - {"ab/cd / ", " / ", {"ab/cd", ""}}, - }; - - for (const TestCase& test : tests) - { - const std::vector res{ StringUtils::splitString(test.input, test.delimiter) }; - EXPECT_EQ(res, test.expectedOutput) << "Input = '" << test.input << "', delims = '" << test.delimiter << "'"; - } -} - -TEST(StringUtils, joinStrings) -{ - struct TestCase - { - std::vector input; - std::string delimiter; - std::string expectedOutput; - }; - - TestCase tests[] - { - {{"a", "b", "c"}, "-", "a-b-c"}, - {{"a", "b", "c"}, ",", "a,b,c"}, - {{"a", "b", "c"}, "***", "a***b***c"}, - {{"a", "", "c"}, "-", "a--c"}, - {{"", "b", "c"}, "-", "-b-c"}, - {{"a"}, "-", "a"}, - {{"a"}, ",", "a"}, - }; - - for (const TestCase& test : tests) - { - const std::string str{ StringUtils::joinStrings(test.input, test.delimiter) }; - EXPECT_EQ(str, test.expectedOutput); - } -} - -TEST(StringUtils, escapeAndJoinStrings) -{ - struct TestCase - { - std::vector input; - char delimiter; - char escapeChar; - std::string expectedOutput; - }; - - TestCase tests[] - { - {{""}, ';', '\\', ""}, - {{";"}, ';', '\\', "\\;"}, - {{";;"}, ';', '\\', "\\;\\;"}, - {{"a;", "b"}, ';', '\\', "a\\;;b"}, - {{"a;", "b;"}, ';', '\\', "a\\;;b\\;"}, - }; - - for (const TestCase& test : tests) - { - const std::string str{ StringUtils::escapeAndJoinStrings(test.input, test.delimiter, test.escapeChar) }; - EXPECT_EQ(str, test.expectedOutput); - } -} - -TEST(StringUtils, splitEscapedStrings) -{ - struct TestCase - { - std::string input; - char delimiter; - char escapeChar; - std::vector expectedOutput; - }; - - TestCase tests[] - { - {"", ';', '\\', {}}, - {"\\;", ';', '\\', {";"}}, - {"\\;\\;", ';', '\\', {";;"}}, - {"a\\;;b", ';', '\\', {"a;", "b"}}, - {"a\\;;b\\;", ';', '\\', {"a;", "b;"}}, - }; - - for (const TestCase& test : tests) - { - const std::vector str{ StringUtils::splitEscapedStrings(test.input, test.delimiter, test.escapeChar) }; - EXPECT_EQ(str, test.expectedOutput); - } -} - -TEST(StringUtils, escapeJSString) -{ - EXPECT_EQ(StringUtils::jsEscape(""), ""); - EXPECT_EQ(StringUtils::jsEscape(R"(Test'.mp3)"), R"(Test\'.mp3)"); - EXPECT_EQ(StringUtils::jsEscape(R"(Test"".mp3)"), R"(Test\"\".mp3)"); - EXPECT_EQ(StringUtils::jsEscape(R"(\Test\.mp3)"), R"(\\Test\\.mp3)"); -} - -TEST(StringUtils, escapeJsonString) -{ - EXPECT_EQ(StringUtils::jsonEscape(""), ""); - EXPECT_EQ(StringUtils::jsonEscape(R"(Test'.mp3)"), R"(Test'.mp3)"); - EXPECT_EQ(StringUtils::jsonEscape(R"(Test"".mp3)"), R"(Test\"\".mp3)"); - EXPECT_EQ(StringUtils::jsonEscape(R"(\Test\.mp3)"), R"(\\Test\\.mp3)"); -} - -TEST(StringUtils, escapeString) -{ - EXPECT_EQ(StringUtils::escapeString("", "*", ' '), ""); - EXPECT_EQ(StringUtils::escapeString("", "", ' '), ""); - EXPECT_EQ(StringUtils::escapeString("a", "", ' '), "a"); - EXPECT_EQ(StringUtils::escapeString("*", "*", '_'), "_*"); - EXPECT_EQ(StringUtils::escapeString("*a*", "*", '_'), "_*a_*"); - EXPECT_EQ(StringUtils::escapeString("*a|", "*|", '_'), "_*a_|"); - EXPECT_EQ(StringUtils::escapeString("**||", "*|", '_'), "_*_*_|_|"); - EXPECT_EQ(StringUtils::escapeString("one;two", ";", '\\'), "one\\;two"); - EXPECT_EQ(StringUtils::escapeString("one\\;two", ";", '\\'), "one\\\\;two"); - EXPECT_EQ(StringUtils::escapeString("one;", ";", '\\'), "one\\;"); -} - -TEST(StringUtils, unescapeString) -{ - EXPECT_EQ(StringUtils::unescapeString("one\\", '\\'), "one\\"); - EXPECT_EQ(StringUtils::unescapeString("\\\\one", '\\'), "\\one"); - EXPECT_EQ(StringUtils::unescapeString("one\\;two", '\\'), "one;two"); - EXPECT_EQ(StringUtils::unescapeString("one\\\\;two", '\\'), "one\\;two"); -} - -TEST(StringUtils, readAs_bool) -{ - EXPECT_EQ(StringUtils::readAs("true"), true); - EXPECT_EQ(StringUtils::readAs("1"), true); - EXPECT_EQ(StringUtils::readAs("false"), false); - EXPECT_EQ(StringUtils::readAs("0"), false); - EXPECT_EQ(StringUtils::readAs("foo"), std::nullopt); - EXPECT_EQ(StringUtils::readAs(""), std::nullopt); -} - -TEST(StringUtils, readAs_int) -{ - EXPECT_EQ(StringUtils::readAs("1024"), 1024); - EXPECT_EQ(StringUtils::readAs("0"), 0); - EXPECT_EQ(StringUtils::readAs("-0"), 0); - EXPECT_EQ(StringUtils::readAs("-1"), -1); - EXPECT_EQ(StringUtils::readAs(""), std::nullopt); - EXPECT_EQ(StringUtils::readAs("a"), std::nullopt); - EXPECT_EQ(StringUtils::readAs("-"), std::nullopt); - EXPECT_EQ(StringUtils::readAs("1024-1"), 1024); - EXPECT_EQ(StringUtils::readAs("1024-"), 1024); - EXPECT_EQ(StringUtils::readAs("1024/5"), 1024); - EXPECT_EQ(StringUtils::readAs("1024a"), 1024); - EXPECT_EQ(StringUtils::readAs("a1024a"), std::nullopt); -} - -TEST(StringUtils, capitalize) -{ - struct TestCase - { - std::string input; - std::string expectedOutput; - }; - - TestCase tests[] - { - {"", ""}, - {"C", "C"}, - {"c", "C"}, - {" c", " C"}, - {" cc", " Cc"}, - {"(c", "(c"}, - {"1c", "1c"}, - {"&c", "&c"}, - {"c c", "C c"} - }; - - for (const TestCase& test : tests) - { - std::string str{ test.input }; - StringUtils::capitalize(str); - EXPECT_EQ(str, test.expectedOutput) << " str was '" << test.input << "'"; - } -} - -TEST(Stringutils, date) -{ - const Wt::WDate date{ 2020, 01, 03 }; - EXPECT_EQ(StringUtils::toISO8601String(date), "2020-01-03"); -} - -TEST(Stringutils, dateTime) -{ - const Wt::WDateTime dateTime{ Wt::WDate {2020, 01, 03 }, Wt::WTime{9, 8, 11, 75} }; - EXPECT_EQ(StringUtils::toISO8601String(dateTime), "2020-01-03T09:08:11.075"); -} - -TEST(StringUtils, stringEndsWith) -{ - EXPECT_TRUE(StringUtils::stringEndsWith("FooBar", "Bar")); - EXPECT_TRUE(StringUtils::stringEndsWith("FooBar", "")); - EXPECT_TRUE(StringUtils::stringEndsWith("", "")); - EXPECT_TRUE(StringUtils::stringEndsWith("FooBar", "ar")); - EXPECT_TRUE(StringUtils::stringEndsWith("FooBar", "FooBar")); - EXPECT_FALSE(StringUtils::stringEndsWith("FooBar", "1FooBar")); - EXPECT_FALSE(StringUtils::stringEndsWith("FooBar", "1FooBar")); - EXPECT_FALSE(StringUtils::stringEndsWith("FooBar", "R")); -} \ No newline at end of file diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index 6b926f17..b9754984 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -67,7 +67,7 @@ target_link_libraries(lms PRIVATE lmsscrobbling lmsservice-cover lmssubsonic - lmsutils + lmscore Wt::Wt Wt::HTTP ) diff --git a/src/lms/main.cpp b/src/lms/main.cpp index f1bc70cc..ed32dba8 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -40,85 +40,85 @@ #include "subsonic/SubsonicResource.hpp" #include "ui/LmsApplication.hpp" #include "ui/LmsApplicationManager.hpp" -#include "utils/IChildProcessManager.hpp" -#include "utils/IConfig.hpp" -#include "utils/IOContextRunner.hpp" -#include "utils/ITraceLogger.hpp" -#include "utils/Service.hpp" -#include "utils/String.hpp" -#include "utils/WtLogger.hpp" +#include "core/IChildProcessManager.hpp" +#include "core/IConfig.hpp" +#include "core/IOContextRunner.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" +#include "core/WtLogger.hpp" -namespace +namespace lms { std::size_t getThreadCount() { - const unsigned long configHttpServerThreadCount{ Service::get()->getULong("http-server-thread-count", 0) }; + const unsigned long configHttpServerThreadCount{ core::Service::get()->getULong("http-server-thread-count", 0) }; // Reserve at least 2 threads since we still have some blocking IO (for example when reading from ffmpeg) return configHttpServerThreadCount ? configHttpServerThreadCount : std::max(2, std::thread::hardware_concurrency()); } - Severity getLogMinSeverity() + core::logging::Severity getLogMinSeverity() { - std::string_view minSeverity{ Service::get()->getString("log-min-severity", "info") }; + std::string_view minSeverity{ core::Service::get()->getString("log-min-severity", "info") }; if (minSeverity == "debug") - return Severity::DEBUG; + return core::logging::Severity::DEBUG; else if (minSeverity == "info") - return Severity::INFO; + return core::logging::Severity::INFO; else if (minSeverity == "warning") - return Severity::WARNING; + return core::logging::Severity::WARNING; else if (minSeverity == "error") - return Severity::ERROR; + return core::logging::Severity::ERROR; else if (minSeverity == "fatal") - return Severity::FATAL; + return core::logging::Severity::FATAL; - throw LmsException{ "Invalid config value for 'log-min-severity'" }; + throw core::LmsException{ "Invalid config value for 'log-min-severity'" }; } - std::optional getTracingLevel() + std::optional getTracingLevel() { - std::string_view tracingLevel{ Service::get()->getString("tracing-level", "disabled") }; + std::string_view tracingLevel{ core::Service::get()->getString("tracing-level", "disabled") }; if (tracingLevel == "disabled") return std::nullopt; else if (tracingLevel == "overview") - return tracing::Level::Overview; + return core::tracing::Level::Overview; else if (tracingLevel == "detailed") - return tracing::Level::Detailed; + return core::tracing::Level::Detailed; - throw LmsException{ "Invalid config value for 'tracing-level'" }; + throw core::LmsException{ "Invalid config value for 'tracing-level'" }; } - std::vector generateWtConfig(std::string execPath, Severity minSeverity) + std::vector generateWtConfig(std::string execPath, core::logging::Severity minSeverity) { std::vector args; - const std::filesystem::path wtConfigPath{ Service::get()->getPath("working-dir") / "wt_config.xml" }; - const std::filesystem::path wtLogFilePath{ Service::get()->getPath("log-file", "/var/log/lms.log") }; - const std::filesystem::path wtAccessLogFilePath{ Service::get()->getPath("access-log-file", "/var/log/lms.access.log") }; - const std::filesystem::path wtResourcesPath{ Service::get()->getPath("wt-resources", "/usr/share/Wt/resources") }; + const std::filesystem::path wtConfigPath{ core::Service::get()->getPath("working-dir") / "wt_config.xml" }; + const std::filesystem::path wtLogFilePath{ core::Service::get()->getPath("log-file", "/var/log/lms.log") }; + const std::filesystem::path wtAccessLogFilePath{ core::Service::get()->getPath("access-log-file", "/var/log/lms.access.log") }; + const std::filesystem::path wtResourcesPath{ core::Service::get()->getPath("wt-resources", "/usr/share/Wt/resources") }; args.push_back(execPath); args.push_back("--config=" + wtConfigPath.string()); - args.push_back("--docroot=" + std::string{ Service::get()->getString("docroot") }); - args.push_back("--approot=" + std::string{ Service::get()->getString("approot") }); - args.push_back("--deploy-path=" + std::string{ Service::get()->getString("deploy-path", "/") }); + args.push_back("--docroot=" + std::string{ core::Service::get()->getString("docroot") }); + args.push_back("--approot=" + std::string{ core::Service::get()->getString("approot") }); + args.push_back("--deploy-path=" + std::string{ core::Service::get()->getString("deploy-path", "/") }); if (!wtResourcesPath.empty()) args.push_back("--resources-dir=" + wtResourcesPath.string()); - if (Service::get()->getBool("tls-enable", false)) + if (core::Service::get()->getBool("tls-enable", false)) { - args.push_back("--https-port=" + std::to_string(Service::get()->getULong("listen-port", 5082))); - args.push_back("--https-address=" + std::string{ Service::get()->getString("listen-addr", "0.0.0.0") }); - args.push_back("--ssl-certificate=" + std::string{ Service::get()->getString("tls-cert") }); - args.push_back("--ssl-private-key=" + std::string{ Service::get()->getString("tls-key") }); - args.push_back("--ssl-tmp-dh=" + std::string{ Service::get()->getString("tls-dh") }); + args.push_back("--https-port=" + std::to_string(core::Service::get()->getULong("listen-port", 5082))); + args.push_back("--https-address=" + std::string{ core::Service::get()->getString("listen-addr", "0.0.0.0") }); + args.push_back("--ssl-certificate=" + std::string{ core::Service::get()->getString("tls-cert") }); + args.push_back("--ssl-private-key=" + std::string{ core::Service::get()->getString("tls-key") }); + args.push_back("--ssl-tmp-dh=" + std::string{ core::Service::get()->getString("tls-dh") }); } else { - args.push_back("--http-port=" + std::to_string(Service::get()->getULong("listen-port", 5082))); - args.push_back("--http-address=" + std::string{ Service::get()->getString("listen-addr", "0.0.0.0") }); + args.push_back("--http-port=" + std::to_string(core::Service::get()->getULong("listen-port", 5082))); + args.push_back("--http-address=" + std::string{ core::Service::get()->getString("listen-addr", "0.0.0.0") }); } if (!wtAccessLogFilePath.empty()) @@ -133,8 +133,8 @@ namespace pt.put("server.application-settings.log-file", wtLogFilePath.string()); // log-config - pt.put("server.application-settings.log-config", WtLogger::computeLogConfig(minSeverity)); - pt.put("server.application-settings.behind-reverse-proxy", Service::get()->getBool("behind-reverse-proxy", false)); + pt.put("server.application-settings.log-config", core::logging::WtLogger::computeLogConfig(minSeverity)); + pt.put("server.application-settings.behind-reverse-proxy", core::Service::get()->getBool("behind-reverse-proxy", false)); { boost::property_tree::ptree viewport; @@ -153,18 +153,18 @@ namespace { std::ofstream oss{ wtConfigPath.string().c_str(), std::ios::out }; if (!oss) - throw LmsException{ "Can't open '" + wtConfigPath.string() + "' for writing!" }; + throw core::LmsException{ "Can't open '" + wtConfigPath.string() + "' for writing!" }; boost::property_tree::xml_parser::write_xml(oss, pt); if (!oss) - throw LmsException{ "Can't write in file '" + wtConfigPath.string() + "', no space left?" }; + throw core::LmsException{ "Can't write in file '" + wtConfigPath.string() + "', no space left?" }; } return args; } - void proxyScannerEventsToApplication(Scanner::IScannerService& scanner, Wt::WServer& server) + void proxyScannerEventsToApplication(scanner::IScannerService& scanner, Wt::WServer& server) { auto postAll{ [](Wt::WServer& server, std::function cb) { @@ -194,7 +194,7 @@ namespace }); }); - scanner.getEvents().scanComplete.connect([&](const Scanner::ScanStats& stats) + scanner.getEvents().scanComplete.connect([&](const scanner::ScanStats& stats) { postAll(server, [=] { @@ -203,7 +203,7 @@ namespace }); }); - scanner.getEvents().scanInProgress.connect([&](const Scanner::ScanStepStats& stats) + scanner.getEvents().scanInProgress.connect([&](const scanner::ScanStepStats& stats) { postAll(server, [=] { @@ -221,154 +221,158 @@ namespace }); }); } + + int main(int argc, char* argv[]) + { + std::filesystem::path configFilePath{ "/etc/lms.conf" }; + int res{ EXIT_FAILURE }; + + assert(argc > 0); + assert(argv[0] != NULL); + + if (argc == 2) + configFilePath = std::string(argv[1], 0, 256); + else if (argc > 2) + { + std::cerr << "Usage:\t" << argv[0] << "\t[conf_file]\n\n" + << "Options:\n" + << "\tconf_file:\t path to the LMS configuration file (defaults to " << configFilePath << ")\n\n"; + return EXIT_FAILURE; + } + + try + { + close(STDIN_FILENO); + + core::Service config{ core::createConfig(configFilePath) }; + const core::logging::Severity minLogSeverity{ getLogMinSeverity() }; + core::Service logger{ std::make_unique(minLogSeverity) }; + core::Service traceLogger; + if (const auto level{ getTracingLevel() }) + traceLogger.assign(core::tracing::createTraceLogger(level.value(), config->getULong("tracing-buffer-size", core::tracing::MinBufferSizeInMBytes))); + + // use system locale. libarchive relies on this to write filenames + if (char* locale{ ::setlocale(LC_ALL, "") }) + LMS_LOG(MAIN, INFO, "locale set to '" << locale << "'"); + else + LMS_LOG(MAIN, WARNING, "Cannot set locale from system"); + + // Make sure the working directory exists + std::filesystem::create_directories(config->getPath("working-dir")); + std::filesystem::create_directories(config->getPath("working-dir") / "cache"); + + // Construct WT configuration and get the argc/argv back + const std::vector wtServerArgs{ generateWtConfig(argv[0], minLogSeverity) }; + + std::vector wtArgv(wtServerArgs.size()); + for (std::size_t i = 0; i < wtServerArgs.size(); ++i) + { + std::cout << "ARG = " << wtServerArgs[i] << std::endl; + wtArgv[i] = wtServerArgs[i].c_str(); + } + + boost::asio::io_context ioContext; // ioContext used to dispatch all the services that are out of the Wt event loop + Wt::WServer server{ argv[0] }; + server.setServerConfiguration(wtServerArgs.size(), const_cast(&wtArgv[0])); + + core::IOContextRunner ioContextRunner{ ioContext, getThreadCount(), "Misc" }; + + // Connection pool size must be twice the number of threads: we have at least 2 io pools with getThreadCount() each and they all may access the database + db::Db database{ config->getPath("working-dir") / "lms.db", getThreadCount() * 2 }; + { + db::Session session{ database }; + session.prepareTables(); + + // force optimize in case scanner aborted during a large import: + // queries may be too slow to even be able to relaunch a scan sing the web interface + session.analyze(); + } + + ui::LmsApplicationManager appManager; + + // Service initialization order is important (reverse-order for deinit) + core::Service childProcessManagerService{ core::createChildProcessManager(ioContext) }; + core::Service authTokenService; + core::Service authPasswordService; + core::Service authEnvService; + + const std::string authenticationBackend{ core::stringUtils::stringToLower(config->getString("authentication-backend", "internal")) }; + if (authenticationBackend == "internal" || authenticationBackend == "pam") + { + authTokenService.assign(auth::createAuthTokenService(database, config->getULong("login-throttler-max-entriees", 10000))); + authPasswordService.assign(auth::createPasswordService(authenticationBackend, database, config->getULong("login-throttler-max-entriees", 10000), *authTokenService.get())); + } + else if (authenticationBackend == "http-headers") + { + authEnvService.assign(auth::createEnvService(authenticationBackend, database)); + } + else + throw core::LmsException{ "Bad value '" + authenticationBackend + "' for 'authentication-backend'" }; + + image::init(argv[0]); + core::Service coverService{ cover::createCoverService(database, argv[0], server.appRoot() + "/images/unknown-cover.jpg") }; + core::Service recommendationService{ recommendation::createRecommendationService(database) }; + core::Service playlistGeneratorService{ recommendation::createPlaylistGeneratorService(database, *recommendationService.get()) }; + core::Service scannerService{ scanner::createScannerService(database) }; + + scannerService->getEvents().scanComplete.connect([&] + { + // Flush cover cache even if no changes: + // covers may be external files that changed and we don't keep track of them for now (but we should) + coverService->flushCache(); + }); + + core::Service feedbackService{ feedback::createFeedbackService(ioContext, database) }; + core::Service scrobblingService{ scrobbling::createScrobblingService(ioContext, database) }; + + std::unique_ptr subsonicResource; + + // bind API resources + if (config->getBool("api-subsonic", true)) + { + subsonicResource = api::subsonic::createSubsonicResource(database); + server.addResource(subsonicResource.get(), "/rest"); + } + + // bind UI entry point + server.addEntryPoint(Wt::EntryPointType::Application, + [&](const Wt::WEnvironment& env) + { + return ui::LmsApplication::create(env, database, appManager); + }); + + proxyScannerEventsToApplication(*scannerService, server); + + LMS_LOG(MAIN, INFO, "Starting server..."); + server.start(); + + LMS_LOG(MAIN, INFO, "Now running..."); + Wt::WServer::waitForShutdown(); + + LMS_LOG(MAIN, INFO, "Stopping server..."); + server.stop(); + + LMS_LOG(MAIN, INFO, "Quitting..."); + res = EXIT_SUCCESS; + } + catch (const Wt::WServer::Exception& e) + { + LMS_LOG(MAIN, FATAL, "Caught WServer::Exception: " << e.what()); + std::cerr << "Caught a WServer::Exception: " << e.what() << std::endl; + res = EXIT_FAILURE; + } + catch (const std::exception& e) + { + LMS_LOG(MAIN, FATAL, "Caught std::exception: " << e.what()); + std::cerr << "Caught std::exception: " << e.what() << std::endl; + res = EXIT_FAILURE; + } + + return res; + } } int main(int argc, char* argv[]) { - std::filesystem::path configFilePath{ "/etc/lms.conf" }; - int res{ EXIT_FAILURE }; - - assert(argc > 0); - assert(argv[0] != NULL); - - if (argc == 2) - configFilePath = std::string(argv[1], 0, 256); - else if (argc > 2) - { - std::cerr << "Usage:\t" << argv[0] << "\t[conf_file]\n\n" - << "Options:\n" - << "\tconf_file:\t path to the LMS configuration file (defaults to " << configFilePath << ")\n\n"; - return EXIT_FAILURE; - } - - try - { - close(STDIN_FILENO); - - Service config{ createConfig(configFilePath) }; - const Severity minLogSeverity{getLogMinSeverity()}; - Service logger{ std::make_unique(minLogSeverity) }; - std::optional> traceLogger; - if (const auto level{ getTracingLevel() }) - traceLogger.emplace(tracing::createTraceLogger(level.value(), config->getULong("tracing-buffer-size", tracing::MinBufferSizeInMBytes))); - - // use system locale. libarchive relies on this to write filenames - if (char* locale{ ::setlocale(LC_ALL, "") }) - LMS_LOG(MAIN, INFO, "locale set to '" << locale << "'"); - else - LMS_LOG(MAIN, WARNING, "Cannot set locale from system"); - - // Make sure the working directory exists - std::filesystem::create_directories(config->getPath("working-dir")); - std::filesystem::create_directories(config->getPath("working-dir") / "cache"); - - // Construct WT configuration and get the argc/argv back - const std::vector wtServerArgs{ generateWtConfig(argv[0], minLogSeverity) }; - - std::vector wtArgv(wtServerArgs.size()); - for (std::size_t i = 0; i < wtServerArgs.size(); ++i) - { - std::cout << "ARG = " << wtServerArgs[i] << std::endl; - wtArgv[i] = wtServerArgs[i].c_str(); - } - - boost::asio::io_context ioContext; // ioContext used to dispatch all the services that are out of the Wt event loop - Wt::WServer server{ argv[0] }; - server.setServerConfiguration(wtServerArgs.size(), const_cast(&wtArgv[0])); - - IOContextRunner ioContextRunner{ ioContext, getThreadCount(), "Misc" }; - - // Connection pool size must be twice the number of threads: we have at least 2 io pools with getThreadCount() each and they all may access the database - Database::Db database{ config->getPath("working-dir") / "lms.db", getThreadCount() * 2 }; - { - Database::Session session{ database }; - session.prepareTables(); - - // force optimize in case scanner aborted during a large import: - // queries may be too slow to even be able to relaunch a scan sing the web interface - session.analyze(); - } - - UserInterface::LmsApplicationManager appManager; - - // Service initialization order is important (reverse-order for deinit) - Service childProcessManagerService{ createChildProcessManager(ioContext) }; - Service authTokenService; - Service authPasswordService; - Service authEnvService; - - const std::string authenticationBackend{ StringUtils::stringToLower(config->getString("authentication-backend", "internal")) }; - if (authenticationBackend == "internal" || authenticationBackend == "pam") - { - authTokenService.assign(Auth::createAuthTokenService(database, config->getULong("login-throttler-max-entriees", 10000))); - authPasswordService.assign(Auth::createPasswordService(authenticationBackend, database, config->getULong("login-throttler-max-entriees", 10000), *authTokenService.get())); - } - else if (authenticationBackend == "http-headers") - { - authEnvService.assign(Auth::createEnvService(authenticationBackend, database)); - } - else - throw LmsException{ "Bad value '" + authenticationBackend + "' for 'authentication-backend'" }; - - Image::init(argv[0]); - Service coverService{ Cover::createCoverService(database, argv[0], server.appRoot() + "/images/unknown-cover.jpg") }; - Service recommendationService{ Recommendation::createRecommendationService(database) }; - Service playlistGeneratorService{ Recommendation::createPlaylistGeneratorService(database, *recommendationService.get()) }; - Service scannerService{ Scanner::createScannerService(database) }; - - scannerService->getEvents().scanComplete.connect([&] - { - // Flush cover cache even if no changes: - // covers may be external files that changed and we don't keep track of them for now (but we should) - coverService->flushCache(); - }); - - Service feedbackService{ Feedback::createFeedbackService(ioContext, database) }; - Service scrobblingService{ Scrobbling::createScrobblingService(ioContext, database) }; - - std::unique_ptr subsonicResource; - - // bind API resources - if (config->getBool("api-subsonic", true)) - { - subsonicResource = API::Subsonic::createSubsonicResource(database); - server.addResource(subsonicResource.get(), "/rest"); - } - - // bind UI entry point - server.addEntryPoint(Wt::EntryPointType::Application, - [&](const Wt::WEnvironment& env) - { - return UserInterface::LmsApplication::create(env, database, appManager); - }); - - proxyScannerEventsToApplication(*scannerService, server); - - LMS_LOG(MAIN, INFO, "Starting server..."); - server.start(); - - LMS_LOG(MAIN, INFO, "Now running..."); - Wt::WServer::waitForShutdown(); - - LMS_LOG(MAIN, INFO, "Stopping server..."); - server.stop(); - - LMS_LOG(MAIN, INFO, "Quitting..."); - res = EXIT_SUCCESS; - } - catch (const Wt::WServer::Exception& e) - { - LMS_LOG(MAIN, FATAL, "Caught WServer::Exception: " << e.what()); - std::cerr << "Caught a WServer::Exception: " << e.what() << std::endl; - res = EXIT_FAILURE; - } - catch (const std::exception& e) - { - LMS_LOG(MAIN, FATAL, "Caught std::exception: " << e.what()); - std::cerr << "Caught std::exception: " << e.what() << std::endl; - res = EXIT_FAILURE; - } - - return res; + return lms::main(argc, argv); } - diff --git a/src/lms/ui/Auth.cpp b/src/lms/ui/Auth.cpp index afeda877..8adea685 100644 --- a/src/lms/ui/Auth.cpp +++ b/src/lms/ui/Auth.cpp @@ -19,8 +19,6 @@ #include "Auth.hpp" -#include - #include #include #include @@ -32,189 +30,186 @@ #include "services/auth/IPasswordService.hpp" #include "database/Session.hpp" #include "database/User.hpp" -#include "utils/ILogger.hpp" -#include "utils/Service.hpp" +#include "core/ILogger.hpp" +#include "core/Service.hpp" #include "common/LoginNameValidator.hpp" #include "common/MandatoryValidator.hpp" #include "common/PasswordValidator.hpp" #include "LmsApplication.hpp" -namespace UserInterface +namespace lms::ui { + namespace + { + static const std::string authCookieName{ "LmsAuth" }; -static const std::string authCookieName {"LmsAuth"}; + void createAuthToken(db::UserId userId, const Wt::WDateTime& expiry) + { + const std::string secret{ core::Service::get()->createAuthToken(userId, expiry) }; -static -void -createAuthToken(Database::UserId userId, const Wt::WDateTime& expiry) -{ - const std::string secret {Service<::Auth::IAuthTokenService>::get()->createAuthToken(userId, expiry)}; + LmsApp->setCookie(authCookieName, + secret, + expiry.toTime_t() - Wt::WDateTime::currentDateTime().toTime_t(), + "", + "", + LmsApp->environment().urlScheme() == "https"); + } - LmsApp->setCookie(authCookieName, - secret, - expiry.toTime_t() - Wt::WDateTime::currentDateTime().toTime_t(), - "", - "", - LmsApp->environment().urlScheme() == "https"); + class AuthModel : public Wt::WFormModel + { + public: + // Associate each field with a unique string literal. + static const Field LoginNameField; + static const Field PasswordField; + static const Field RememberMeField; + + AuthModel() + { + addField(LoginNameField); + addField(PasswordField); + addField(RememberMeField); + + setValidator(LoginNameField, createLoginNameValidator()); + setValidator(PasswordField, createMandatoryValidator()); + } + + + void saveData() + { + bool isDemo; + { + auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; + + db::User::pointer user{ db::User::find(LmsApp->getDbSession(), valueText(LoginNameField).toUTF8()) }; + user.modify()->setLastLogin(Wt::WDateTime::currentDateTime()); + _userId = user->getId(); + + isDemo = user->isDemo(); + } + + if (Wt::asNumber(value(RememberMeField))) + { + const Wt::WDateTime now{ Wt::WDateTime::currentDateTime() }; + + createAuthToken(*_userId, isDemo ? now.addDays(3) : now.addYears(1)); + } + } + + bool validateField(Field field) + { + Wt::WString error; + + if (field == PasswordField) + { + const auto checkResult{ core::Service::get()->checkUserPassword( + boost::asio::ip::address::from_string(LmsApp->environment().clientAddress()), + valueText(LoginNameField).toUTF8(), + valueText(PasswordField).toUTF8()) }; + switch (checkResult.state) + { + case auth::IPasswordService::CheckResult::State::Granted: + _userId = *checkResult.userId; + break; + case auth::IPasswordService::CheckResult::State::Denied: + error = Wt::WString::tr("Lms.password-bad-login-combination"); + break; + case auth::IPasswordService::CheckResult::State::Throttled: + error = Wt::WString::tr("Lms.password-client-throttled"); + break; + } + } + else + { + return Wt::WFormModel::validateField(field); + } + + setValidation(field, Wt::WValidator::Result(error.empty() ? Wt::ValidationState::Valid : Wt::ValidationState::Invalid, error)); + + return (validation(field).state() == Wt::ValidationState::Valid); + } + + std::optional getUserId() const { return _userId; } + + private: + std::optional _userId; + }; + + const AuthModel::Field AuthModel::LoginNameField{ "login-name" }; + const AuthModel::Field AuthModel::PasswordField{ "password" }; + const AuthModel::Field AuthModel::RememberMeField{ "remember-me" }; + } + + std::optional processAuthToken(const Wt::WEnvironment& env) + { + const std::string* authCookie{ env.getCookie(authCookieName) }; + if (!authCookie) + return std::nullopt; + + const auto res{ core::Service::get()->processAuthToken(boost::asio::ip::address::from_string(env.clientAddress()), *authCookie) }; + switch (res.state) + { + case auth::IAuthTokenService::AuthTokenProcessResult::State::Denied: + case auth::IAuthTokenService::AuthTokenProcessResult::State::Throttled: + LmsApp->setCookie(authCookieName, std::string{}, 0, "", "", env.urlScheme() == "https"); + return std::nullopt; + + case auth::IAuthTokenService::AuthTokenProcessResult::State::Granted: + createAuthToken(res.authTokenInfo->userId, res.authTokenInfo->expiry); + break; + } + + return res.authTokenInfo->userId; + } + + + Auth::Auth() + : Wt::WTemplateFormView{ Wt::WString::tr("Lms.Auth.template") } + { + auto model{ std::make_shared() }; + + auto processAuth = [this, model] + { + updateModel(model.get()); + + if (model->validate()) + { + model->saveData(); + userLoggedIn.emit(*model->getUserId()); + } + else + updateView(model.get()); + }; + + // LoginName + auto loginName{ std::make_unique() }; + loginName->setAttributeValue("autocomplete", "username"); + setFormWidget(AuthModel::LoginNameField, std::move(loginName)); + + // Password + auto password = std::make_unique(); + password->setEchoMode(Wt::EchoMode::Password); + password->enterPressed().connect(this, processAuth); + password->setAttributeValue("autocomplete", "current-password"); + setFormWidget(AuthModel::PasswordField, std::move(password)); + + // Remember me + setFormWidget(AuthModel::RememberMeField, std::make_unique()); + + { + auto transaction{ LmsApp->getDbSession().createReadTransaction() }; + + db::User::pointer demoUser{ db::User::findDemoUser(LmsApp->getDbSession()) }; + if (demoUser) + { + model->setValue(AuthModel::LoginNameField, demoUser->getLoginName()); + model->setValue(AuthModel::PasswordField, demoUser->getLoginName()); + } + } + + Wt::WPushButton* loginBtn{ bindNew("login-btn", Wt::WString::tr("Lms.login")) }; + loginBtn->clicked().connect(this, processAuth); + + updateView(model.get()); + } } - - -std::optional -processAuthToken(const Wt::WEnvironment& env) -{ - const std::string* authCookie {env.getCookie(authCookieName)}; - if (!authCookie) - return std::nullopt; - - const auto res {Service<::Auth::IAuthTokenService>::get()->processAuthToken(boost::asio::ip::address::from_string(env.clientAddress()), *authCookie)}; - switch (res.state) - { - case ::Auth::IAuthTokenService::AuthTokenProcessResult::State::Denied: - case ::Auth::IAuthTokenService::AuthTokenProcessResult::State::Throttled: - LmsApp->setCookie(authCookieName, std::string {}, 0, "", "", env.urlScheme() == "https"); - return std::nullopt; - - case ::Auth::IAuthTokenService::AuthTokenProcessResult::State::Granted: - createAuthToken(res.authTokenInfo->userId, res.authTokenInfo->expiry); - break; - } - - return res.authTokenInfo->userId; -} - -class AuthModel : public Wt::WFormModel -{ - public: - // Associate each field with a unique string literal. - static const Field LoginNameField; - static const Field PasswordField; - static const Field RememberMeField; - - AuthModel() - { - addField(LoginNameField); - addField(PasswordField); - addField(RememberMeField); - - setValidator(LoginNameField, createLoginNameValidator()); - setValidator(PasswordField, createMandatoryValidator()); - } - - - void saveData() - { - bool isDemo; - { - auto transaction {LmsApp->getDbSession().createWriteTransaction()}; - - Database::User::pointer user {Database::User::find(LmsApp->getDbSession(), valueText(LoginNameField).toUTF8())}; - user.modify()->setLastLogin(Wt::WDateTime::currentDateTime()); - _userId = user->getId(); - - isDemo = user->isDemo(); - } - - if (Wt::asNumber(value(RememberMeField))) - { - const Wt::WDateTime now {Wt::WDateTime::currentDateTime()}; - - createAuthToken(*_userId, isDemo ? now.addDays(3) : now.addYears(1)); - } - } - - bool validateField(Field field) - { - Wt::WString error; - - if (field == PasswordField) - { - const auto checkResult {Service<::Auth::IPasswordService>::get()->checkUserPassword( - boost::asio::ip::address::from_string(LmsApp->environment().clientAddress()), - valueText(LoginNameField).toUTF8(), - valueText(PasswordField).toUTF8())}; - switch (checkResult.state) - { - case ::Auth::IPasswordService::CheckResult::State::Granted: - _userId = *checkResult.userId; - break; - case ::Auth::IPasswordService::CheckResult::State::Denied: - error = Wt::WString::tr("Lms.password-bad-login-combination"); - break; - case ::Auth::IPasswordService::CheckResult::State::Throttled: - error = Wt::WString::tr("Lms.password-client-throttled"); - break; - } - } - else - { - return Wt::WFormModel::validateField(field); - } - - setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::ValidationState::Valid : Wt::ValidationState::Invalid, error)); - - return (validation(field).state() == Wt::ValidationState::Valid); - } - - std::optional getUserId() const { return _userId; } - - private: - std::optional _userId; -}; - -const AuthModel::Field AuthModel::LoginNameField {"login-name"}; -const AuthModel::Field AuthModel::PasswordField {"password"}; -const AuthModel::Field AuthModel::RememberMeField {"remember-me"}; - -Auth::Auth() -: Wt::WTemplateFormView {Wt::WString::tr("Lms.Auth.template")} -{ - auto model {std::make_shared()}; - - auto processAuth = [this, model] - { - updateModel(model.get()); - - if (model->validate()) - { - model->saveData(); - userLoggedIn.emit(*model->getUserId()); - } - else - updateView(model.get()); - }; - - // LoginName - auto loginName {std::make_unique()}; - loginName->setAttributeValue("autocomplete", "username"); - setFormWidget(AuthModel::LoginNameField, std::move(loginName)); - - // Password - auto password = std::make_unique(); - password->setEchoMode(Wt::EchoMode::Password); - password->enterPressed().connect(this, processAuth); - password->setAttributeValue("autocomplete", "current-password"); - setFormWidget(AuthModel::PasswordField, std::move(password)); - - // Remember me - setFormWidget(AuthModel::RememberMeField, std::make_unique()); - - { - auto transaction {LmsApp->getDbSession().createReadTransaction()}; - - Database::User::pointer demoUser {Database::User::findDemoUser(LmsApp->getDbSession())}; - if (demoUser) - { - model->setValue(AuthModel::LoginNameField, demoUser->getLoginName()); - model->setValue(AuthModel::PasswordField, demoUser->getLoginName()); - } - } - - Wt::WPushButton* loginBtn {bindNew("login-btn", Wt::WString::tr("Lms.login"))}; - loginBtn->clicked().connect(this, processAuth); - - updateView(model.get()); -} - -} // namespace UserInterface - diff --git a/src/lms/ui/Auth.hpp b/src/lms/ui/Auth.hpp index 4463c3af..8a549ca3 100644 --- a/src/lms/ui/Auth.hpp +++ b/src/lms/ui/Auth.hpp @@ -24,18 +24,15 @@ #include "database/UserId.hpp" -namespace UserInterface +namespace lms::ui { - - std::optional - processAuthToken(const Wt::WEnvironment& env); + std::optional processAuthToken(const Wt::WEnvironment& env); class Auth : public Wt::WTemplateFormView { public: Auth(); - Wt::Signal userLoggedIn; + Wt::Signal userLoggedIn; }; - -} // namespace UserInterface +} // namespace lms::ui diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index e9704765..f6268ac7 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -37,10 +37,10 @@ #include "database/TrackList.hpp" #include "database/User.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "utils/ILogger.hpp" -#include "utils/ITraceLogger.hpp" -#include "utils/Service.hpp" -#include "utils/String.hpp" +#include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "admin/InitWizardView.hpp" #include "admin/MediaLibrariesView.hpp" @@ -65,7 +65,7 @@ #include "PlayQueue.hpp" #include "SettingsView.hpp" -namespace UserInterface +namespace lms::ui { namespace { @@ -175,12 +175,12 @@ namespace UserInterface } } - std::unique_ptr LmsApplication::create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationManager& appManager) + std::unique_ptr LmsApplication::create(const Wt::WEnvironment& env, db::Db& db, LmsApplicationManager& appManager) { - if (auto * authEnvService{ Service<::Auth::IEnvService>::get() }) + if (auto * authEnvService{ core::Service::get() }) { const auto checkResult{ authEnvService->processEnv(env) }; - if (checkResult.state != ::Auth::IEnvService::CheckResult::State::Granted) + if (checkResult.state != auth::IEnvService::CheckResult::State::Granted) { LMS_LOG(UI, ERROR, "Cannot authenticate user from environment!"); // return a blank page @@ -198,25 +198,25 @@ namespace UserInterface return static_cast(Wt::WApplication::instance()); } - Database::Db& LmsApplication::getDb() + db::Db& LmsApplication::getDb() { return _db; } - Database::Session& LmsApplication::getDbSession() + db::Session& LmsApplication::getDbSession() { return _db.getTLSSession(); } - Database::User::pointer LmsApplication::getUser() + db::User::pointer LmsApplication::getUser() { if (!_authenticatedUser) return {}; - return Database::User::find(getDbSession(), _authenticatedUser->userId); + return db::User::find(getDbSession(), _authenticatedUser->userId); } - Database::UserId LmsApplication::getUserId() + db::UserId LmsApplication::getUserId() { return _authenticatedUser->userId; } @@ -226,7 +226,7 @@ namespace UserInterface return _authenticatedUser->strongAuth; } - Database::UserType LmsApplication::getUserType() + db::UserType LmsApplication::getUserType() { auto transaction{ getDbSession().createReadTransaction() }; @@ -241,9 +241,9 @@ namespace UserInterface } LmsApplication::LmsApplication(const Wt::WEnvironment& env, - Database::Db& db, + db::Db& db, LmsApplicationManager& appManager, - std::optional userId) + std::optional userId) : Wt::WApplication{ env } , _db{ db } , _appManager{ appManager } @@ -261,7 +261,7 @@ namespace UserInterface catch (std::exception& e) { LMS_LOG(UI, ERROR, "Caught exception: " << e.what()); - throw LmsException{ "Internal error" }; // Do not put details here at it may appear on the user rendered html + throw core::LmsException{ "Internal error" }; // Do not put details here at it may appear on the user rendered html } } @@ -284,14 +284,14 @@ namespace UserInterface if (_authenticatedUser) onUserLoggedIn(); - else if (Service<::Auth::IPasswordService>::exists()) + else if (core::Service::exists()) processPasswordAuth(); } void LmsApplication::processPasswordAuth() { { - std::optional userId{ processAuthToken(environment()) }; + std::optional userId{ processAuthToken(environment()) }; if (userId) { LMS_LOG(UI, DEBUG, "User authenticated using Auth token!"); @@ -305,19 +305,19 @@ namespace UserInterface bool firstConnection{}; { auto transaction{ getDbSession().createReadTransaction() }; - firstConnection = Database::User::getCount(getDbSession()) == 0; + firstConnection = db::User::getCount(getDbSession()) == 0; } LMS_LOG(UI, DEBUG, "Creating root widget. First connection = " << firstConnection); - if (firstConnection && Service<::Auth::IPasswordService>::get()->canSetPasswords()) + if (firstConnection && core::Service::get()->canSetPasswords()) { root()->addWidget(std::make_unique()); } else { Auth* auth{ root()->addNew() }; - auth->userLoggedIn.connect(this, [this](Database::UserId userId) + auth->userLoggedIn.connect(this, [this](db::UserId userId) { _authenticatedUser = { userId, true }; onUserLoggedIn(); @@ -376,7 +376,7 @@ namespace UserInterface // Only one active session by user if (otherApplication.getUserId() == getUserId()) { - if (LmsApp->getUserType() != Database::UserType::DEMO) + if (LmsApp->getUserType() != db::UserType::DEMO) { quit(Wt::WString::tr("Lms.quit-other-session")); } @@ -438,7 +438,7 @@ namespace UserInterface Wt::WLineEdit* searchEdit{ navbar->bindNew("search") }; searchEdit->setPlaceholderText(Wt::WString::tr("Lms.Explore.Search.search-placeholder")); - if (LmsApp->getUserType() == Database::UserType::ADMIN) + if (LmsApp->getUserType() == db::UserType::ADMIN) { navbar->setCondition("if-is-admin", true); navbar->bindNew("media-libraries", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/libraries" }, Wt::WString::tr("Lms.Admin.menu-media-libraries")); @@ -446,7 +446,7 @@ namespace UserInterface navbar->bindNew("scanner", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/scanner" }, Wt::WString::tr("Lms.Admin.menu-scanner")); navbar->bindNew("users", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/users" }, Wt::WString::tr("Lms.Admin.menu-users")); // Hide the entry if the trace logger is not enabled - if (Service<::tracing::ITraceLogger>::get()) + if (core::Service::get()) navbar->bindNew("tracing", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/tracing" }, Wt::WString::tr("Lms.Admin.menu-tracing")); else navbar->bindEmpty("tracing"); @@ -474,7 +474,7 @@ namespace UserInterface }); // Admin stuff - if (getUserType() == Database::UserType::ADMIN) + if (getUserType() == db::UserType::ADMIN) { mainStack->addNew(); mainStack->addNew(); @@ -496,18 +496,18 @@ namespace UserInterface _playQueue->playPrevious(); }); - _mediaPlayer->scrobbleListenNow.connect([this](Database::TrackId trackId) + _mediaPlayer->scrobbleListenNow.connect([this](db::TrackId trackId) { LMS_LOG(UI, DEBUG, "Received ScrobbleListenNow from player for trackId = " << trackId.toString()); - const Scrobbling::Listen listen{ getUserId(), trackId }; - Service::get()->listenStarted(listen); + const scrobbling::Listen listen{ getUserId(), trackId }; + core::Service::get()->listenStarted(listen); }); - _mediaPlayer->scrobbleListenFinished.connect([this](Database::TrackId trackId, unsigned durationMs) + _mediaPlayer->scrobbleListenFinished.connect([this](db::TrackId trackId, unsigned durationMs) { LMS_LOG(UI, DEBUG, "Received ScrobbleListenFinished from player for trackId = " << trackId.toString() << ", duration = " << (durationMs / 1000) << "s"); const std::chrono::milliseconds duration{ durationMs }; - const Scrobbling::Listen listen{ getUserId(), trackId }; - Service::get()->listenFinished(listen, std::chrono::duration_cast(duration)); + const scrobbling::Listen listen{ getUserId(), trackId }; + core::Service::get()->listenFinished(listen, std::chrono::duration_cast(duration)); }); _mediaPlayer->playbackEnded.connect([this] @@ -515,7 +515,7 @@ namespace UserInterface _playQueue->onPlaybackEnded(); }); - _playQueue->trackSelected.connect([this](Database::TrackId trackId, bool play, float replayGain) + _playQueue->trackSelected.connect([this](db::TrackId trackId, bool play, float replayGain) { _mediaPlayer->loadTrack(trackId, play, replayGain); }); @@ -530,10 +530,10 @@ namespace UserInterface }); _mediaPlayer->onPlayQueueUpdated(_playQueue->getCount()); - const bool isAdmin{ getUserType() == Database::UserType::ADMIN }; + const bool isAdmin{ getUserType() == db::UserType::ADMIN }; if (isAdmin) { - _scannerEvents.scanComplete.connect([this](const Scanner::ScanStats& stats) + _scannerEvents.scanComplete.connect([this](const scanner::ScanStats& stats) { notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), @@ -570,7 +570,7 @@ namespace UserInterface catch (std::exception& e) { LMS_LOG(UI, ERROR, "Caught exception: " << e.what()); - throw LmsException{ "Internal error" }; // Do not put details here at it may appear on the user rendered html + throw core::LmsException{ "Internal error" }; // Do not put details here at it may appear on the user rendered html } } @@ -592,4 +592,4 @@ namespace UserInterface LMS_LOG(UI, INFO, "Notifying message '" << message.toUTF8() << "' for category '" << category.toUTF8() << "'"); _notificationContainer->add(type, category, message, duration); } -} // namespace UserInterface \ No newline at end of file +} // namespace lms::ui \ No newline at end of file diff --git a/src/lms/ui/LmsApplication.hpp b/src/lms/ui/LmsApplication.hpp index f902829c..68ab0d68 100644 --- a/src/lms/ui/LmsApplication.hpp +++ b/src/lms/ui/LmsApplication.hpp @@ -30,14 +30,14 @@ #include "admin/ScannerController.hpp" #include "Notification.hpp" -namespace Database +namespace lms::db { class Db; class Session; class User; } -namespace UserInterface +namespace lms::ui { class CoverResource; class LmsApplicationException; @@ -50,25 +50,25 @@ namespace UserInterface class LmsApplication : public Wt::WApplication { public: - LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationManager& appManager, std::optional userId = std::nullopt); + LmsApplication(const Wt::WEnvironment& env, db::Db& db, LmsApplicationManager& appManager, std::optional userId = std::nullopt); ~LmsApplication(); - static std::unique_ptr create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationManager& appManager); + static std::unique_ptr create(const Wt::WEnvironment& env, db::Db& db, LmsApplicationManager& appManager); static LmsApplication* instance(); // Session application data std::shared_ptr getCoverResource() { return _coverResource; } - Database::Db& getDb(); - Database::Session& getDbSession(); // always thread safe + db::Db& getDb(); + db::Session& getDbSession(); // always thread safe - Database::ObjectPtr getUser(); - Database::UserId getUserId(); + db::ObjectPtr getUser(); + db::UserId getUserId(); bool isUserAuthStrong() const; // user must be logged in prior this call - Database::UserType getUserType(); // user must be logged in prior this call + db::UserType getUserType(); // user must be logged in prior this call std::string getUserLoginName(); // user must be logged in prior this call // Proxified scanner events - Scanner::Events& getScannerEvents() { return _scannerEvents; } + scanner::Events& getScannerEvents() { return _scannerEvents; } // Utils void post(std::function func); @@ -99,13 +99,13 @@ namespace UserInterface void createHome(); - Database::Db& _db; + db::Db& _db; Wt::Signal<> _preQuit; LmsApplicationManager& _appManager; - Scanner::Events _scannerEvents; + scanner::Events _scannerEvents; struct UserAuthInfo { - Database::UserId userId; + db::UserId userId; bool strongAuth{}; }; std::optional _authenticatedUser; @@ -118,7 +118,7 @@ namespace UserInterface // Helper to get session instance -#define LmsApp ::UserInterface::LmsApplication::instance() +#define LmsApp lms::ui::LmsApplication::instance() -} // namespace UserInterface +} // namespace lms::ui diff --git a/src/lms/ui/LmsApplicationException.hpp b/src/lms/ui/LmsApplicationException.hpp index f6239507..8b07956b 100644 --- a/src/lms/ui/LmsApplicationException.hpp +++ b/src/lms/ui/LmsApplicationException.hpp @@ -20,11 +20,11 @@ #pragma once #include "database/Types.hpp" -#include "utils/Exception.hpp" +#include "core/Exception.hpp" -namespace UserInterface +namespace lms::ui { - class LmsApplicationException : public LmsException + class LmsApplicationException : public core::LmsException { public: LmsApplicationException(const Wt::WString& error) : LmsException {error.toUTF8()} {} diff --git a/src/lms/ui/LmsApplicationManager.cpp b/src/lms/ui/LmsApplicationManager.cpp index 44390e06..73546f16 100644 --- a/src/lms/ui/LmsApplicationManager.cpp +++ b/src/lms/ui/LmsApplicationManager.cpp @@ -21,7 +21,7 @@ #include "LmsApplication.hpp" -namespace UserInterface +namespace lms::ui { void LmsApplicationManager::registerApplication(LmsApplication& application) diff --git a/src/lms/ui/LmsApplicationManager.hpp b/src/lms/ui/LmsApplicationManager.hpp index aae40ff5..d9d760ca 100644 --- a/src/lms/ui/LmsApplicationManager.hpp +++ b/src/lms/ui/LmsApplicationManager.hpp @@ -27,7 +27,7 @@ #include "database/UserId.hpp" -namespace UserInterface +namespace lms::ui { class LmsApplication; class LmsApplicationManager @@ -43,6 +43,6 @@ namespace UserInterface void unregisterApplication(LmsApplication& application); std::mutex _mutex; - std::unordered_map> m_applications; + std::unordered_map> m_applications; }; } // UserInterface diff --git a/src/lms/ui/LmsTheme.cpp b/src/lms/ui/LmsTheme.cpp index c4b3bc43..f59a04a5 100644 --- a/src/lms/ui/LmsTheme.cpp +++ b/src/lms/ui/LmsTheme.cpp @@ -9,7 +9,7 @@ #include #include -namespace UserInterface +namespace lms::ui { void @@ -55,5 +55,5 @@ namespace UserInterface } } -} // namespace UserInterface +} // namespace lms::ui diff --git a/src/lms/ui/LmsTheme.hpp b/src/lms/ui/LmsTheme.hpp index 1a4aa420..93cb06a5 100644 --- a/src/lms/ui/LmsTheme.hpp +++ b/src/lms/ui/LmsTheme.hpp @@ -21,7 +21,7 @@ #include -namespace UserInterface +namespace lms::ui { class LmsTheme : public Wt::WTheme { diff --git a/src/lms/ui/MediaPlayer.cpp b/src/lms/ui/MediaPlayer.cpp index b1644471..9d744997 100644 --- a/src/lms/ui/MediaPlayer.cpp +++ b/src/lms/ui/MediaPlayer.cpp @@ -24,7 +24,7 @@ #include #include -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "database/Artist.hpp" #include "database/Release.hpp" @@ -38,13 +38,13 @@ #include "resource/AudioTranscodingResource.hpp" #include "resource/AudioFileResource.hpp" -#include "utils/String.hpp" -#include "utils/Utils.hpp" +#include "core/String.hpp" +#include "core/Utils.hpp" #include "LmsApplication.hpp" #include "Utils.hpp" -namespace UserInterface +namespace lms::ui { namespace { @@ -75,7 +75,7 @@ namespace UserInterface std::optional transcodingModeFromString(const std::string& str) { - const auto value{ StringUtils::readAs(str) }; + const auto value{ core::stringUtils::readAs(str) }; if (!value) return std::nullopt; @@ -93,7 +93,7 @@ namespace UserInterface std::optional formatFromString(const std::string& str) { - const auto value{ StringUtils::readAs(str) }; + const auto value{ core::stringUtils::readAs(str) }; if (!value) return std::nullopt; @@ -113,11 +113,11 @@ namespace UserInterface std::optional bitrateFromString(const std::string& str) { - const auto value{ StringUtils::readAs(str) }; + const auto value{ core::stringUtils::readAs(str) }; if (!value) return std::nullopt; - if (!Database::isAudioBitrateAllowed(*value)) + if (!db::isAudioBitrateAllowed(*value)) return std::nullopt; return *value; @@ -125,7 +125,7 @@ namespace UserInterface std::optional replayGainModeFromString(const std::string& str) { - const auto value{ StringUtils::readAs(str) }; + const auto value{ core::stringUtils::readAs(str) }; if (!value) return std::nullopt; @@ -144,11 +144,11 @@ namespace UserInterface std::optional replayGainPreAmpGainFromString(const std::string& str) { - const auto value{ StringUtils::readAs(str) }; + const auto value{ core::stringUtils::readAs(str) }; if (!value) return std::nullopt; - return ::Utils::clamp(*value, (double)MediaPlayer::Settings::ReplayGain::minPreAmpGain, (double)MediaPlayer::Settings::ReplayGain::maxPreAmpGain); + return core::utils::clamp(*value, (double)MediaPlayer::Settings::ReplayGain::minPreAmpGain, (double)MediaPlayer::Settings::ReplayGain::maxPreAmpGain); } MediaPlayer::Settings settingsfromJSString(const std::string& strSettings) @@ -231,7 +231,7 @@ namespace UserInterface } } - void MediaPlayer::loadTrack(Database::TrackId trackId, bool play, float replayGain) + void MediaPlayer::loadTrack(db::TrackId trackId, bool play, float replayGain) { LMS_LOG(UI, DEBUG, "Playing track ID = " << trackId.toString()); @@ -239,14 +239,14 @@ namespace UserInterface { auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - const auto track{ Database::Track::find(LmsApp->getDbSession(), trackId) }; + const auto track{ db::Track::find(LmsApp->getDbSession(), trackId) }; if (!track) return; const std::string transcodingResource{ _audioTranscodingResource->getUrl(trackId) }; const std::string nativeResource{ _audioFileResource->getUrl(trackId) }; - const auto artists{ track->getArtists({Database::TrackArtistLinkType::Artist}) }; + const auto artists{ track->getArtists({db::TrackArtistLinkType::Artist}) }; oss << "var params = {" @@ -255,9 +255,9 @@ namespace UserInterface << " transcodingResource: \"" << transcodingResource << "\"," << " duration: " << std::chrono::duration_cast(track->getDuration()).count() << "," << " replayGain: " << replayGain << "," - << " title: \"" << StringUtils::jsEscape(track->getName()) << "\"," - << " artist: \"" << (!artists.empty() ? StringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\"," - << " release: \"" << (track->getRelease() ? StringUtils::jsEscape(track->getRelease()->getName()) : "") << "\"," + << " title: \"" << core::stringUtils::jsEscape(track->getName()) << "\"," + << " artist: \"" << (!artists.empty() ? core::stringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\"," + << " release: \"" << (track->getRelease() ? core::stringUtils::jsEscape(track->getRelease()->getName()) : "") << "\"," << " artwork: [" << " { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" }," << " { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" }," @@ -277,7 +277,7 @@ namespace UserInterface { _artist->setTextFormat(Wt::TextFormat::Plain); _artist->setText(Wt::WString::fromUTF8(artists.front()->getName())); - _artist->setLink(Utils::createArtistLink(artists.front())); + _artist->setLink(utils::createArtistLink(artists.front())); } else { @@ -290,7 +290,7 @@ namespace UserInterface { _release->setTextFormat(Wt::TextFormat::Plain); _release->setText(Wt::WString::fromUTF8(std::string{ track->getRelease()->getName() })); - _release->setLink(Utils::createReleaseLink(track->getRelease())); + _release->setLink(utils::createReleaseLink(track->getRelease())); } else { @@ -335,4 +335,4 @@ namespace UserInterface _playQueue->setText(Wt::WString::tr("Lms.MediaPlayer.template.playqueue-btn").arg(trackCount)); } -} // namespace UserInterface +} // namespace lms::ui diff --git a/src/lms/ui/MediaPlayer.hpp b/src/lms/ui/MediaPlayer.hpp index 5dd4ab8a..88b178db 100644 --- a/src/lms/ui/MediaPlayer.hpp +++ b/src/lms/ui/MediaPlayer.hpp @@ -30,7 +30,7 @@ #include "database/TrackId.hpp" #include "database/Types.hpp" -namespace UserInterface +namespace lms::ui { class AudioFileResource; class AudioTranscodingResource; @@ -38,8 +38,8 @@ namespace UserInterface class MediaPlayer : public Wt::WTemplate { public: - using Bitrate = Database::Bitrate; - using Format = Database::TranscodingOutputFormat; + using Bitrate = db::Bitrate; + using Format = db::TranscodingOutputFormat; using Gain = float; // Do not change enum values as they may be stored locally in browser @@ -93,9 +93,9 @@ namespace UserInterface MediaPlayer(const MediaPlayer&) = delete; MediaPlayer& operator=(const MediaPlayer&) = delete; - std::optional getTrackLoaded() const { return _trackIdLoaded; } + std::optional getTrackLoaded() const { return _trackIdLoaded; } - void loadTrack(Database::TrackId trackId, bool play, float replayGain); + void loadTrack(db::TrackId trackId, bool play, float replayGain); void stop(); std::optional getSettings() const { return _settings; } @@ -106,11 +106,11 @@ namespace UserInterface // Signals Wt::JSignal<> playPrevious; Wt::JSignal<> playNext; - Wt::Signal trackLoaded; + Wt::Signal trackLoaded; Wt::Signal<> settingsLoaded; - Wt::JSignal scrobbleListenNow; - Wt::JSignal scrobbleListenFinished; + Wt::JSignal scrobbleListenNow; + Wt::JSignal scrobbleListenFinished; Wt::JSignal<> playbackEnded; @@ -118,7 +118,7 @@ namespace UserInterface std::unique_ptr _audioFileResource; std::unique_ptr _audioTranscodingResource; - std::optional _trackIdLoaded; + std::optional _trackIdLoaded; std::optional _settings; Wt::JSignal _settingsLoaded; @@ -130,5 +130,5 @@ namespace UserInterface Wt::WPushButton* _playQueue{}; }; -} // namespace UserInterface +} // namespace lms::ui diff --git a/src/lms/ui/ModalManager.cpp b/src/lms/ui/ModalManager.cpp index 26741613..0630988d 100644 --- a/src/lms/ui/ModalManager.cpp +++ b/src/lms/ui/ModalManager.cpp @@ -18,9 +18,9 @@ */ #include "ModalManager.hpp" -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" -namespace UserInterface +namespace lms::ui { ModalManager::ModalManager() : _closed{ this, "closed" } diff --git a/src/lms/ui/ModalManager.hpp b/src/lms/ui/ModalManager.hpp index 6c1d9c9f..49ac7fc8 100644 --- a/src/lms/ui/ModalManager.hpp +++ b/src/lms/ui/ModalManager.hpp @@ -23,7 +23,7 @@ #include #include -namespace UserInterface +namespace lms::ui { class ModalManager : public Wt::WContainerWidget { diff --git a/src/lms/ui/Notification.hpp b/src/lms/ui/Notification.hpp index 4141b7f6..abd451a5 100644 --- a/src/lms/ui/Notification.hpp +++ b/src/lms/ui/Notification.hpp @@ -1,6 +1,6 @@ #pragma once -namespace UserInterface::Notification +namespace lms::ui::Notification { enum class Type { diff --git a/src/lms/ui/NotificationContainer.cpp b/src/lms/ui/NotificationContainer.cpp index d7a96564..20cbd0ab 100644 --- a/src/lms/ui/NotificationContainer.cpp +++ b/src/lms/ui/NotificationContainer.cpp @@ -22,10 +22,10 @@ #include #include -#include "utils/ILogger.hpp" +#include "core/ILogger.hpp" #include "LmsApplication.hpp" -namespace UserInterface +namespace lms::ui { namespace { diff --git a/src/lms/ui/NotificationContainer.hpp b/src/lms/ui/NotificationContainer.hpp index cc871719..4f7b7e59 100644 --- a/src/lms/ui/NotificationContainer.hpp +++ b/src/lms/ui/NotificationContainer.hpp @@ -25,7 +25,7 @@ #include "Notification.hpp" -namespace UserInterface +namespace lms::ui { class NotificationContainer : public Wt::WContainerWidget { @@ -34,4 +34,4 @@ namespace UserInterface private: }; -} // namespace UserInterface +} // namespace lms::ui diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp index 63eaf5ff..8e9e7045 100644 --- a/src/lms/ui/PlayQueue.cpp +++ b/src/lms/ui/PlayQueue.cpp @@ -36,11 +36,11 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/recommendation/IPlaylistGeneratorService.hpp" -#include "utils/IConfig.hpp" -#include "utils/ILogger.hpp" -#include "utils/Random.hpp" -#include "utils/Service.hpp" -#include "utils/String.hpp" +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/Random.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "common/InfiniteScrollingContainer.hpp" #include "common/MandatoryValidator.hpp" @@ -51,7 +51,7 @@ #include "ModalManager.hpp" #include "Utils.hpp" -namespace UserInterface +namespace lms::ui { namespace { @@ -73,7 +73,7 @@ namespace UserInterface { public: static inline const Field NameField{ "name" }; - using TrackListModel = ValueStringModel; + using TrackListModel = ValueStringModel; ReplaceTrackListModel() { @@ -81,7 +81,7 @@ namespace UserInterface setValidator(NameField, createMandatoryValidator()); } - Database::TrackListId getTrackListId() const + db::TrackListId getTrackListId() const { auto row{ trackListModel->getRowFromString(valueText(NameField)) }; return trackListModel->getValue(*row); @@ -91,7 +91,7 @@ namespace UserInterface std::shared_ptr createTrackListModel() { - using namespace Database; + using namespace db; auto model{ std::make_shared() }; @@ -118,7 +118,7 @@ namespace UserInterface PlayQueue::PlayQueue() : Template{ Wt::WString::tr("Lms.PlayQueue.template") } - , _capacity{ Service::get()->getULong("playqueue-max-entry-count", 1000) } + , _capacity{ core::Service::get()->getULong("playqueue-max-entry-count", 1000) } { initTrackLists(); @@ -150,13 +150,13 @@ namespace UserInterface { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - Database::TrackList::pointer queue{ getQueue() }; + db::TrackList::pointer queue{ getQueue() }; auto entries{ queue->getEntries() }; - Random::shuffleContainer(entries); + core::random::shuffleContainer(entries); queue.modify()->clear(); - for (const Database::TrackListEntry::pointer& entry : entries) - LmsApp->getDbSession().create(entry->getTrack(), queue); + for (const db::TrackListEntry::pointer& entry : entries) + LmsApp->getDbSession().create(entry->getTrack(), queue); } _entriesContainer->reset(); addSome(); @@ -227,7 +227,7 @@ namespace UserInterface if (LmsApp->getUser()->isDemo()) { LMS_LOG(UI, DEBUG, "Removing queue (tracklist id " << _queueId.toString() << ")"); - if (Database::TrackList::pointer queue{ getQueue() }) + if (db::TrackList::pointer queue{ getQueue() }) queue.remove(); } }); @@ -245,9 +245,9 @@ namespace UserInterface return _radioBtn->checkState() == Wt::CheckState::Checked; } - Database::TrackList::pointer PlayQueue::getQueue() const + db::TrackList::pointer PlayQueue::getQueue() const { - return Database::TrackList::find(LmsApp->getDbSession(), _queueId); + return db::TrackList::find(LmsApp->getDbSession(), _queueId); } bool PlayQueue::isFull() const @@ -280,12 +280,12 @@ namespace UserInterface { updateCurrentTrack(false); - Database::TrackId trackId{}; + db::TrackId trackId{}; std::optional replayGain{}; { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - const Database::TrackList::pointer queue{ getQueue() }; + const db::TrackList::pointer queue{ getQueue() }; // If out of range, stop playing if (pos >= queue->getCount()) @@ -300,7 +300,7 @@ namespace UserInterface } _trackPos = pos; - const Database::Track::pointer track{ queue->getEntry(*_trackPos)->getTrack() }; + const db::Track::pointer track{ queue->getEntry(*_trackPos)->getTrack() }; trackId = track->getId(); @@ -353,20 +353,20 @@ namespace UserInterface { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - Database::TrackList::pointer queue; - Database::TrackList::pointer radioStartingTracks; + db::TrackList::pointer queue; + db::TrackList::pointer radioStartingTracks; if (!LmsApp->getUser()->isDemo()) { static const std::string queueName{ "__queued_tracks__" }; - queue = Database::TrackList::find(LmsApp->getDbSession(), queueName, Database::TrackListType::Internal, LmsApp->getUserId()); + queue = db::TrackList::find(LmsApp->getDbSession(), queueName, db::TrackListType::Internal, LmsApp->getUserId()); if (!queue) - queue = LmsApp->getDbSession().create(queueName, Database::TrackListType::Internal, false, LmsApp->getUser()); + queue = LmsApp->getDbSession().create(queueName, db::TrackListType::Internal, false, LmsApp->getUser()); } else { static const std::string queueName{ "__temp_queue__" }; - queue = LmsApp->getDbSession().create(queueName, Database::TrackListType::Internal, false, LmsApp->getUser()); + queue = LmsApp->getDbSession().create(queueName, db::TrackListType::Internal, false, LmsApp->getUser()); } _queueId = queue->getId(); @@ -376,10 +376,10 @@ namespace UserInterface { auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - const Database::TrackList::pointer queue{ getQueue() }; + const db::TrackList::pointer queue{ getQueue() }; const std::size_t trackCount{ queue->getCount() }; _nbTracks->setText(Wt::WString::trn("Lms.track-count", trackCount).arg(trackCount)); - _duration->setText(Utils::durationToString(queue->getDuration())); + _duration->setText(utils::durationToString(queue->getDuration())); trackCountChanged.emit(trackCount); } @@ -395,25 +395,25 @@ namespace UserInterface entry->toggleStyleClass("Lms-entry-playing", selected); } - void PlayQueue::enqueueTracks(const std::vector& trackIds) + void PlayQueue::enqueueTracks(const std::vector& trackIds) { { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - Database::TrackList::pointer queue{ getQueue() }; + db::TrackList::pointer queue{ getQueue() }; const std::size_t queueSize{ queue->getCount() }; std::size_t nbTracksToEnqueue{ queueSize + trackIds.size() > getCapacity() ? getCapacity() - queueSize : trackIds.size() }; - for (const Database::TrackId trackId : trackIds) + for (const db::TrackId trackId : trackIds) { if (nbTracksToEnqueue == 0) break; - Database::Track::pointer track{ Database::Track::find(LmsApp->getDbSession(), trackId) }; + db::Track::pointer track{ db::Track::find(LmsApp->getDbSession(), trackId) }; if (!track) continue; - LmsApp->getDbSession().create(track, queue); + LmsApp->getDbSession().create(track, queue); nbTracksToEnqueue--; } } @@ -423,16 +423,16 @@ namespace UserInterface _entriesContainer->setHasMore(); } - std::vector PlayQueue::getAndClearNextTracks() + std::vector PlayQueue::getAndClearNextTracks() { - std::vector tracks; + std::vector tracks; auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - Database::TrackList::pointer queue{ getQueue() }; - std::vector entries{ queue->getEntries(Database::Range {_trackPos ? *_trackPos + 1 : 0, getCapacity()}) }; + db::TrackList::pointer queue{ getQueue() }; + std::vector entries{ queue->getEntries(db::Range {_trackPos ? *_trackPos + 1 : 0, getCapacity()}) }; tracks.reserve(entries.size()); - for (Database::TrackListEntry::pointer entry : entries) + for (db::TrackListEntry::pointer entry : entries) { tracks.push_back(entry->getTrack()->getId()); entry.remove(); @@ -452,35 +452,35 @@ namespace UserInterface return tracks; } - void PlayQueue::play(const std::vector& trackIds) + void PlayQueue::play(const std::vector& trackIds) { playAtIndex(trackIds, 0); } - void PlayQueue::playNext(const std::vector& trackIds) + void PlayQueue::playNext(const std::vector& trackIds) { - std::vector nextTracks{ getAndClearNextTracks() }; + std::vector nextTracks{ getAndClearNextTracks() }; nextTracks.insert(std::cbegin(nextTracks), std::cbegin(trackIds), std::cend(trackIds)); playOrAddLast(nextTracks); } - void PlayQueue::playShuffled(const std::vector& trackIds) + void PlayQueue::playShuffled(const std::vector& trackIds) { clearTracks(); - std::vector shuffledTrackIds{ trackIds }; - Random::shuffleContainer(shuffledTrackIds); + std::vector shuffledTrackIds{ trackIds }; + core::random::shuffleContainer(shuffledTrackIds); enqueueTracks(shuffledTrackIds); loadTrack(0, true); } - void PlayQueue::playOrAddLast(const std::vector& trackIds) + void PlayQueue::playOrAddLast(const std::vector& trackIds) { enqueueTracks(trackIds); if (!_isTrackSelected) loadTrack(0, true); } - void PlayQueue::playAtIndex(const std::vector& trackIds, std::size_t index) + void PlayQueue::playAtIndex(const std::vector& trackIds, std::size_t index) { clearTracks(); enqueueTracks(trackIds); @@ -491,50 +491,50 @@ namespace UserInterface { auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - const Database::TrackList::pointer queue{ getQueue() }; - const auto tracklistEntries{ queue->getEntries(Database::Range {_entriesContainer->getCount(), _batchSize}) }; - for (const Database::TrackListEntry::pointer& tracklistEntry : tracklistEntries) + const db::TrackList::pointer queue{ getQueue() }; + const auto tracklistEntries{ queue->getEntries(db::Range {_entriesContainer->getCount(), _batchSize}) }; + for (const db::TrackListEntry::pointer& tracklistEntry : tracklistEntries) addEntry(tracklistEntry); } - void PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry) + void PlayQueue::addEntry(const db::TrackListEntry::pointer& tracklistEntry) { - const Database::TrackListEntryId tracklistEntryId{ tracklistEntry->getId() }; + const db::TrackListEntryId tracklistEntryId{ tracklistEntry->getId() }; const auto track{ tracklistEntry->getTrack() }; - const Database::TrackId trackId{ track->getId() }; + const db::TrackId trackId{ track->getId() }; Template* entry{ _entriesContainer->addNew