Merge branch 'develop' for release v3.45.0

This commit is contained in:
emeric
2023-11-25 17:02:04 +01:00
178 changed files with 4544 additions and 4174 deletions
+2 -3
View File
@@ -40,13 +40,12 @@ __Note__: depending on your database size and/or your hardware, the tag-based re
_LMS_ relies exclusively on tags to organize your music collection.
### Filtering
You can specify the tags you want to use to filter your collection. By default, `genre`, `albumgrouping`, `mood` and `albummood` tags are used.
In the administration settings, you can set whatever tags you want, even custom tags.
It is possible to apply global filters on your collection using `genre`, `mood`, `grouping` and `language` tags. More tags can be added in the database administration settings, even custom tags.
__Note__: you can use the `lms-metadata` tool to have an idea of the tags parsed by _LMS_ using [TagLib](https://github.com/taglib/taglib).
### Multiple album artists
_LMS_ requires the `ALBUMARTISTS` and `ALBUMARTISTSSORT` tags to properly handle multiple album artists on the same album. As they are a custom tags, you may need to setup your favorite tagger to add them.
_LMS_ requires the `albumartists` and `albumartistssort` tags to properly handle multiple album artists on the same album. As they are a custom tags, you may need to setup your favorite tagger to add them.
__Note__: if you use [Picard](https://picard.musicbrainz.org/), add the following script to include these tags:
```
+4 -4
View File
@@ -44,12 +44,12 @@
</div>
</div>
<div class="col-12">
<label class="form-label" for="${id:clusters}">
${tr:Lms.clusters}
<label class="form-label" for="${id:extra-tags-to-scan}">
${tr:Lms.Admin.Database.extra-tags-to-scan}
</label>
${clusters class="form-control"}
${extra-tags-to-scan class="form-control"}
<div class="invalid-feedback">
${clusters-info}
${extra-tags-to-scan-info}
</div>
</div>
<div class="col-12">
+3
View File
@@ -12,6 +12,7 @@
<message id="Lms.delete">Delete</message>
<message id="Lms.discard">Discard</message>
<message id="Lms.edit">Edit</message>
<message id="Lms.field-must-be-in-upper-case">This field must be in upper case</message>
<message id="Lms.loading">Loading...</message>
<message id="Lms.login">Login</message>
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Logout</message>
@@ -46,6 +47,7 @@
<!--Administration-->
<message id="Lms.Admin.Database.daily">Daily</message>
<message id="Lms.Admin.Database.database">Music collection</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Extra tags to scan (use <kbd>;</kbd> as separator)</message>
<message id="Lms.Admin.Database.hourly">Hourly</message>
<message id="Lms.Admin.Database.immediate-scan">Scan now!</message>
<message id="Lms.Admin.Database.monthly">Monthly</message>
@@ -135,6 +137,7 @@
<message id="Lms.Explore.play-last">Play last</message>
<message id="Lms.Explore.play-next">Play next</message>
<message id="Lms.Explore.play-shuffled">Play shuffled</message>
<message id="Lms.Explore.playcount">Play count</message>
<message id="Lms.Explore.random">Random</message>
<message id="Lms.Explore.recently-added">Recently added</message>
<message id="Lms.Explore.recently-modified">Recently modified</message>
+3
View File
@@ -12,6 +12,7 @@
<message id="Lms.delete">Supprimer</message>
<message id="Lms.discard">Annuler</message>
<message id="Lms.edit">Editer</message>
<message id="Lms.field-must-be-in-upper-case">Ce champ doit être en majuscule</message>
<message id="Lms.loading">Chargement...</message>
<message id="Lms.login">Login</message>
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Quitter</message>
@@ -46,6 +47,7 @@
<!--Administration-->
<message id="Lms.Admin.Database.daily">Tous les jours</message>
<message id="Lms.Admin.Database.database">Collection de musiques</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Tags supplémentaires à scanner (utiliser <kbd>;</kbd> comme séparateur)</message>
<message id="Lms.Admin.Database.hourly">Toutes les heures</message>
<message id="Lms.Admin.Database.immediate-scan">Scanner maintenant !</message>
<message id="Lms.Admin.Database.monthly">Tous les mois</message>
@@ -135,6 +137,7 @@
<message id="Lms.Explore.play-last">Jouer en dernier</message>
<message id="Lms.Explore.play-next">Jouer ensuite</message>
<message id="Lms.Explore.play-shuffled">Jouer aléatoirement</message>
<message id="Lms.Explore.playcount">Nombre d'écoutes</message>
<message id="Lms.Explore.random">Aléatoire</message>
<message id="Lms.Explore.recently-added">Ajoutés récemment</message>
<message id="Lms.Explore.recently-modified">Modifiés récemment</message>
+3
View File
@@ -12,6 +12,7 @@
<message id="Lms.delete">Elimina</message>
<message id="Lms.discard">Annulla</message>
<message id="Lms.edit">Modifica</message>
<message id="Lms.loading">Caricamento...</message>
<message id="Lms.login">Login</message>
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Logout</message>
@@ -46,6 +47,7 @@
<!--Administration-->
<message id="Lms.Admin.Database.daily">Giornaliera</message>
<message id="Lms.Admin.Database.database">Raccolta musicale</message>
<message id="Lms.Admin.Database.hourly">Ogni ora</message>
<message id="Lms.Admin.Database.immediate-scan">Scansiona ora!</message>
<message id="Lms.Admin.Database.monthly">Mensile</message>
@@ -135,6 +137,7 @@
<message id="Lms.Explore.play-last">Aggiungi alla coda di riproduzione</message>
<message id="Lms.Explore.play-shuffled">Aggiungi alla coda di riproduzione mischiando</message>
<message id="Lms.Explore.random">Casuali</message>
<message id="Lms.Explore.recently-added">Aggiunti di recente</message>
+3
View File
@@ -12,6 +12,7 @@
<message id="Lms.delete">删除</message>
<message id="Lms.discard">撤销</message>
<message id="Lms.edit">编辑</message>
<message id="Lms.loading">加载中...</message>
<message id="Lms.login">登陆</message>
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> 注销</message>
@@ -46,6 +47,7 @@
<!--Administration-->
<message id="Lms.Admin.Database.daily">每日</message>
<message id="Lms.Admin.Database.database">音乐收藏</message>
<message id="Lms.Admin.Database.hourly">每小时</message>
<message id="Lms.Admin.Database.immediate-scan">立即扫描!</message>
<message id="Lms.Admin.Database.monthly">每月</message>
@@ -135,6 +137,7 @@
<message id="Lms.Explore.play-last">最后播放</message>
<message id="Lms.Explore.play-shuffled">随机播放</message>
<message id="Lms.Explore.random">随机</message>
<message id="Lms.Explore.recently-added">最近添加</message>
<message id="Lms.Explore.recently-modified">最近更改</message>
+8
View File
@@ -137,6 +137,14 @@
</div>
</div>
${</if-has-bitrate>}
<div class="row align-items-center">
<div class="col-4">
${tr:Lms.Explore.playcount}
</div>
<div class="col">
${playcount}
</div>
</div>
</div>
</div>
<div class="modal-footer">
+8
View File
@@ -133,6 +133,14 @@
</div>
</div>
${</if-has-bitrate>}
<div class="row align-items-center">
<div class="col-4">
${tr:Lms.Explore.playcount}
</div>
<div class="col">
${playcount}
</div>
</div>
</div>
</div>
<div class="modal-footer">
+4 -2
View File
@@ -10,8 +10,10 @@ ffmpeg-file = "/usr/bin/ffmpeg";
# Log files, empty means stdout
log-file = "";
access-log-file = "";
# Logger configuration, see log-config in https://webtoolkit.eu/wt/doc/reference/html/overview.html#config_general
log-config = "* -debug -info:WebRequest";
# Minimum severity, can be "debug", "info", "warning", "error" or "fatal"
log-min-severity = "info";
# Output db queries on stdout
db-show-queries = false;
# Listen port/addr of the web server
listen-port = 5082;
+8 -8
View File
@@ -31,7 +31,7 @@ extern "C"
#include <map>
#include <unordered_map>
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/String.hpp"
namespace Av
@@ -105,14 +105,14 @@ namespace Av
int error{ avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr) };
if (error < 0)
{
LMS_LOG(AV, ERROR) << "Cannot open " << _p.string() << ": " << averror_to_string(error);
LMS_LOG(AV, ERROR, "Cannot open " << _p.string() << ": " << averror_to_string(error));
throw AudioFileException{ error };
}
error = avformat_find_stream_info(_context, nullptr);
if (error < 0)
{
LMS_LOG(AV, ERROR) << "Cannot find stream information on " << _p.string() << ": " << averror_to_string(error);
LMS_LOG(AV, ERROR, "Cannot find stream information on " << _p.string() << ": " << averror_to_string(error));
avformat_close_input(&_context);
throw AudioFileException{ error };
}
@@ -233,7 +233,7 @@ namespace Av
if (avstream->codecpar == nullptr)
{
LMS_LOG(AV, ERROR) << "Skipping stream " << i << " since no codecpar is set";
LMS_LOG(AV, ERROR, "Skipping stream " << i << " since no codecpar is set");
continue;
}
@@ -247,7 +247,7 @@ namespace Av
else
{
picture.mimeType = "application/octet-stream";
LMS_LOG(AV, ERROR) << "CODEC ID " << avstream->codecpar->codec_id << " not handled in mime type conversion";
LMS_LOG(AV, ERROR, "CODEC ID " << avstream->codecpar->codec_id << " not handled in mime type conversion");
}
const AVPacket& pkt{ avstream->attached_pic };
@@ -271,7 +271,7 @@ namespace Av
if (!avstream->codecpar)
{
LMS_LOG(AV, ERROR) << "Skipping stream " << streamIndex << " since no codecpar is set";
LMS_LOG(AV, ERROR, "Skipping stream " << streamIndex << " since no codecpar is set");
return res;
}
@@ -293,7 +293,7 @@ namespace Av
// List should be sync with the demuxers shipped in the lms's docker version
// + the _audioFileExtensions in ScanSettings
// std::filesystem::path does not seem to have std::hash specialization on freebsd
static const std::map<std::filesystem::path, std::string_view> entries
static const std::unordered_map<std::string, std::string_view> entries
{
{".mp3", "audio/mpeg"},
{".ogg", "audio/ogg"},
@@ -320,7 +320,7 @@ namespace Av
{".mka", "audio/x-matroska"},
};
auto it{ entries.find(fileExtension) };
auto it{ entries.find(StringUtils::stringToLower(fileExtension.string())) };
if (it == std::cend(entries))
return "";
+5 -5
View File
@@ -25,13 +25,13 @@
#include "utils/IChildProcessManager.hpp"
#include "utils/IConfig.hpp"
#include "utils/Path.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Service.hpp"
namespace Av::Transcoding
{
#define LOG(sev) LMS_LOG(TRANSCODING, sev) << "[" << _debugId << "] - "
#define LOG(severity, message) LMS_LOG(TRANSCODING, severity, "[" << _debugId << "] - " << message)
static std::atomic<size_t> globalId{};
static std::filesystem::path ffmpegPath;
@@ -84,7 +84,7 @@ namespace Av::Transcoding
throw Exception{ "File error '" + _inputParameters.trackPath.string() + "': " + e.what() };
}
LOG(INFO) << "Transcoding file '" << _inputParameters.trackPath.string() << "'";
LOG(INFO, "Transcoding file '" << _inputParameters.trackPath.string() << "'");
std::vector<std::string> args;
@@ -176,9 +176,9 @@ namespace Av::Transcoding
args.emplace_back("pipe:1");
LOG(DEBUG) << "Dumping args (" << args.size() << ")";
LOG(DEBUG, "Dumping args (" << args.size() << ")");
for (const std::string& arg : args)
LOG(DEBUG) << "Arg = '" << arg << "'";
LOG(DEBUG, "Arg = '" << arg << "'");
// Caution: stdin must have been closed before
try
@@ -18,7 +18,7 @@
*/
#include "TranscodingResourceHandler.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Av::Transcoding
{
@@ -43,9 +43,9 @@ namespace Av::Transcoding
, _transcoder{ inputParameters, outputParameters }
{
if (_estimatedContentLength)
LMS_LOG(TRANSCODING, DEBUG) << "Estimated content length = " << *_estimatedContentLength;
LMS_LOG(TRANSCODING, DEBUG, "Estimated content length = " << *_estimatedContentLength);
else
LMS_LOG(TRANSCODING, DEBUG) << "Not using estimated content length";
LMS_LOG(TRANSCODING, DEBUG, "Not using estimated content length");
}
Wt::Http::ResponseContinuation* TranscodingResourceHandler::processRequest(const Wt::Http::Request& /*request*/, Wt::Http::Response& response)
@@ -53,11 +53,11 @@ namespace Av::Transcoding
if (_estimatedContentLength)
response.setContentLength(*_estimatedContentLength);
response.setMimeType(_transcoder.getOutputMimeType());
LMS_LOG(TRANSCODING, DEBUG) << "Transcoder finished = " << _transcoder.finished() << ", total served bytes = " << _totalServedByteCount << ", mime type = " << _transcoder.getOutputMimeType();
LMS_LOG(TRANSCODING, DEBUG, "Transcoder finished = " << _transcoder.finished() << ", total served bytes = " << _totalServedByteCount << ", mime type = " << _transcoder.getOutputMimeType());
if (_bytesReadyCount > 0)
{
LMS_LOG(TRANSCODING, DEBUG) << "Writing " << _bytesReadyCount << " bytes back to client";
LMS_LOG(TRANSCODING, DEBUG, "Writing " << _bytesReadyCount << " bytes back to client");
response.out().write(reinterpret_cast<const char*>(&_buffer[0]), _bytesReadyCount);
_totalServedByteCount += _bytesReadyCount;
@@ -70,7 +70,7 @@ namespace Av::Transcoding
continuation->waitForMoreData();
_transcoder.asyncRead(_buffer.data(), _buffer.size(), [=](std::size_t nbBytesRead)
{
LMS_LOG(TRANSCODING, DEBUG) << "Have " << nbBytesRead << " more bytes to send back";
LMS_LOG(TRANSCODING, DEBUG, "Have " << nbBytesRead << " more bytes to send back");
assert(_bytesReadyCount == 0);
_bytesReadyCount = nbBytesRead;
@@ -86,7 +86,7 @@ namespace Av::Transcoding
{
const std::size_t padSize{ *_estimatedContentLength - _totalServedByteCount };
LMS_LOG(TRANSCODING, DEBUG) << "Adding " << padSize << " padding bytes";
LMS_LOG(TRANSCODING, DEBUG, "Adding " << padSize << " padding bytes");
for (std::size_t i{}; i < padSize; ++i)
response.out().put(0);
@@ -94,7 +94,7 @@ namespace Av::Transcoding
_totalServedByteCount += padSize;
}
LMS_LOG(TRANSCODING, DEBUG) << "Transcoding finished. Total served byte count = " << _totalServedByteCount;
LMS_LOG(TRANSCODING, DEBUG, "Transcoding finished. Total served byte count = " << _totalServedByteCount);
}
return {};
@@ -21,7 +21,7 @@
#include "RawImage.hpp"
#include "image/Exception.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Image::GraphicsMagick
{
@@ -36,7 +36,7 @@ namespace Image::GraphicsMagick
}
catch (Magick::Exception& e)
{
LMS_LOG(COVER, ERROR) << "Caught Magick exception: " << e.what();
LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what());
throw ImageException {std::string {"Magick read error: "} + e.what()};
}
}
+13 -13
View File
@@ -23,7 +23,7 @@
#include "JPEGImage.hpp"
#include "image/Exception.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Image
{
@@ -43,16 +43,16 @@ namespace Image
Magick::InitializeMagick(path.string().c_str());
if (auto nbThreads {MagickLib::GetMagickResourceLimit(MagickLib::ThreadsResource)}; nbThreads != 1)
LMS_LOG(COVER, WARNING) << "Consider setting env var OMP_NUM_THREADS=1 to save resources";
LMS_LOG(COVER, WARNING, "Consider setting env var OMP_NUM_THREADS=1 to save resources");
if (!MagickLib::SetMagickResourceLimit(MagickLib::ThreadsResource, 1))
LMS_LOG(COVER, ERROR) << "Cannot set Magick thread resource limit to 1!";
LMS_LOG(COVER, ERROR, "Cannot set Magick thread resource limit to 1!");
if (!MagickLib::SetMagickResourceLimit(MagickLib::DiskResource, 0))
LMS_LOG(COVER, ERROR) << "Cannot set Magick disk resource limit to 0!";
LMS_LOG(COVER, ERROR, "Cannot set Magick disk resource limit to 0!");
LMS_LOG(COVER, INFO) << "Magick threads resource limit = " << GetMagickResourceLimit(MagickLib::ThreadsResource);
LMS_LOG(COVER, INFO) << "Magick Disk resource limit = " << GetMagickResourceLimit(MagickLib::DiskResource);
LMS_LOG(COVER, INFO, "Magick threads resource limit = " << GetMagickResourceLimit(MagickLib::ThreadsResource));
LMS_LOG(COVER, INFO, "Magick Disk resource limit = " << GetMagickResourceLimit(MagickLib::DiskResource));
}
}
@@ -68,16 +68,16 @@ RawImage::RawImage(const std::byte* encodedData, std::size_t encodedDataSize)
}
catch (Magick::WarningCoder& e)
{
LMS_LOG(COVER, WARNING) << "Caught Magick WarningCoder: " << e.what();
LMS_LOG(COVER, WARNING, "Caught Magick WarningCoder: " << e.what());
}
catch (Magick::Warning& e)
{
LMS_LOG(COVER, WARNING) << "Caught Magick warning: " << e.what();
LMS_LOG(COVER, WARNING, "Caught Magick warning: " << e.what());
throw ImageException {std::string {"Magick read warning: "} + e.what()};
}
catch (Magick::Exception& e)
{
LMS_LOG(COVER, ERROR) << "Caught Magick exception: " << e.what();
LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what());
throw ImageException {std::string {"Magick read error: "} + e.what()};
}
}
@@ -90,16 +90,16 @@ RawImage::RawImage(const std::filesystem::path& p)
}
catch (Magick::WarningCoder& e)
{
LMS_LOG(COVER, WARNING) << "Caught Magick WarningCoder: " << e.what();
LMS_LOG(COVER, WARNING, "Caught Magick WarningCoder: " << e.what());
}
catch (Magick::Warning& e)
{
LMS_LOG(COVER, WARNING) << "Caught Magick warning: " << e.what();
LMS_LOG(COVER, WARNING, "Caught Magick warning: " << e.what());
throw ImageException {std::string {"Magick read warning: "} + e.what()};
}
catch (Magick::Exception& e)
{
LMS_LOG(COVER, ERROR) << "Caught Magick exception: " << e.what();
LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what());
throw ImageException {std::string {"Magick read error: "} + e.what()};
}
}
@@ -113,7 +113,7 @@ RawImage::resize(ImageSize width)
}
catch (Magick::Exception& e)
{
LMS_LOG(COVER, ERROR) << "Caught Magick exception while resizing: " << e.what();
LMS_LOG(COVER, ERROR, "Caught Magick exception while resizing: " << e.what());
throw ImageException {std::string {"Magick resize error: "} + e.what()};
}
}
+184 -196
View File
@@ -23,240 +23,228 @@
#include <iostream>
#include "av/IAudioFile.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/String.hpp"
#include "Utils.hpp"
namespace MetaData
{
namespace
{
template <typename T>
std::optional<T> findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initializer_list<std::string> tags)
{
auto it = std::find_first_of(std::cbegin(metadataMap), std::cend(metadataMap), std::cbegin(tags), std::cend(tags), [](const auto& it, const auto& str) { return it.first == str; });
if (it == std::cend(metadataMap))
return std::nullopt;
template <typename T>
std::optional<T>
findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initializer_list<std::string> tags)
{
auto it = std::find_first_of(std::cbegin(metadataMap), std::cend(metadataMap), std::cbegin(tags), std::cend(tags), [](const auto& it, const auto& str) { return it.first == str; });
if (it == std::cend(metadataMap))
return std::nullopt;
return StringUtils::readAs<T>(StringUtils::stringTrim(it->second));
}
return StringUtils::readAs<T>(StringUtils::stringTrim(it->second));
}
template <>
std::optional<std::vector<UUID>> findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initializer_list<std::string> tags)
{
std::optional<std::string> str{ findFirstValueOfAs<std::string>(metadataMap, tags) };
if (!str)
return std::nullopt;
template <>
std::optional<std::vector<UUID>>
findFirstValueOfAs(const Av::IAudioFile::MetadataMap& metadataMap, std::initializer_list<std::string> tags)
{
std::optional<std::string> str {findFirstValueOfAs<std::string>(metadataMap, tags)};
if (!str)
return std::nullopt;
const std::vector<std::string_view> strUuids{ StringUtils::splitString(*str, "/") };
std::vector<UUID> res;
const std::vector<std::string_view> strUuids {StringUtils::splitString(*str, "/")};
std::vector<UUID> res;
for (std::string_view strUuid : strUuids)
{
std::optional<UUID> uuid{ UUID::fromString(strUuid) };
if (!uuid)
return std::nullopt;
for (std::string_view strUuid : strUuids)
{
std::optional<UUID> uuid {UUID::fromString(strUuid)};
if (!uuid)
return std::nullopt;
res.push_back(std::move(*uuid));
}
res.push_back(std::move(*uuid));
}
return res;
}
return res;
}
std::vector<Artist> getReleaseArtists(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::vector<Artist> res;
static
std::vector<Artist>
getReleaseArtists(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::vector<Artist> res;
auto name{ findFirstValueOfAs<std::string>(metadataMap, {"ALBUM_ARTIST"}) };
if (!name)
return res;
auto name {findFirstValueOfAs<std::string>(metadataMap, {"ALBUM_ARTIST"})};
if (!name)
return res;
auto mbid{ findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"}) };
auto mbid {findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID"})};
return { Artist {mbid, *name, std::nullopt} };
}
return {Artist {mbid, *name, std::nullopt} };
}
std::vector<Artist> getArtists(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::vector<Artist> artists;
static
std::vector<Artist>
getArtists(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::vector<Artist> artists;
std::vector<std::string_view> artistNames;
if (metadataMap.find("ARTISTS") != metadataMap.end())
{
artistNames = StringUtils::splitString(metadataMap.find("ARTISTS")->second, "/;");
}
else if (metadataMap.find("ARTIST") != metadataMap.end())
{
artistNames = { metadataMap.find("ARTIST")->second };
}
std::vector<std::string_view> artistNames;
if (metadataMap.find("ARTISTS") != metadataMap.end())
{
artistNames = StringUtils::splitString(metadataMap.find("ARTISTS")->second, "/;");
}
else if (metadataMap.find("ARTIST") != metadataMap.end())
{
artistNames = {metadataMap.find("ARTIST")->second};
}
auto artistMBIDs{ findFirstValueOfAs<std::vector<UUID>>(metadataMap, {"MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ/ARTIST ID"}) };
auto artistMBIDs {findFirstValueOfAs<std::vector<UUID>>(metadataMap, {"MUSICBRAINZ ARTIST ID", "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ/ARTIST ID"})};
for (std::size_t i{}; i < artistNames.size(); ++i)
{
if (artistMBIDs && artistNames.size() == artistMBIDs->size())
artists.emplace_back(Artist{ (*artistMBIDs)[i], artistNames[i], std::nullopt });
else
artists.emplace_back(Artist{ std::nullopt, artistNames[i], std::nullopt });
}
for (std::size_t i {}; i < artistNames.size(); ++i)
{
if (artistMBIDs && artistNames.size() == artistMBIDs->size())
artists.emplace_back(Artist {(*artistMBIDs)[i], artistNames[i], std::nullopt});
else
artists.emplace_back(Artist {std::nullopt, artistNames[i], std::nullopt});
}
return artists;
}
return artists;
}
std::optional<Release> getRelease(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::optional<Release> res;
static
std::optional<Release>
getRelease(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::optional<Release> res;
std::optional<std::string> releaseName{ findFirstValueOfAs<std::string>(metadataMap, {"ALBUM", "TALB", "WM/ALBUMTITLE"}) };
if (!releaseName)
return res;
std::optional<std::string> releaseName {findFirstValueOfAs<std::string>(metadataMap, {"ALBUM", "TALB", "WM/ALBUMTITLE"})};
if (!releaseName)
return res;
res.emplace();
res->name = std::move(*releaseName);
res->mbid = findFirstValueOfAs<UUID>(metadataMap, { "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ/ALBUM ID" });
res->artists = getReleaseArtists(metadataMap);
res->mediumCount = findFirstValueOfAs<std::size_t>(metadataMap, { "TOTALDISCS", "DISCTOTAL" });
if (!res->mediumCount)
{
// mediumCount may be encoded as position/count
if (const auto value{ findFirstValueOfAs<std::string>(metadataMap, {"TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET"}) })
{
// Expecting 'Number/Total'
const std::vector<std::string_view> strings{ StringUtils::splitString(*value, "/") };
if (strings.size() == 2)
res->mediumCount = StringUtils::readAs<std::size_t>(strings[1]);
}
}
res.emplace();
res->name = std::move(*releaseName);
res->mbid = findFirstValueOfAs<UUID>(metadataMap, {"MUSICBRAINZ ALBUM ID", "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ/ALBUM ID"});
res->artists = getReleaseArtists(metadataMap);
res->mediumCount = findFirstValueOfAs<std::size_t>(metadataMap, {"TOTALDISCS", "DISCTOTAL"});
if (!res->mediumCount)
{
// mediumCount may be encoded as position/count
if (const auto value {findFirstValueOfAs<std::string>(metadataMap, {"TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET"})})
{
// Expecting 'Number/Total'
const std::vector<std::string_view> strings {StringUtils::splitString(*value, "/") };
if (strings.size() == 2)
res->mediumCount = StringUtils::readAs<std::size_t>(strings[1]);
}
}
return res;
}
return res;
}
std::optional<Medium> getMedium(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::optional<Medium> res;
res.emplace();
static
std::optional<Medium>
getMedium(const Av::IAudioFile::MetadataMap& metadataMap)
{
std::optional<Medium> res;
res.emplace();
res->type = findFirstValueOfAs<std::string>(metadataMap, { "TMED", "MEDIA", "WM/MEDIA" }).value_or("");
res->name = findFirstValueOfAs<std::string>(metadataMap, { "TSST", "DISCSUBTITLE", "SETSUBTITLE" }).value_or("");
res->trackCount = findFirstValueOfAs<std::size_t>(metadataMap, { "TOTALTRACKS", "TRACKTOTAL" });
if (!res->trackCount)
{
// totalTracks may be encoded as "position/count"
if (const auto value{ findFirstValueOfAs<std::string>(metadataMap, {"TRCK", "TRACK", "TRACKNUMBER", "TRKN", "WM/TRACKNUMBER"}) })
{
// Expecting 'Number/Total'
const std::vector<std::string_view> strings{ StringUtils::splitString(*value, "/") };
if (strings.size() == 2)
res->trackCount = StringUtils::readAs<std::size_t>(strings[1]);
}
}
res->type = findFirstValueOfAs<std::string>(metadataMap, {"TMED", "MEDIA", "WM/MEDIA"}).value_or("");
res->name = findFirstValueOfAs<std::string>(metadataMap, {"TSST", "DISCSUBTITLE", "SETSUBTITLE"}).value_or("");
res->trackCount = findFirstValueOfAs<std::size_t>(metadataMap, {"TOTALTRACKS", "TRACKTOTAL"});
if (!res->trackCount)
{
// totalTracks may be encoded as "position/count"
if (const auto value {findFirstValueOfAs<std::string>(metadataMap, {"TRCK", "TRACK", "TRACKNUMBER", "TRKN", "WM/TRACKNUMBER"})})
{
// Expecting 'Number/Total'
const std::vector<std::string_view> strings {StringUtils::splitString(*value, "/") };
if (strings.size() == 2)
res->trackCount = StringUtils::readAs<std::size_t>(strings[1]);
}
}
// position may be encoded in TPOS/DISC/DISK as "position/count". Expecting 'Number[/Total]'
res->position = findFirstValueOfAs<std::size_t>(metadataMap, { "TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET" });
res->release = getRelease(metadataMap);
// position may be encoded in TPOS/DISC/DISK as "position/count". Expecting 'Number[/Total]'
res->position = findFirstValueOfAs<std::size_t>(metadataMap, {"TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET"});
res->release = getRelease(metadataMap);
if (res->type.empty()
&& res->name.empty()
&& !res->trackCount
&& !res->position
&& !res->release
&& !res->replayGain)
{
res.reset();
}
if (res->type.empty()
&& res->name.empty()
&& !res->trackCount
&& !res->position
&& !res->release
&& !res->replayGain)
{
res.reset();
}
return res;
}
}
return res;
}
std::optional<Track> AvFormatParser::parse(const std::filesystem::path& p, bool debug)
{
Track track;
std::optional<Track>
AvFormatParser::parse(const std::filesystem::path& p, bool debug)
{
Track track;
try
{
const auto mediaFile{ Av::parseAudioFile(p) };
try
{
const auto mediaFile {Av::parseAudioFile(p)};
Av::ContainerInfo info{ mediaFile->getContainerInfo() };
track.duration = info.duration;
track.bitrate = info.bitrate;
track.hasCover = mediaFile->hasAttachedPictures();
Av::ContainerInfo info{ mediaFile->getContainerInfo() };
track.duration = info.duration;
track.bitrate = info.bitrate;
track.hasCover = mediaFile->hasAttachedPictures();
MetaData::Tags tags;
MetaData::Tags tags;
const Av::IAudioFile::MetadataMap metadataMap{ mediaFile->getMetaData() };
const Av::IAudioFile::MetadataMap metadataMap {mediaFile->getMetaData()};
track.artists = getArtists(metadataMap);
track.medium = getMedium(metadataMap);
track.artists = getArtists(metadataMap);
track.medium = getMedium(metadataMap);
for (const auto& [tag, value] : metadataMap)
{
if (debug)
std::cout << "TAG = " << tag << ", VAL = " << value << std::endl;
for (const auto& [tag, value] : metadataMap)
{
if (debug)
std::cout << "TAG = " << tag << ", VAL = " << value << std::endl;
if (tag == "TITLE")
track.title = value;
else if (tag == "TRACK")
{
// Expecting 'Number/Total'
track.position = StringUtils::readAs<std::size_t>(value);
}
else if (tag == "DATE"
|| tag == "YEAR"
|| tag == "WM/YEAR")
{
track.date = Utils::parseDate(value);
}
else if (tag == "TDOR" // Original release time (ID3v2 2.4)
|| tag == "TORY") // Original release year
{
track.originalDate = Utils::parseDate(value);
}
else if (tag == "ACOUSTID ID")
{
track.acoustID = UUID::fromString(value);
}
else if (tag == "MUSICBRAINZ RELEASE TRACK ID"
|| tag == "MUSICBRAINZ_RELEASETRACKID")
{
track.mbid = UUID::fromString(value);
}
else if (tag == "MUSICBRAINZ_TRACKID"
|| tag == "MUSICBRAINZ/TRACK ID")
{
track.recordingMBID = UUID::fromString(value);
}
else if (std::find(std::cbegin(_extraTags), std::cend(_extraTags), tag) != std::cend(_extraTags))
{
const std::vector<std::string_view> tagValues{ StringUtils::splitString(value, "/,;") };
if (tag == "TITLE")
track.title = value;
else if (tag == "TRACK")
{
// Expecting 'Number/Total'
track.position = StringUtils::readAs<std::size_t>(value);
}
else if (tag == "DATE"
|| tag == "YEAR"
|| tag == "WM/YEAR")
{
track.date = Utils::parseDate(value);
}
else if (tag == "TDOR" // Original release time (ID3v2 2.4)
|| tag == "TORY") // Original release year
{
track.originalDate = Utils::parseDate(value);
}
else if (tag == "ACOUSTID ID")
{
track.acoustID = UUID::fromString(value);
}
else if (tag == "MUSICBRAINZ RELEASE TRACK ID"
|| tag == "MUSICBRAINZ_RELEASETRACKID")
{
track.mbid = UUID::fromString(value);
}
else if (tag == "MUSICBRAINZ_TRACKID"
|| tag == "MUSICBRAINZ/TRACK ID")
{
track.recordingMBID = UUID::fromString(value);
}
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
{
const std::vector<std::string_view> clusterNames {StringUtils::splitString(value, "/,;")};
if (!tagValues.empty())
{
std::set<std::string> values;
std::transform(std::cbegin(tagValues), std::cend(tagValues), std::inserter(values, std::begin(values)), [](std::string_view v) { return std::string{ v }; });
track.tags[tag] = std::move(values);
}
}
}
}
catch (Av::Exception& e)
{
return std::nullopt;
}
if (!clusterNames.empty())
{
std::set<std::string> values;
std::transform(std::cbegin(clusterNames), std::cend(clusterNames),
std::inserter(values, std::begin(values)),
[](std::string_view clusterName) { return std::string {clusterName}; });
track.tags[tag] = std::move(values);
}
}
}
}
catch(Av::Exception& e)
{
return std::nullopt;
}
return track;
}
return track;
}
} // namespace MetaData
+3 -3
View File
@@ -20,7 +20,7 @@
#include "metadata/IParser.hpp"
#include "utils/Exception.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "AvFormatParser.hpp"
#include "TagLibParser.hpp"
@@ -34,10 +34,10 @@ namespace MetaData
switch (parserType)
{
case ParserType::TagLib:
LMS_LOG(METADATA, INFO) << "Creating TagLib parser with read style = " << Utils::readStyleToString(parserReadStyle);
LMS_LOG(METADATA, INFO, "Creating TagLib parser with read style = " << Utils::readStyleToString(parserReadStyle));
return std::make_unique<TagLibParser>(parserReadStyle);
case ParserType::AvFormat:
LMS_LOG(METADATA, INFO) << "Creating AvFormat parser";
LMS_LOG(METADATA, INFO, "Creating AvFormat parser");
return std::make_unique<AvFormatParser>();
}
+9 -9
View File
@@ -37,7 +37,7 @@
#include "utils/IConfig.hpp"
#include "utils/Exception.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Service.hpp"
#include "utils/String.hpp"
#include "Utils.hpp"
@@ -368,18 +368,18 @@ namespace MetaData
track.replayGain = StringUtils::readAs<float>(value);
else if (tag == "ARTIST")
track.artistDisplayName = value;
else if (_clusterTypeNames.find(tag) != _clusterTypeNames.end())
else if (std::find(std::cbegin(_extraTags), std::cend(_extraTags), tag) != std::cend(_extraTags))
{
std::set<std::string> clusterNames;
std::set<std::string> tagValues;
for (std::string_view valueList : values)
{
const std::vector<std::string_view> splittedValues{ splitAndTrimString(valueList, "/,;") };
const std::vector<std::string_view> splittedValues{ splitAndTrimString(valueList, "/,;") }; // handle possibily bad split tags
for (std::string_view value : splittedValues)
clusterNames.insert(std::string{ value });
tagValues.insert(std::string{ value });
}
if (!clusterNames.empty())
track.tags[tag] = std::move(clusterNames);
if (!tagValues.empty())
track.tags[tag] = std::move(tagValues);
}
}
@@ -391,7 +391,7 @@ namespace MetaData
if (f.isNull())
{
LMS_LOG(METADATA, ERROR) << "File '" << p.string() << "': parsing failed";
LMS_LOG(METADATA, ERROR, "File '" << p.string() << "': parsing failed");
return std::nullopt;
}
@@ -404,7 +404,7 @@ namespace MetaData
}
else
{
LMS_LOG(METADATA, INFO) << "File '" << p.string() << "': no audio properties";
LMS_LOG(METADATA, INFO, "File '" << p.string() << "': no audio properties");
return std::nullopt;
}
@@ -34,7 +34,7 @@
namespace MetaData
{
using Tags = std::map<std::string /* type */, std::set<std::string> /* names */>;
using Tags = std::map<std::string /* type */, std::set<std::string> /* values */>;
// Very simplified version of https://musicbrainz.org/doc/MusicBrainz_Database/Schema
@@ -131,10 +131,10 @@ namespace MetaData
virtual std::optional<Track> parse(const std::filesystem::path& p, bool debug = false) = 0;
void setClusterTypeNames(const std::set<std::string>& clusterTypeNames) { _clusterTypeNames = clusterTypeNames; }
void setExtraTags(const std::vector<std::string>& extraTags) { _extraTags = std::set(extraTags.cbegin(), extraTags.cend()); }
protected:
std::set<std::string> _clusterTypeNames;
std::set<std::string> _extraTags;
};
enum class ParserType
@@ -22,7 +22,7 @@
#include "services/database/Db.hpp"
#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Auth
{
@@ -36,14 +36,14 @@ namespace Auth
AuthServiceBase::getOrCreateUser(std::string_view loginName)
{
Session& session {getDbSession()};
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createWriteTransaction()};
User::pointer 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<User>(loginName);
user.modify()->setType(type);
@@ -56,7 +56,7 @@ namespace Auth
AuthServiceBase::onUserAuthenticated(UserId userId)
{
Session& session {getDbSession()};
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createWriteTransaction()};
User::pointer user {User::find(session, userId)};
if (user)
@@ -28,7 +28,7 @@
#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "utils/Exception.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Auth
{
@@ -54,7 +54,7 @@ namespace Auth
Database::Session& session {getDbSession()};
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createWriteTransaction()};
Database::User::pointer user {Database::User::find(session, userId)};
if (!user)
@@ -62,7 +62,7 @@ namespace Auth
Database::AuthToken::pointer authToken {session.create<Database::AuthToken>(secretHash, expiry, user)};
LMS_LOG(UI, DEBUG) << "Created auth token for user '" << user->getLoginName() << "', expiry = " << expiry.toString();
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());
@@ -76,7 +76,7 @@ namespace Auth
const std::string secretHash {sha1Function.compute(std::string {secret}, {})};
Database::Session& session {getDbSession()};
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createWriteTransaction()};
Database::AuthToken::pointer authToken {Database::AuthToken::find(session, secretHash)};
if (!authToken)
@@ -88,7 +88,7 @@ namespace Auth
return std::nullopt;
}
LMS_LOG(UI, DEBUG) << "Found auth token for user '" << authToken->getUser()->getLoginName() << "'!";
LMS_LOG(UI, DEBUG, "Found auth token for user '" << authToken->getUser()->getLoginName() << "'!");
AuthTokenService::AuthTokenProcessResult::AuthTokenInfo res {authToken->getUser()->getId(), authToken->getExpiry()};
authToken.remove();
@@ -131,7 +131,7 @@ namespace Auth
{
Database::Session& session {getDbSession()};
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createWriteTransaction()};
Database::User::pointer user {Database::User::find(session, userId)};
if (!user)
@@ -21,7 +21,7 @@
#include "LoginThrottler.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Random.hpp"
namespace Auth {
@@ -81,10 +81,10 @@ LoginThrottler::onBadClientAttempt(const boost::asio::ip::address& address)
attemptInfo.badConsecutiveAttemptCount += 1;
LMS_LOG(AUTH, DEBUG) << "Registering bad attempt for '" << clientAddress.to_string() << "', consecutive bad attempts count = " << attemptInfo.badConsecutiveAttemptCount;
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() << "'";
LMS_LOG(AUTH, DEBUG, "Throttling '" << clientAddress.to_string() << "'");
attemptInfo.nextAttempt = now.addMSecs(std::chrono::duration_cast<std::chrono::milliseconds>(_throttlingDuration).count());
}
else
@@ -31,7 +31,7 @@
#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "utils/Exception.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Auth
{
@@ -61,7 +61,7 @@ namespace Auth
PasswordServiceBase::CheckResult
PasswordServiceBase::checkUserPassword(const boost::asio::ip::address& clientAddress, std::string_view loginName, std::string_view password)
{
LMS_LOG(AUTH, DEBUG) << "Checking password for user '" << loginName << "'";
LMS_LOG(AUTH, DEBUG, "Checking password for user '" << loginName << "'");
// Do not waste too much resource on brute force attacks (optim)
{
@@ -22,46 +22,41 @@
#include <Wt/WEnvironment.h>
#include "utils/IConfig.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Service.hpp"
namespace Auth
{
HttpHeadersEnvService::HttpHeadersEnvService(Database::Db& db)
: AuthServiceBase{ db }
, _fieldName{ Service<IConfig>::get()->getString("http-headers-login-field", "X-Forwarded-User") }
{
LMS_LOG(AUTH, INFO, "Using http header field = '" << _fieldName << "'");
}
HttpHeadersEnvService::HttpHeadersEnvService(Database::Db& db)
: AuthServiceBase {db}
, _fieldName {Service<IConfig>::get()->getString("http-headers-login-field", "X-Forwarded-User")}
{
LMS_LOG(AUTH, INFO) << "Using http header field = '" << _fieldName << "'";
}
HttpHeadersEnvService::CheckResult HttpHeadersEnvService::processEnv(const Wt::WEnvironment& env)
{
const std::string loginName{ env.headerValue(_fieldName) };
if (loginName.empty())
return { CheckResult::State::Denied };
HttpHeadersEnvService::CheckResult
HttpHeadersEnvService::processEnv(const Wt::WEnvironment& env)
{
const std::string loginName {env.headerValue(_fieldName)};
if (loginName.empty())
return {CheckResult::State::Denied};
LMS_LOG(AUTH, DEBUG, "Extracted login name = '" << loginName << "' from HTTP header");
LMS_LOG(AUTH, DEBUG) << "Extracted login name = '" << loginName << "' from HTTP header";
const Database::UserId userId{ getOrCreateUser(loginName) };
onUserAuthenticated(userId);
return { CheckResult::State::Granted, userId };
}
const Database::UserId userId {getOrCreateUser(loginName)};
onUserAuthenticated(userId);
return {CheckResult::State::Granted, userId};
}
HttpHeadersEnvService::CheckResult HttpHeadersEnvService::processRequest(const Wt::Http::Request& request)
{
const std::string loginName{ request.headerValue(_fieldName) };
if (loginName.empty())
return { CheckResult::State::Denied };
HttpHeadersEnvService::CheckResult
HttpHeadersEnvService::processRequest(const Wt::Http::Request& request)
{
const std::string loginName {request.headerValue(_fieldName)};
if (loginName.empty())
return {CheckResult::State::Denied};
LMS_LOG(AUTH, DEBUG) << "Extracted login name = '" << loginName << "' from HTTP header";
const Database::UserId userId {getOrCreateUser(loginName)};
onUserAuthenticated(userId);
return {CheckResult::State::Granted, userId};
}
LMS_LOG(AUTH, DEBUG, "Extracted login name = '" << loginName << "' from HTTP header");
const Database::UserId userId{ getOrCreateUser(loginName) };
onUserAuthenticated(userId);
return { CheckResult::State::Granted, userId };
}
} // namespace Auth
@@ -25,7 +25,7 @@
#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "utils/Exception.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Auth
{
@@ -44,17 +44,17 @@ namespace Auth
bool
InternalPasswordService::checkUserPassword(std::string_view loginName, std::string_view password)
{
LMS_LOG(AUTH, DEBUG) << "Checking internal password for user '" << loginName << "'";
LMS_LOG(AUTH, DEBUG, "Checking internal password for user '" << loginName << "'");
Database::User::PasswordHash passwordHash;
{
Database::Session& session {getDbSession()};
auto transaction {session.createSharedTransaction()};
auto transaction {session.createReadTransaction()};
const Database::User::pointer user {Database::User::find(session, loginName)};
if (!user)
{
LMS_LOG(AUTH, DEBUG) << "hashing random stuff";
LMS_LOG(AUTH, DEBUG, "hashing random stuff");
// hash random stuff here to waste some time
hashRandomPassword();
return false;
@@ -100,7 +100,7 @@ namespace Auth
const Database::User::PasswordHash passwordHash {hashPassword(newPassword)};
Database::Session& session {getDbSession()};
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createWriteTransaction()};
Database::User::pointer user {Database::User::find(session, userId)};
if (!user)
@@ -28,175 +28,173 @@
#include "services/auth/Types.hpp"
#include "services/database/Session.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Auth
{
class PAMError
{
public:
PAMError(std::string_view msg, pam_handle_t *pamh, int err)
{
_errorMsg = std::string {msg} + ": " + pam_strerror(pamh, err);
}
namespace
{
class PAMError
{
public:
PAMError(std::string_view msg, pam_handle_t* pamh, int err)
{
_errorMsg = std::string{ msg } + ": " + pam_strerror(pamh, err);
}
std::string_view message() const { return _errorMsg; }
std::string_view message() const { return _errorMsg; }
private:
std::string _errorMsg;
};
private:
std::string _errorMsg;
};
class PAMContext
{
public:
PAMContext(std::string_view loginName)
{
int err {pam_start("lms", std::string {loginName}.c_str(), &_conv, &_pamh)};
if (err != PAM_SUCCESS)
throw PAMError {"start failed", _pamh, err};
}
class PAMContext
{
public:
PAMContext(std::string_view loginName)
{
int err{ pam_start("lms", std::string {loginName}.c_str(), &_conv, &_pamh) };
if (err != PAM_SUCCESS)
throw PAMError{ "start failed", _pamh, err };
}
~PAMContext()
{
int err {pam_end(_pamh, 0)};
if (err != PAM_SUCCESS)
LMS_LOG(AUTH, ERROR) << "end failed: " << pam_strerror(_pamh, err);
}
~PAMContext()
{
int err{ pam_end(_pamh, 0) };
if (err != PAM_SUCCESS)
LMS_LOG(AUTH, ERROR, "end failed: " << pam_strerror(_pamh, err));
}
void authenticate(std::string_view password)
{
AuthenticateConvContext authContext {password};
ScopedConvContextSetter scopedContext {*this, authContext};
void authenticate(std::string_view password)
{
AuthenticateConvContext authContext{ password };
ScopedConvContextSetter scopedContext{ *this, authContext };
int err {pam_authenticate(_pamh, 0)};
if (err != PAM_SUCCESS)
throw PAMError {"authenticate failed", _pamh, err};
}
int err{ pam_authenticate(_pamh, 0) };
if (err != PAM_SUCCESS)
throw PAMError{ "authenticate failed", _pamh, err };
}
void validateAccount()
{
int err {pam_acct_mgmt(_pamh, PAM_SILENT)};
if (err != PAM_SUCCESS)
throw PAMError {"acct_mgmt failed", _pamh, err};
}
void validateAccount()
{
int err{ pam_acct_mgmt(_pamh, PAM_SILENT) };
if (err != PAM_SUCCESS)
throw PAMError{ "acct_mgmt failed", _pamh, err };
}
private:
class ConvContext
{
public:
virtual ~ConvContext() = default;
};
private:
class ConvContext
{
public:
virtual ~ConvContext() = default;
};
class AuthenticateConvContext final : public ConvContext
{
public:
AuthenticateConvContext(std::string_view password) : _password {password} {}
class AuthenticateConvContext final : public ConvContext
{
public:
AuthenticateConvContext(std::string_view password) : _password{ password } {}
std::string_view getPassword() const { return _password; }
std::string_view getPassword() const { return _password; }
private:
std::string_view _password;
};
private:
std::string_view _password;
};
class ScopedConvContextSetter
{
public:
ScopedConvContextSetter(PAMContext& pamContext, ConvContext& convContext)
: _pamContext {pamContext}
{
_pamContext._convContext = &convContext;
}
class ScopedConvContextSetter
{
public:
ScopedConvContextSetter(PAMContext& pamContext, ConvContext& convContext)
: _pamContext{ pamContext }
{
_pamContext._convContext = &convContext;
}
~ScopedConvContextSetter()
{
_pamContext._convContext = nullptr;
}
~ScopedConvContextSetter()
{
_pamContext._convContext = nullptr;
}
ScopedConvContextSetter(const ScopedConvContextSetter&) = delete;
ScopedConvContextSetter(ScopedConvContextSetter&&) = delete;
ScopedConvContextSetter& operator=(const ScopedConvContextSetter&) = delete;
ScopedConvContextSetter& operator=(ScopedConvContextSetter&&) = delete;
ScopedConvContextSetter(const ScopedConvContextSetter&) = delete;
ScopedConvContextSetter(ScopedConvContextSetter&&) = delete;
ScopedConvContextSetter& operator=(const ScopedConvContextSetter&) = delete;
ScopedConvContextSetter& operator=(ScopedConvContextSetter&&) = delete;
private:
PAMContext& _pamContext;
};
private:
PAMContext& _pamContext;
};
static int conv(int msgCount, const pam_message** msgs, pam_response** resps, void* userData)
{
if (msgCount < 1)
return PAM_CONV_ERR;
if (!resps || !msgs || !userData)
return PAM_CONV_ERR;
static int conv(int msgCount, const pam_message** msgs, pam_response** resps, void* userData)
{
if (msgCount < 1)
return PAM_CONV_ERR;
if (!resps || !msgs || !userData)
return PAM_CONV_ERR;
PAMContext& context{ *static_cast<PAMContext*>(userData) };
PAMContext& context {*static_cast<PAMContext*>(userData)};
AuthenticateConvContext* authenticateContext = dynamic_cast<AuthenticateConvContext*>(context._convContext);
if (!authenticateContext)
{
LMS_LOG(AUTH, ERROR, "Unexpected conv!");
return PAM_CONV_ERR;
}
AuthenticateConvContext* authenticateContext = dynamic_cast<AuthenticateConvContext*>(context._convContext);
if (!authenticateContext)
{
LMS_LOG(AUTH, ERROR) << "Unexpected conv!";
return PAM_CONV_ERR;
}
// Only expect a PAM_PROMPT_ECHO_OFF msg
if (msgCount != 1 || msgs[0]->msg_style != PAM_PROMPT_ECHO_OFF)
{
LMS_LOG(AUTH, ERROR, "Unexpected conv message. Count = " << msgCount);
return PAM_CONV_ERR;
}
// Only expect a PAM_PROMPT_ECHO_OFF msg
if (msgCount != 1 || msgs[0]->msg_style != PAM_PROMPT_ECHO_OFF)
{
LMS_LOG(AUTH, ERROR) << "Unexpected conv message. Count = " << msgCount;
return PAM_CONV_ERR;
}
pam_response* response{ static_cast<pam_response*>(malloc(sizeof(pam_response))) };
if (!response)
return PAM_CONV_ERR;
pam_response* response {static_cast<pam_response*>(malloc(sizeof(pam_response)))};
if (!response)
return PAM_CONV_ERR;
response->resp = strdup(std::string{ authenticateContext->getPassword() }.c_str());
response->resp = strdup(std::string {authenticateContext->getPassword()}.c_str());
*resps = response;
return PAM_SUCCESS;
}
*resps = response;
return PAM_SUCCESS;
}
ConvContext* _convContext{};
pam_conv _conv{ &PAMContext::conv, this };
pam_handle_t* _pamh{};
};
}
ConvContext* _convContext {};
pam_conv _conv {&PAMContext::conv, this};
pam_handle_t *_pamh {};
};
bool PAMPasswordService::checkUserPassword(std::string_view loginName, std::string_view password)
{
try
{
LMS_LOG(AUTH, DEBUG, "Checking PAM password for user '" << loginName << "'");
PAMContext pamContext{ loginName };
bool
PAMPasswordService::checkUserPassword(std::string_view loginName, std::string_view password)
{
try
{
LMS_LOG(AUTH, DEBUG) << "Checking PAM password for user '" << loginName << "'";
PAMContext pamContext {loginName};
pamContext.authenticate(password);
pamContext.validateAccount();
pamContext.authenticate(password);
pamContext.validateAccount();
return true;
}
catch (const PAMError& error)
{
LMS_LOG(AUTH, ERROR, "PAM error: " << error.message());
return false;
}
}
return true;
}
catch (const PAMError& error)
{
LMS_LOG(AUTH, ERROR) << "PAM error: " << error.message();
return false;
}
}
bool PAMPasswordService::canSetPasswords() const
{
return false;
}
bool
PAMPasswordService::canSetPasswords() const
{
return false;
}
IPasswordService::PasswordAcceptabilityResult PAMPasswordService::checkPasswordAcceptability(std::string_view, const PasswordValidationContext&) const
{
throw NotImplementedException{};
}
IPasswordService::PasswordAcceptabilityResult
PAMPasswordService::checkPasswordAcceptability(std::string_view, const PasswordValidationContext&) const
{
throw NotImplementedException {};
}
void
PAMPasswordService::setPassword(Database::UserId, std::string_view)
{
throw NotImplementedException {};
}
void PAMPasswordService::setPassword(Database::UserId, std::string_view)
{
throw NotImplementedException{};
}
} // namespace Auth
+14 -14
View File
@@ -29,7 +29,7 @@
#include "image/Exception.hpp"
#include "image/IRawImage.hpp"
#include "utils/IConfig.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Random.hpp"
#include "utils/String.hpp"
#include "utils/Utils.hpp"
@@ -51,7 +51,7 @@ namespace Cover
{
std::optional<TrackInfo> res;
auto transaction{ dbSession.createSharedTransaction() };
auto transaction{ dbSession.createReadTransaction() };
const Database::Track::pointer track{ Database::Track::find(dbSession, trackId) };
if (!track)
@@ -110,10 +110,10 @@ namespace Cover
{
setJpegQuality(Service<IConfig>::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, "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, ","));
#if LMS_SUPPORT_IMAGE_GM
GraphicsMagick::init(execPath);
@@ -148,7 +148,7 @@ namespace Cover
}
catch (const Image::ImageException& e)
{
LMS_LOG(COVER, ERROR) << "Cannot read embedded cover: " << e.what();
LMS_LOG(COVER, ERROR, "Cannot read embedded cover: " << e.what());
}
});
@@ -167,7 +167,7 @@ namespace Cover
}
catch (const ImageException& e)
{
LMS_LOG(COVER, ERROR) << "Cannot read cover in file '" << p.string() << "': " << e.what();
LMS_LOG(COVER, ERROR, "Cannot read cover in file '" << p.string() << "': " << e.what());
}
return image;
@@ -190,7 +190,7 @@ namespace Cover
std::shared_ptr<IEncodedImage> image{ getFromCoverFile(_defaultCoverPath, width) };
_defaultCoverCache[width] = image;
LMS_LOG(COVER, DEBUG) << "Default cache entries = " << _defaultCoverCache.size();
LMS_LOG(COVER, DEBUG, "Default cache entries = " << _defaultCoverCache.size());
return image;
}
@@ -269,7 +269,7 @@ namespace Cover
if (std::filesystem::file_size(filePath, ec) > _maxFileSize && !ec)
{
LMS_LOG(COVER, INFO) << "Cover file '" << filePath.string() << " is too big (" << std::filesystem::file_size(filePath, ec) << "), limit is " << _maxFileSize;
LMS_LOG(COVER, INFO, "Cover file '" << filePath.string() << " is too big (" << std::filesystem::file_size(filePath, ec) << "), limit is " << _maxFileSize);
return false;
}
@@ -306,7 +306,7 @@ namespace Cover
}
catch (Av::Exception& e)
{
LMS_LOG(COVER, ERROR) << "Cannot get covers from track " << p.string() << ": " << e.what();
LMS_LOG(COVER, ERROR, "Cannot get covers from track " << p.string() << ": " << e.what());
}
return image;
@@ -372,7 +372,7 @@ namespace Cover
{
std::optional<ReleaseInfo> res;
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
// get a track in this release, consider the release is in a single directory
const auto tracks{ Track::find(session, Track::FindParameters {}.setRelease(releaseId).setRange(Range{ 0, 1 }).setSortMethod(TrackSortMethod::Release)) };
@@ -404,7 +404,7 @@ namespace Cover
{
std::unique_lock lock{ _cacheMutex };
LMS_LOG(COVER, DEBUG) << "Cache stats: hits = " << _cacheHits << ", misses = " << _cacheMisses << ", nb entries = " << _cache.size() << ", size = " << _cacheSize;
LMS_LOG(COVER, DEBUG, "Cache stats: hits = " << _cacheHits << ", misses = " << _cacheMisses << ", nb entries = " << _cache.size() << ", size = " << _cacheSize);
_cacheHits = 0;
_cacheMisses = 0;
_cacheSize = 0;
@@ -415,7 +415,7 @@ namespace Cover
{
_jpegQuality = Utils::clamp<unsigned>(quality, 1, 100);
LMS_LOG(COVER, INFO) << "JPEG export quality = " << _jpegQuality;
LMS_LOG(COVER, INFO, "JPEG export quality = " << _jpegQuality);
}
void CoverService::saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr<IEncodedImage> image)
@@ -17,6 +17,7 @@ add_library(lmsdatabase SHARED
impl/SqlQuery.cpp
impl/Track.cpp
impl/TrackBookmark.cpp
impl/TransactionChecker.cpp
impl/Types.cpp
impl/User.cpp
impl/Utils.cpp
+14 -14
View File
@@ -25,7 +25,7 @@
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "services/database/User.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "SqlQuery.hpp"
#include "Utils.hpp"
#include "EnumSetTraits.hpp"
@@ -38,7 +38,7 @@ namespace Database
template <typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Artist::FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<ResultType>("SELECT DISTINCT " + std::string{ itemToSelect } + " FROM artist a") };
if (params.sortMethod == ArtistSortMethod::LastWritten
@@ -175,14 +175,14 @@ namespace Database
std::size_t Artist::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM artist");
}
std::vector<Artist::pointer> Artist::find(Session& session, const std::string& name)
{
session.checkSharedLocked();
session.checkReadTransaction();
Wt::Dbo::collection<Wt::Dbo::ptr<Artist>> res = session.getDboSession().find<Artist>()
.where("name = ?").bind(std::string{ name, 0, _maxNameLength })
@@ -193,33 +193,33 @@ namespace Database
Artist::pointer Artist::find(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<Artist>().where("mbid = ?").bind(std::string{ mbid.getAsString() }).resultValue();
}
Artist::pointer Artist::find(Session& session, ArtistId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<Artist>().where("id = ?").bind(id).resultValue();
}
bool Artist::exists(Session& session, ArtistId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT 1 FROM artist").where("id = ?").bind(id).resultValue() == 1;
}
RangeResults<ArtistId> Artist::findOrphanIds(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<ArtistId>("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<ArtistId>(query, range);
}
RangeResults<ArtistId> Artist::findIds(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery<ArtistId>(session, params) };
return Utils::execQuery<ArtistId>(query, params.range);
@@ -227,7 +227,7 @@ namespace Database
RangeResults<Artist::pointer> Artist::find(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery<Wt::Dbo::ptr<Artist>>(session, params) };
return Utils::execQuery<Artist::pointer>(query, params.range);
@@ -235,7 +235,7 @@ namespace Database
void Artist::find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery<Wt::Dbo::ptr<Artist>>(session, params) };
Utils::execQuery(query, params.range, func);
@@ -288,7 +288,7 @@ namespace Database
return Utils::execQuery<ArtistId>(query, range);
}
std::vector<std::vector<Cluster::pointer>> Artist::getClusterGroups(std::vector<ClusterType::pointer> clusterTypes, std::size_t size) const
std::vector<std::vector<Cluster::pointer>> Artist::getClusterGroups(std::vector<ClusterTypeId> clusterTypeIds, std::size_t size) const
{
assert(session());
@@ -300,8 +300,8 @@ namespace Database
where.And(WhereClause("a.id = ?")).bind(getId().toString());
{
WhereClause clusterClause;
for (auto clusterType : clusterTypes)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterType->getId().toString());
for (ClusterTypeId clusterTypeId : clusterTypeIds)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterTypeId.toString());
where.And(clusterClause);
}
@@ -44,7 +44,7 @@ namespace Database
void
AuthToken::removeExpiredTokens(Session& session, const Wt::WDateTime& now)
{
session.checkUniqueLocked();
session.checkWriteTransaction();
session.getDboSession().execute("DELETE FROM auth_token WHERE expiry < ?").bind(now);
}
@@ -52,7 +52,7 @@ namespace Database
AuthToken::pointer
AuthToken::find(Session& session, std::string_view value)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<AuthToken>()
.where("value = ?").bind(value)
+23 -18
View File
@@ -35,7 +35,7 @@ namespace Database
template <typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Cluster::FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<ResultType>("SELECT DISTINCT " + std::string{ itemToSelect } + " FROM cluster c") };
@@ -44,14 +44,19 @@ namespace Database
query.join("track_cluster t_c ON t_c.cluster_id = c.id");
query.join("track t ON t.id = t_c.track_id");
}
if (!params.clusterTypeName.empty())
query.join("cluster_type c_t ON c_t.id = c.cluster_type_id");
if (params.track.isValid())
query.where("t.id = ?").bind(params.track);
if (params.release.isValid())
query.where("t.release_id = ?").bind(params.release);
assert(!params.clusterType.isValid() || params.clusterTypeName.empty());
if (params.clusterType.isValid())
query.where("c.cluster_type_id = ?").bind(params.clusterType);
else if (!params.clusterTypeName.empty())
query.where("c_t.name = ?").bind(params.clusterTypeName);
return query;
}
@@ -85,14 +90,14 @@ namespace Database
std::size_t Cluster::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM cluster");
}
RangeResults<ClusterId> Cluster::findIds(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery<ClusterId>(session, params) };
return Utils::execQuery<ClusterId>(query, params.range);
@@ -100,15 +105,15 @@ namespace Database
RangeResults<Cluster::pointer> Cluster::find(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery<Wt::Dbo::ptr<Cluster>>(session, params) };
return Utils::execQuery<Cluster::pointer>(query, params.range);
}
RangeResults<ClusterId> Cluster::findOrphans(Session& session, std::optional<Range> range)
RangeResults<ClusterId> Cluster::findOrphanIds(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<ClusterId>("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<ClusterId>(query, range);
@@ -116,14 +121,14 @@ namespace Database
Cluster::pointer Cluster::find(Session& session, ClusterId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<Cluster>().where("id = ?").bind(id).resultValue();
}
std::size_t Cluster::computeTrackCount(Session& session, ClusterId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(t.id) FROM track t INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("t_c.cluster_id = ?").bind(id).resultValue();
@@ -131,7 +136,7 @@ namespace Database
std::size_t Cluster::computeReleaseCount(Session& session, ClusterId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(DISTINCT r.id) FROM release r INNER JOIN track t on t.release_id = r.id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("t_c.cluster_id = ?").bind(id).resultValue();
@@ -157,22 +162,22 @@ namespace Database
{
}
ClusterType::pointer ClusterType::create(Session& session, const std::string& name)
ClusterType::pointer ClusterType::create(Session& session, std::string_view name)
{
return session.getDboSession().add(std::unique_ptr<ClusterType> {new ClusterType{ name }});
}
std::size_t ClusterType::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM cluster_type");
}
RangeResults<ClusterTypeId> ClusterType::findOrphans(Session& session, std::optional<Range> range)
RangeResults<ClusterTypeId> ClusterType::findOrphanIds(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<ClusterTypeId>(
"SELECT c_t.id from cluster_type c_t"
@@ -184,7 +189,7 @@ namespace Database
RangeResults<ClusterTypeId> ClusterType::findUsed(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<ClusterTypeId>(
"SELECT DISTINCT c_t.id from cluster_type c_t")
@@ -195,21 +200,21 @@ namespace Database
ClusterType::pointer ClusterType::find(Session& session, std::string_view name)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<ClusterType>().where("name = ?").bind(std::string{ name }).resultValue();
}
ClusterType::pointer ClusterType::find(Session& session, ClusterTypeId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<ClusterType>().where("id = ?").bind(id).resultValue();
}
RangeResults<ClusterTypeId> ClusterType::find(Session& session, std::optional<Range> range)
RangeResults<ClusterTypeId> ClusterType::findIds(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<ClusterTypeId>("SELECT id from cluster_type") };
+10 -7
View File
@@ -24,7 +24,9 @@
#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "utils/Logger.hpp"
#include "utils/IConfig.hpp"
#include "utils/Service.hpp"
#include "utils/ILogger.hpp"
namespace Database
{
@@ -63,18 +65,18 @@ namespace Database
void prepare()
{
LMS_LOG(DB, DEBUG) << "Setting per-connection settings...";
LMS_LOG(DB, DEBUG, "Setting per-connection settings...");
executeSql("pragma journal_mode=WAL");
executeSql("pragma synchronous=normal");
executeSql("pragma analysis_limit=2000"); // to help make analyze command faster, 1000 does not seem to be enough to speed up all queries
LMS_LOG(DB, DEBUG) << "Setting per-connection settings done!";
LMS_LOG(DB, DEBUG, "Setting per-connection settings done!");
}
void optimize()
{
LMS_LOG(DB, DEBUG) << "connection close: Running pragma optimize...";
LMS_LOG(DB, DEBUG, "connection close: Running pragma optimize...");
executeSql("pragma optimize");
LMS_LOG(DB, DEBUG) << "connection close: pragma optimize complete";
LMS_LOG(DB, DEBUG, "connection close: pragma optimize complete");
}
std::filesystem::path _dbPath;
@@ -84,10 +86,11 @@ namespace Database
// Session living class handling the database and the login
Db::Db(const std::filesystem::path& dbPath, std::size_t connectionCount)
{
LMS_LOG(DB, INFO) << "Creating connection pool on file " << dbPath.string();
LMS_LOG(DB, INFO, "Creating connection pool on file " << dbPath.string());
auto connection{ std::make_unique<Connection>(dbPath.string()) };
// connection->setProperty("show-queries", "true");
if (IConfig * config{ Service<IConfig>::get() })// may not be here on testU
connection->setProperty("show-queries", config->getBool("db-show-queries", false) ? "true" : "false");
auto connectionPool{ std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), connectionCount) };
connectionPool->setTimeout(std::chrono::seconds{ 10 });
+15 -15
View File
@@ -140,25 +140,25 @@ namespace Database
Listen::pointer Listen::create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track, ScrobblingBackend backend, const Wt::WDateTime& dateTime)
{
session.checkUniqueLocked();
session.checkWriteTransaction();
return session.getDboSession().add(std::unique_ptr<Listen> {new Listen{ user, track, backend, dateTime }});
}
std::size_t Listen::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM listen");
}
Listen::pointer Listen::find(Session& session, ListenId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<Listen>().where("id = ?").bind(id).resultValue();
}
RangeResults<ListenId> Listen::find(Session& session, const FindParameters& parameters)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<ListenId>("SELECT id FROM listen")
.orderBy("date_time") };
@@ -177,7 +177,7 @@ namespace Database
Listen::pointer Listen::find(Session& session, UserId userId, TrackId trackId, ScrobblingBackend backend, const Wt::WDateTime& dateTime)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<Listen>()
.where("user_id = ?").bind(userId)
@@ -189,7 +189,7 @@ namespace Database
RangeResults<ArtistId> Listen::getTopArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createArtistsQuery(session.getDboSession(), userId, backend, clusterIds, linkType) };
auto collection{ query
@@ -201,7 +201,7 @@ namespace Database
RangeResults<ReleaseId> Listen::getTopReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createReleasesQuery(session.getDboSession(), userId, backend, clusterIds)
.orderBy("COUNT(r.id) DESC")
.groupBy("r.id") };
@@ -211,7 +211,7 @@ namespace Database
RangeResults<TrackId> Listen::getTopTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createTracksQuery(session.getDboSession(), userId, backend, clusterIds)
.orderBy("COUNT(t.id) DESC")
.groupBy("t.id") };
@@ -221,7 +221,7 @@ namespace Database
RangeResults<ArtistId> Listen::getRecentArtists(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createArtistsQuery(session.getDboSession(), userId, backend, clusterIds, linkType)
.groupBy("a.id").having("l.date_time = MAX(l.date_time)")
.orderBy("l.date_time DESC") };
@@ -231,7 +231,7 @@ namespace Database
RangeResults<ReleaseId> Listen::getRecentReleases(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createReleasesQuery(session.getDboSession(), userId, backend, clusterIds)
.groupBy("r.id").having("l.date_time = MAX(l.date_time)")
.orderBy("l.date_time DESC") };
@@ -241,7 +241,7 @@ namespace Database
RangeResults<TrackId> Listen::getRecentTracks(Session& session, UserId userId, ScrobblingBackend backend, const std::vector<ClusterId>& clusterIds, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createTracksQuery(session.getDboSession(), userId, backend, clusterIds)
.groupBy("t.id").having("l.date_time = MAX(l.date_time)")
.orderBy("l.date_time DESC") };
@@ -251,7 +251,7 @@ namespace Database
std::size_t Listen::getCount(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) from listen l")
.where("l.track_id = ?").bind(trackId)
@@ -262,7 +262,7 @@ namespace Database
std::size_t Listen::getCount(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId releaseId)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT IFNULL(MIN(count_result), 0)"
" FROM ("
@@ -279,7 +279,7 @@ namespace Database
Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId releaseId)
{
session.checkSharedLocked();
session.checkReadTransaction();
// TODO not pending remove?
return session.getDboSession().query<Wt::Dbo::ptr<Listen>>("SELECT l from listen l")
@@ -294,7 +294,7 @@ namespace Database
Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId)
{
session.checkSharedLocked();
session.checkReadTransaction();
// TODO not pending remove?
return session.getDboSession().query<Wt::Dbo::ptr<Listen>>("SELECT l from listen l")
.where("l.track_id = ?").bind(trackId)
+27 -8
View File
@@ -26,13 +26,13 @@
#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "utils/Exception.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Database
{
VersionInfo::pointer VersionInfo::getOrCreate(Session& session)
{
session.checkUniqueLocked();
session.checkWriteTransaction();
pointer versionInfo{ session.getDboSession().find<VersionInfo>() };
if (!versionInfo)
@@ -43,7 +43,7 @@ namespace Database
VersionInfo::pointer VersionInfo::get(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<VersionInfo>();
}
@@ -249,6 +249,24 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
session.getDboSession().execute("ALTER TABLE user ADD subsonic_enable_transcoding_by_default INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*User::defaultSubsonicEnableTranscodingByDefault*/0)) + ")");
}
void migrateFromV46(Session& session)
{
// add extra tags to parse
session.getDboSession().execute(R"(CREATE TABLE IF NOT EXISTS "cluster_type_backup" (
"id" integer primary key autoincrement,
"version" integer not null,
"name" text not null
);)");
session.getDboSession().execute("INSERT INTO cluster_type_backup SELECT id, version, name FROM cluster_type");
session.getDboSession().execute("DROP TABLE cluster_type");
session.getDboSession().execute("ALTER TABLE cluster_type_backup RENAME TO cluster_type");
session.getDboSession().execute("ALTER TABLE scan_settings ADD COLUMN extra_tags_to_scan TEXT");
// Just increment the scan version of the settings to make the next scheduled scan rescan everything
ScanSettings::get(session).modify()->incScanVersion();
}
void doDbMigration(Session& session)
{
static const std::string outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
@@ -273,20 +291,21 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
{43, migrateFromV43},
{44, migrateFromV44},
{45, migrateFromV45},
{46, migrateFromV46},
};
{
auto uniqueTransaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
Version version;
try
{
version = VersionInfo::getOrCreate(session)->getVersion();
LMS_LOG(DB, INFO) << "Database version = " << version << ", LMS binary version = " << LMS_DATABASE_VERSION;
LMS_LOG(DB, INFO, "Database version = " << version << ", LMS binary version = " << LMS_DATABASE_VERSION);
}
catch (std::exception& e)
{
LMS_LOG(DB, ERROR) << "Cannot get database version info: " << e.what();
LMS_LOG(DB, ERROR, "Cannot get database version info: " << e.what());
throw LmsException{ outdatedMsg };
}
@@ -298,7 +317,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
while (version < LMS_DATABASE_VERSION)
{
LMS_LOG(DB, INFO) << "Migrating database from version " << version << " to " << version + 1 << "...";
LMS_LOG(DB, INFO, "Migrating database from version " << version << " to " << version + 1 << "...");
auto itMigrationFunc{ migrationFunctions.find(version) };
assert(itMigrationFunc != std::cend(migrationFunctions));
@@ -306,7 +325,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
VersionInfo::get(session).modify()->setVersion(++version);
LMS_LOG(DB, INFO) << "Migration complete to version " << version;
LMS_LOG(DB, INFO, "Migration complete to version " << version);
}
}
}
@@ -26,7 +26,7 @@ namespace Database
class Session;
using Version = std::size_t;
static constexpr Version LMS_DATABASE_VERSION{ 46 };
static constexpr Version LMS_DATABASE_VERSION{ 47 };
class VersionInfo
{
public:
+30 -36
View File
@@ -26,7 +26,7 @@
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "services/database/User.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "SqlQuery.hpp"
#include "EnumSetTraits.hpp"
#include "IdTypeTraits.hpp"
@@ -39,7 +39,7 @@ namespace Database
template <typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Release::FindParameters& params)
{
auto query{ session.getDboSession().query<ResultType>("SELECT DISTINCT " + std::string{ itemToSelect } + " from release r") };
auto query{ session.getDboSession().query<ResultType>("SELECT " + std::string{ itemToSelect } + " from release r") };
if (params.sortMethod == ReleaseSortMethod::LastWritten
|| params.sortMethod == ReleaseSortMethod::Date
@@ -183,21 +183,6 @@ namespace Database
return query;
}
template <typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, const Release::FindParameters& params)
{
std::string_view itemToSelect;
if constexpr (std::is_same_v<ResultType, ReleaseId>)
itemToSelect = "r.id";
else if constexpr (std::is_same_v<ResultType, Wt::Dbo::ptr<Release>>)
itemToSelect = "r";
else
static_assert("Unhandled type");
return createQuery<ResultType>(session, itemToSelect, params);
}
}
Release::Release(const std::string& name, const std::optional<UUID>& MBID)
@@ -211,13 +196,15 @@ namespace Database
return session.getDboSession().add(std::unique_ptr<Release> {new Release{ name, MBID }});
}
std::vector<Release::pointer> Release::find(Session& session, const std::string& name)
std::vector<Release::pointer> Release::find(Session& session, const std::string& name, const std::filesystem::path& releaseDirectory)
{
session.checkUniqueLocked();
session.checkReadTransaction();
auto res{ session.getDboSession()
.find<Release>()
.where("name = ?").bind(std::string(name, 0, _maxNameLength))
.query<Wt::Dbo::ptr<Release>>("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()) + "%")
.resultList() };
return std::vector<Release::pointer>(res.begin(), res.end());
@@ -225,7 +212,7 @@ namespace Database
Release::pointer Release::find(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession()
.find<Release>()
@@ -235,7 +222,7 @@ namespace Database
Release::pointer Release::find(Session& session, ReleaseId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession()
.find<Release>()
@@ -245,20 +232,20 @@ namespace Database
bool Release::exists(Session& session, ReleaseId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT 1 FROM release").where("id = ?").bind(id).resultValue() == 1;
}
std::size_t Release::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM release");
}
RangeResults<ReleaseId> Release::findIdsOrderedByArtist(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
// TODO merge with find
auto query{ session.getDboSession().query<ReleaseId>(
@@ -273,7 +260,7 @@ namespace Database
RangeResults<ReleaseId> Release::findOrphanIds(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<ReleaseId>("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<ReleaseId>(query, range);
@@ -281,28 +268,35 @@ namespace Database
RangeResults<Release::pointer> Release::find(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery<Wt::Dbo::ptr<Release>>(session, params) };
auto query{ createQuery<Wt::Dbo::ptr<Release>>(session, "DISTINCT r", params) };
return Utils::execQuery<pointer>(query, params.range);
}
void Release::find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery<Wt::Dbo::ptr<Release>>(session, params) };
auto query{ createQuery<Wt::Dbo::ptr<Release>>(session, "DISTINCT r", params) };
Utils::execQuery<pointer>(query, params.range, func);
}
RangeResults<ReleaseId> Release::findIds(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery<ReleaseId>(session, params) };
auto query{ createQuery<ReleaseId>(session, "DISTINCT r.id", params) };
return Utils::execQuery<ReleaseId>(query, params.range);
}
std::size_t Release::getCount(Session& session, const FindParameters& params)
{
session.checkReadTransaction();
return createQuery<int>(session, "COUNT(DISTINCT r.id)", params).resultValue();
}
std::size_t Release::getDiscCount() const
{
assert(session());
@@ -487,7 +481,7 @@ namespace Database
return query.resultValue();
}
std::vector<std::vector<Cluster::pointer>> Release::getClusterGroups(const std::vector<ClusterType::pointer>& clusterTypes, std::size_t size) const
std::vector<std::vector<Cluster::pointer>> Release::getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const
{
assert(session());
@@ -500,8 +494,8 @@ namespace Database
where.And(WhereClause("r.id = ?")).bind(getId().toString());
{
WhereClause clusterClause;
for (auto clusterType : clusterTypes)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterType->getId().toString());
for (const ClusterTypeId clusterTypeId : clusterTypeIds)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterTypeId.toString());
where.And(clusterClause);
}
oss << " " << where.get();
+38 -111
View File
@@ -22,131 +22,58 @@
#include <Wt/Dbo/WtSqlTraits.h>
#include "utils/Path.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/String.hpp"
#include "services/database/Cluster.hpp"
#include "services/database/Session.hpp"
namespace {
const std::set<std::string> defaultClusterTypeNames =
namespace Database
{
"GENRE",
"ALBUMGROUPING",
"MOOD",
"ALBUMMOOD",
};
ScanSettings::pointer ScanSettings::get(Session& session)
{
session.checkReadTransaction();
}
return session.getDboSession().find<ScanSettings>().resultValue();
}
namespace Database {
std::vector<std::filesystem::path> ScanSettings::getAudioFileExtensions() const
{
const auto extensions{ StringUtils::splitString(_audioFileExtensions, " ") };
void
ScanSettings::init(Session& session)
{
session.checkUniqueLocked();
std::vector<std::filesystem::path> res(std::cbegin(extensions), std::cend(extensions));
std::sort(std::begin(res), std::end(res));
res.erase(std::unique(std::begin(res), std::end(res)), std::end(res));
pointer settings {get(session)};
if (settings)
return;
return res;
}
settings = session.getDboSession().add(std::make_unique<ScanSettings>());
settings.modify()->setClusterTypes(session, defaultClusterTypeNames );
}
void ScanSettings::addAudioFileExtension(const std::filesystem::path& ext)
{
_audioFileExtensions += " " + ext.string();
}
ScanSettings::pointer
ScanSettings::get(Session& session)
{
session.checkSharedLocked();
std::vector<std::string_view> ScanSettings::getExtraTagsToScan() const
{
return StringUtils::splitString(_extraTagsToScan, ";");
}
return session.getDboSession().find<ScanSettings>().resultValue();
}
void ScanSettings::setMediaDirectory(const std::filesystem::path& p)
{
_mediaDirectory = StringUtils::stringTrimEnd(p.string(), "/\\");
}
std::vector<std::filesystem::path>
ScanSettings::getAudioFileExtensions() const
{
const auto extensions {StringUtils::splitString(_audioFileExtensions, " ")};
void ScanSettings::setExtraTagsToScan(const std::vector<std::string_view>& extraTags)
{
std::string newTagsToScan{ StringUtils::joinStrings(extraTags, ";") };
if (newTagsToScan != _extraTagsToScan)
incScanVersion();
std::vector<std::filesystem::path> res (std::cbegin(extensions), std::cend(extensions));
std::sort(std::begin(res), std::end(res));
res.erase(std::unique( std::begin(res), std::end(res)), std::end(res));
return res;
}
void
ScanSettings::addAudioFileExtension(const std::filesystem::path& ext)
{
_audioFileExtensions += " " + ext.string();
}
std::vector<ClusterType::pointer>
ScanSettings::getClusterTypes() const
{
return std::vector<ClusterType::pointer>(std::cbegin(_clusterTypes), std::cend(_clusterTypes));
}
void
ScanSettings::setMediaDirectory(const std::filesystem::path& p)
{
_mediaDirectory = StringUtils::stringTrimEnd(p.string(), "/\\");
}
template <typename It>
std::set<std::string> getNames(It begin, It end)
{
std::set<std::string> names;
std::transform(begin, end, std::inserter(names, std::cbegin(names)),
[](const ClusterType::pointer& clusterType)
{
return clusterType->getName();
});
return names;
}
void
ScanSettings::setClusterTypes(Session& session, const std::set<std::string>& clusterTypeNames)
{
session.checkUniqueLocked();
bool needRescan {};
// Create any missing cluster type
for (const std::string& clusterTypeName : clusterTypeNames)
{
ClusterType::pointer clusterType {ClusterType::find(session, clusterTypeName)};
if (!clusterType)
{
LMS_LOG(DB, INFO) << "Creating cluster type " << clusterTypeName;
clusterType = session.create<ClusterType>(clusterTypeName);
_clusterTypes.insert(getDboPtr(clusterType));
needRescan = true;
}
}
// Delete no longer existing cluster types
for (Wt::Dbo::ptr<ClusterType> clusterType : _clusterTypes)
{
if (std::none_of(clusterTypeNames.begin(), clusterTypeNames.end(),
[clusterType](const std::string& name) { return name == clusterType->getName(); }))
{
LMS_LOG(DB, INFO) << "Deleting cluster type " << clusterType->getName();
clusterType.remove();
}
}
if (needRescan)
_scanVersion += 1;
}
void
ScanSettings::incScanVersion()
{
_scanVersion += 1;
}
_extraTagsToScan = std::move(newTagsToScan);
}
void ScanSettings::incScanVersion()
{
_scanVersion += 1;
}
} // namespace Database
+40 -45
View File
@@ -22,7 +22,7 @@
#include <cassert>
#include "utils/Exception.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "services/database/Artist.hpp"
#include "services/database/AuthToken.hpp"
@@ -39,6 +39,7 @@
#include "services/database/TrackArtistLink.hpp"
#include "services/database/TrackList.hpp"
#include "services/database/TrackFeatures.hpp"
#include "services/database/TransactionChecker.hpp"
#include "services/database/User.hpp"
#include "EnumSetTraits.hpp"
#include "Migration.hpp"
@@ -46,6 +47,29 @@
namespace Database
{
WriteTransaction::WriteTransaction(RecursiveSharedMutex& mutex, Wt::Dbo::Session& session)
: _lock{ mutex },
_transaction{ session }
{
TransactionChecker::pushWriteTransaction(_transaction.session());
}
WriteTransaction::~WriteTransaction()
{
TransactionChecker::popWriteTransaction(_transaction.session());
}
ReadTransaction::ReadTransaction(Wt::Dbo::Session& session)
: _transaction{ session }
{
TransactionChecker::pushReadTransaction(_transaction.session());
}
ReadTransaction::~ReadTransaction()
{
TransactionChecker::popReadTransaction(_transaction.session());
}
Session::Session(Db& db)
: _db{ db }
{
@@ -71,54 +95,32 @@ namespace Database
_session.mapClass<User>("user");
}
UniqueTransaction::UniqueTransaction(RecursiveSharedMutex& mutex, Wt::Dbo::Session& session)
: _lock{ mutex },
_transaction{ session }
WriteTransaction Session::createWriteTransaction()
{
return WriteTransaction{ _db.getMutex(), _session };
}
SharedTransaction::SharedTransaction(RecursiveSharedMutex& mutex, Wt::Dbo::Session& session)
: _lock{ mutex },
_transaction{ session }
ReadTransaction Session::createReadTransaction()
{
}
void Session::checkUniqueLocked()
{
assert(_db.getMutex().isUniqueLocked());
}
void Session::checkSharedLocked()
{
assert(_db.getMutex().isSharedLocked());
}
UniqueTransaction Session::createUniqueTransaction()
{
return UniqueTransaction{ _db.getMutex(), _session };
}
SharedTransaction Session::createSharedTransaction()
{
return SharedTransaction{ _db.getMutex(), _session };
return ReadTransaction{ _session };
}
void Session::prepareTables()
{
LMS_LOG(DB, INFO) << "Preparing tables...";
LMS_LOG(DB, INFO, "Preparing tables...");
// Initial creation case
try
{
_session.createTables();
LMS_LOG(DB, INFO) << "Tables created";
LMS_LOG(DB, INFO, "Tables created");
}
catch (Wt::Dbo::Exception& e)
{
LMS_LOG(DB, DEBUG) << "Cannot create tables: " << e.what();
LMS_LOG(DB, DEBUG, "Cannot create tables: " << e.what());
if (std::string_view{ e.what() }.find("already exists") == std::string_view::npos)
{
LMS_LOG(DB, ERROR) << "Cannot create tables: " << e.what();
LMS_LOG(DB, ERROR, "Cannot create tables: " << e.what());
throw e;
}
}
@@ -127,7 +129,7 @@ namespace Database
// Indexes
{
auto uniqueTransaction{ createUniqueTransaction() };
auto transaction{ createWriteTransaction() };
_session.execute("CREATE INDEX IF NOT EXISTS artist_name_idx ON artist(name)");
_session.execute("CREATE INDEX IF NOT EXISTS artist_sort_name_nocase_idx ON artist(sort_name COLLATE NOCASE)");
_session.execute("CREATE INDEX IF NOT EXISTS artist_mbid_idx ON artist(mbid)");
@@ -169,33 +171,26 @@ namespace Database
_session.execute("CREATE INDEX IF NOT EXISTS starred_track_user_backend_idx ON starred_track(user_id,backend)");
_session.execute("CREATE INDEX IF NOT EXISTS starred_track_track_user_backend_idx ON starred_track(track_id,user_id,backend)");
}
// Initial settings tables
{
auto uniqueTransaction{ createUniqueTransaction() };
ScanSettings::init(*this);
}
}
void Session::analyze()
{
LMS_LOG(DB, INFO) << "Analyzing database...";
LMS_LOG(DB, INFO, "Analyzing database...");
{
auto uniqueTransaction{ createUniqueTransaction() };
auto transaction{ createWriteTransaction() };
_session.execute("ANALYZE");
}
LMS_LOG(DB, INFO) << "Database Analyze complete";
LMS_LOG(DB, INFO, "Database Analyze complete");
}
void Session::optimize()
{
LMS_LOG(DB, INFO) << "Optimizing database...";
LMS_LOG(DB, INFO, "Optimizing database...");
{
auto uniqueTransaction{ createUniqueTransaction() };
auto transaction{ createWriteTransaction() };
_session.execute("PRAGMA optimize");
}
LMS_LOG(DB, INFO) << "Database optimizing complete";
LMS_LOG(DB, INFO, "Database optimizing complete");
}
} // namespace Database
+8 -8
View File
@@ -67,11 +67,11 @@ WhereClause::get() const
}
WhereClause&
WhereClause::bind(const std::string& bindArg)
WhereClause::bind(std::string_view bindArg)
{
assert(_bindArgs.size() < static_cast<std::size_t>(std::count(_clause.begin(), _clause.end(), '?')));
_bindArgs.push_back(bindArg);
_bindArgs.push_back(std::string{ bindArg });
return *this;
}
@@ -102,8 +102,8 @@ SelectStatement::And(const std::string& statement)
{
_statement.push_back(statement);
_statement.sort();
_statement.unique();
std::sort(_statement.begin(), _statement.end());
_statement.erase(std::unique(_statement.begin(), _statement.end()), _statement.end());
return *this;
}
@@ -113,7 +113,7 @@ SelectStatement::get() const
{
std::string res = "SELECT ";
for (std::list<std::string>::const_iterator it = _statement.begin(); it != _statement.end(); ++it)
for (auto it = _statement.begin(); it != _statement.end(); ++it)
{
if (it != _statement.begin())
res += ",";
@@ -150,8 +150,8 @@ FromClause::And(const FromClause& clause)
_clause.push_back(fromClause);
}
_clause.sort();
_clause.unique();
std::sort(_clause.begin(), _clause.end());
_clause.erase(std::unique(_clause.begin(), _clause.end()), _clause.end());
return *this;
}
@@ -164,7 +164,7 @@ FromClause::get() const
if (!_clause.empty())
{
oss << "FROM ";
for (std::list<std::string>::const_iterator it = _clause.begin(); it != _clause.end(); ++it) {
for (auto it = _clause.begin(); it != _clause.end(); ++it) {
if (it != _clause.begin())
oss << ",";
+6 -6
View File
@@ -19,7 +19,7 @@
#pragma once
#include <list>
#include <vector>
#include <string>
@@ -33,14 +33,14 @@ class WhereClause
WhereClause& Or(const WhereClause& clause);
// Arguments binding (for each '?' in where clause)
WhereClause& bind(const std::string& arg);
WhereClause& bind(std::string_view arg);
std::string get() const;
const std::list<std::string>& getBindArgs() const {return _bindArgs;}
const std::vector<std::string>& getBindArgs() const {return _bindArgs;}
private:
std::string _clause; // WHERE clause
std::list<std::string> _bindArgs;
std::vector<std::string> _bindArgs;
};
class InnerJoinClause
@@ -81,7 +81,7 @@ class SelectStatement
std::string get() const;
private:
std::list<std::string> _statement;
std::vector<std::string> _statement;
};
class FromClause
@@ -95,7 +95,7 @@ class FromClause
std::string get() const;
private:
std::list<std::string> _clause;
std::vector<std::string> _clause;
};
class SqlQuery
@@ -43,19 +43,30 @@ namespace Database
std::size_t StarredArtist::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM starred_artist");
}
StarredArtist::pointer StarredArtist::find(Session& session, StarredArtistId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<StarredArtist>().where("id = ?").bind(id).resultValue();
}
StarredArtist::pointer StarredArtist::find(Session& session, ArtistId artistId, UserId userId)
{
session.checkReadTransaction();
return session.getDboSession().query<Wt::Dbo::ptr<StarredArtist>>("SELECT s_a from starred_artist s_a")
.join("user u ON u.id = s_a.user_id")
.where("s_a.artist_id = ?").bind(artistId)
.where("s_a.user_id = ?").bind(userId)
.where("s_a.backend = u.feedback_backend")
.resultValue();
}
StarredArtist::pointer StarredArtist::find(Session& session, ArtistId artistId, UserId userId, FeedbackBackend backend)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<StarredArtist>()
.where("artist_id = ?").bind(artistId)
.where("user_id = ?").bind(userId)
@@ -43,19 +43,30 @@ namespace Database
std::size_t StarredRelease::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM starred_release");
}
StarredRelease::pointer StarredRelease::find(Session& session, StarredReleaseId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<StarredRelease>().where("id = ?").bind(id).resultValue();
}
StarredRelease::pointer StarredRelease::find(Session& session, ReleaseId releaseId, UserId userId)
{
session.checkReadTransaction();
return session.getDboSession().query<Wt::Dbo::ptr<StarredRelease>>("SELECT s_r from starred_release s_r")
.join("user u ON u.id = s_r.user_id")
.where("s_r.release_id = ?").bind(releaseId)
.where("s_r.user_id = ?").bind(userId)
.where("s_r.backend = u.feedback_backend")
.resultValue();
}
StarredRelease::pointer StarredRelease::find(Session& session, ReleaseId releaseId, UserId userId, FeedbackBackend backend)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<StarredRelease>()
.where("release_id = ?").bind(releaseId)
.where("user_id = ?").bind(userId)
@@ -43,19 +43,30 @@ namespace Database
std::size_t StarredTrack::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM starred_track");
}
StarredTrack::pointer StarredTrack::find(Session& session, StarredTrackId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<StarredTrack>().where("id = ?").bind(id).resultValue();
}
StarredTrack::pointer StarredTrack::find(Session& session, TrackId trackId, UserId userId)
{
session.checkReadTransaction();
return session.getDboSession().query<Wt::Dbo::ptr<StarredTrack>>("SELECT s_t from starred_track s_t")
.join("user u ON u.id = s_t.user_id")
.where("s_t.track_id = ?").bind(trackId)
.where("s_t.user_id = ?").bind(userId)
.where("s_t.backend = u.feedback_backend")
.resultValue();
}
StarredTrack::pointer StarredTrack::find(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<StarredTrack>()
.where("track_id = ?").bind(trackId)
.where("user_id = ?").bind(userId)
@@ -63,9 +74,18 @@ namespace Database
.resultValue();
}
bool StarredTrack::exists(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend)
{
return session.getDboSession().query<int>("SELECT 1 from starred_track")
.where("track_id = ?").bind(trackId)
.where("user_id = ?").bind(userId)
.where("backend = ?").bind(backend)
.resultValue() == 1;
}
RangeResults<StarredTrackId> StarredTrack::find(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<StarredTrackId>("SELECT DISTINCT s_t.id FROM starred_track s_t") };
+22 -22
View File
@@ -28,7 +28,7 @@
#include "services/database/TrackFeatures.hpp"
#include "services/database/Session.hpp"
#include "services/database/User.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "IdTypeTraits.hpp"
#include "SqlQuery.hpp"
@@ -42,7 +42,7 @@ namespace Database
template <typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Track::FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
std::string selectStatement{ params.distinct ? "SELECT DISTINCT" : "SELECT" };
auto query{ session.getDboSession().query<ResultType>(selectStatement + " " + std::string{ itemToSelect } + " FROM track t") };
@@ -200,21 +200,21 @@ namespace Database
std::size_t
Track::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM track");
}
Track::pointer Track::findByPath(Session& session, const std::filesystem::path& p)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<Track>().where("file_path = ?").bind(p.string()).resultValue();
}
Track::pointer Track::find(Session& session, TrackId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<Track>()
.where("id = ?").bind(id)
@@ -223,14 +223,14 @@ namespace Database
bool Track::exists(Session& session, TrackId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT 1 from track").where("id = ?").bind(id).resultValue() == 1;
}
std::vector<Track::pointer> Track::findByMBID(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto res{ session.getDboSession().find<Track>()
.where("mbid = ?").bind(std::string {mbid.getAsString()})
@@ -241,7 +241,7 @@ namespace Database
std::vector<Track::pointer> Track::findByRecordingMBID(Session& session, const UUID& mbid)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto res{ session.getDboSession().find<Track>()
.where("recording_mbid = ?").bind(std::string {mbid.getAsString()})
@@ -253,7 +253,7 @@ namespace Database
RangeResults<Track::PathResult> Track::findPaths(Session& session, std::optional<Range> range)
{
using QueryResultType = std::tuple<TrackId, std::string>;
session.checkSharedLocked();
session.checkReadTransaction();
// TODO Dbo traits on filesystem
auto query{ session.getDboSession().query<QueryResultType>("SELECT id, file_path FROM track") };
@@ -276,7 +276,7 @@ namespace Database
RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<TrackId>("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") };
@@ -286,7 +286,7 @@ namespace Database
RangeResults<TrackId> Track::findIdsWithRecordingMBIDAndMissingFeatures(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<TrackId>("SELECT t.id FROM track t")
.where("LENGTH(t.recording_mbid) > 0")
@@ -314,7 +314,7 @@ namespace Database
RangeResults<TrackId> Track::findIds(Session& session, const FindParameters& parameters)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery<TrackId>(session, parameters) };
return Utils::execQuery<TrackId>(query, parameters.range);
@@ -322,7 +322,7 @@ namespace Database
RangeResults<Track::pointer> Track::find(Session& session, const FindParameters& parameters)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery<Wt::Dbo::ptr<Track>>(session, parameters) };
return Utils::execQuery<Track::pointer>(query, parameters.range);
@@ -330,7 +330,7 @@ namespace Database
void Track::find(Session& session, const FindParameters& params, std::function<void(const Track::pointer&)> func)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery<Wt::Dbo::ptr<Track>>(session, params)};
Utils::execQuery(query, params.range, func);
@@ -339,7 +339,7 @@ namespace Database
RangeResults<TrackId> Track::findSimilarTrackIds(Session& session, const std::vector<TrackId>& tracks, std::optional<Range> range)
{
assert(!tracks.empty());
session.checkSharedLocked();
session.checkReadTransaction();
std::ostringstream oss;
for (std::size_t i{}; i < tracks.size(); ++i)
@@ -479,7 +479,7 @@ namespace Database
return std::vector<TrackArtistLink::pointer>(_trackArtistLinks.begin(), _trackArtistLinks.end());
}
std::vector<std::vector<Cluster::pointer>> Track::getClusterGroups(const std::vector<ClusterType::pointer>& clusterTypes, std::size_t size) const
std::vector<std::vector<Cluster::pointer>> Track::getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const
{
assert(self());
assert(session());
@@ -493,8 +493,8 @@ namespace Database
where.And(WhereClause("t.id = ?")).bind(getId().toString());
{
WhereClause clusterClause;
for (auto clusterType : clusterTypes)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterType->getId().toString());
for (ClusterTypeId clusterTypeId : clusterTypeIds)
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterTypeId.toString());
where.And(clusterClause);
}
oss << " " << where.get();
@@ -514,8 +514,8 @@ namespace Database
}
std::vector<std::vector<Cluster::pointer>> res;
for (auto cluster_list : clusters)
res.push_back(cluster_list.second);
for (const auto& [type, clusters] : clusters)
res.push_back(clusters);
return res;
}
@@ -524,7 +524,7 @@ namespace Database
{
std::ostream& operator<<(std::ostream& os, const TrackInfo& trackInfo)
{
auto transaction{ trackInfo.session.createSharedTransaction() };
auto transaction{ trackInfo.session.createReadTransaction() };
const Track::pointer track{ Track::find(trackInfo.session, trackInfo.trackId) };
if (track)
@@ -536,7 +536,7 @@ namespace Database
for (auto artist : track->getArtists({ TrackArtistLinkType::Artist }))
os << " - " << artist->getName();
for (auto cluster : track->getClusters())
os << " {" + cluster->getType()->getName() << "-" << cluster->getName() << "}";
os << " {" << cluster->getType()->getName() << "-" << cluster->getName() << "}";
}
else
{
@@ -32,7 +32,7 @@ namespace Database
{
Wt::Dbo::Query<TrackArtistLinkId> createQuery(Session& session, const TrackArtistLink::FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<TrackArtistLinkId>("SELECT DISTINCT t_a_l.id FROM track_artist_link t_a_l") };
@@ -65,7 +65,7 @@ namespace Database
TrackArtistLink::pointer TrackArtistLink::create(Session& session, ObjectPtr<Track> track, ObjectPtr<Artist> artist, TrackArtistLinkType type, std::string_view subType)
{
session.checkUniqueLocked();
session.checkWriteTransaction();
TrackArtistLink::pointer res{ session.getDboSession().add(std::make_unique<TrackArtistLink>(track, artist, type, subType)) };
session.getDboSession().flush();
@@ -75,13 +75,13 @@ namespace Database
TrackArtistLink::pointer TrackArtistLink::find(Session& session, TrackArtistLinkId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<TrackArtistLink>().where("id = ?").bind(id).resultValue();
}
RangeResults<TrackArtistLinkId> TrackArtistLink::find(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ createQuery(session, params) };
return Utils::execQuery<TrackArtistLinkId>(query, params.range);
@@ -89,7 +89,7 @@ namespace Database
EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto res{ session.getDboSession().query<TrackArtistLinkType>("SELECT DISTINCT type from track_artist_link").resultList() };
@@ -98,7 +98,7 @@ namespace Database
EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto res{ session.getDboSession()
.query<TrackArtistLinkType>("SELECT DISTINCT type from track_artist_link")
@@ -40,14 +40,14 @@ namespace Database {
std::size_t TrackBookmark::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM track_bookmark");
}
RangeResults<TrackBookmarkId> TrackBookmark::find(Session& session, UserId userId, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<TrackBookmarkId>("SELECT id from track_bookmark")
.where("user_id = ?").bind(userId) };
@@ -57,7 +57,7 @@ namespace Database {
TrackBookmark::pointer TrackBookmark::find(Session& session, UserId userId, TrackId trackId)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<TrackBookmark>()
.where("user_id = ?").bind(userId)
@@ -67,7 +67,7 @@ namespace Database {
TrackBookmark::pointer TrackBookmark::find(Session& session, TrackBookmarkId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<TrackBookmark>()
.where("id = ?").bind(id)
@@ -24,7 +24,7 @@
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "IdTypeTraits.hpp"
#include "Utils.hpp"
@@ -43,14 +43,14 @@ namespace Database {
std::size_t TrackFeatures::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM track_features");
}
TrackFeatures::pointer TrackFeatures::find(Session& session, TrackFeaturesId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<TrackFeatures>()
.where("id = ?").bind(id)
@@ -59,7 +59,7 @@ namespace Database {
TrackFeatures::pointer TrackFeatures::find(Session& session, TrackId trackId)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<TrackFeatures>()
.where("track_id = ?").bind(trackId)
@@ -68,7 +68,7 @@ namespace Database {
RangeResults<TrackFeaturesId> TrackFeatures::find(Session& session, std::optional<Range> range)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<TrackFeaturesId>("SELECT id from track_features") };
@@ -111,7 +111,7 @@ namespace Database {
}
catch (boost::property_tree::ptree_error& error)
{
LMS_LOG(DB, ERROR) << "Track " << _track.id() << ": ptree exception: " << error.what();
LMS_LOG(DB, ERROR, "Track " << _track.id() << ": ptree exception: " << error.what());
res.clear();
}
+10 -10
View File
@@ -20,7 +20,7 @@
#include <cassert>
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "services/database/Artist.hpp"
#include "services/database/Cluster.hpp"
@@ -53,7 +53,7 @@ namespace Database
std::size_t TrackList::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM tracklist");
}
@@ -61,7 +61,7 @@ namespace Database
TrackList::pointer TrackList::find(Session& session, std::string_view name, TrackListType type, UserId userId)
{
session.checkSharedLocked();
session.checkReadTransaction();
assert(userId.isValid());
return session.getDboSession().find<TrackList>()
@@ -72,7 +72,7 @@ namespace Database
RangeResults<TrackListId> TrackList::find(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<TrackListId>("SELECT DISTINCT t_l.id FROM tracklist t_l") };
@@ -122,7 +122,7 @@ namespace Database
TrackList::pointer TrackList::find(Session& session, TrackListId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<TrackList>().where("id = ?").bind(id).resultValue();
}
@@ -187,12 +187,12 @@ namespace Database
return std::vector<Cluster::pointer>(res.begin(), res.end());
}
std::vector<std::vector<Cluster::pointer>> TrackList::getClusterGroups(const std::vector<ClusterType::pointer>& clusterTypes, std::size_t size) const
std::vector<std::vector<Cluster::pointer>> TrackList::getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const
{
assert(session());
std::vector<std::vector<Cluster::pointer>> res;
if (clusterTypes.empty())
if (clusterTypeIds.empty())
return res;
auto query{ session()->query<Wt::Dbo::ptr<Cluster>>("SELECT c from cluster c") };
@@ -208,12 +208,12 @@ namespace Database
std::ostringstream oss;
oss << "c_type.id IN (";
bool first{ true };
for (auto clusterType : clusterTypes)
for (ClusterTypeId clusterTypeId : clusterTypeIds)
{
if (!first)
oss << ", ";
oss << "?";
query.bind(clusterType->getId());
query.bind(clusterTypeId);
first = false;
}
oss << ")";
@@ -323,7 +323,7 @@ namespace Database
TrackListEntry::pointer TrackListEntry::getById(Session& session, TrackListEntryId id)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<TrackListEntry>().where("id = ?").bind(id).resultValue();
}
@@ -0,0 +1,108 @@
/*
* Copyright (C) 2023 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "services/database/TransactionChecker.hpp"
#include <cassert>
#include "services/database/Session.hpp"
#if !defined(NDEBUG)
#define LMS_CHECK_TRANSACTION_ACCESSES 1
#else
#define LMS_CHECK_TRANSACTION_ACCESSES 0
#endif
namespace Database
{
#if LMS_CHECK_TRANSACTION_ACCESSES
namespace
{
struct StackEntry
{
TransactionChecker::TransactionType type;
Wt::Dbo::Session* session{};
};
static thread_local std::vector<StackEntry> transactionStack;
}
#endif
void TransactionChecker::pushWriteTransaction(Wt::Dbo::Session& session)
{
pushTransaction(TransactionType::Write, session);
}
void TransactionChecker::pushReadTransaction(Wt::Dbo::Session& session)
{
pushTransaction(TransactionType::Read, session);
}
void TransactionChecker::popWriteTransaction(Wt::Dbo::Session& session)
{
popTransaction(TransactionType::Write, session);
}
void TransactionChecker::popReadTransaction(Wt::Dbo::Session& session)
{
popTransaction(TransactionType::Read, session);
}
void TransactionChecker::pushTransaction([[maybe_unused]] TransactionType type, [[maybe_unused]] Wt::Dbo::Session& session)
{
#if LMS_CHECK_TRANSACTION_ACCESSES
assert(transactionStack.empty() || transactionStack.back().session == &session);
transactionStack.push_back(StackEntry{ type, &session });
#endif // LMS_CHECK_TRANSACTION_ACCESSES
}
void TransactionChecker::popTransaction([[maybe_unused]] TransactionType type, [[maybe_unused]] Wt::Dbo::Session& session)
{
#if LMS_CHECK_TRANSACTION_ACCESSES
assert(!transactionStack.empty());
assert(transactionStack.back().type == type);
assert(transactionStack.back().session == &session);
transactionStack.pop_back();
#endif // LMS_CHECK_TRANSACTION_ACCESSES
}
void TransactionChecker::checkWriteTransaction([[maybe_unused]] Wt::Dbo::Session& session)
{
assert(!transactionStack.empty());
assert(transactionStack.back().type == TransactionType::Write);
assert(transactionStack.back().session == &session);
}
void TransactionChecker::checkWriteTransaction(Session& session)
{
checkWriteTransaction(session.getDboSession());
}
void TransactionChecker::checkReadTransaction([[maybe_unused]] Wt::Dbo::Session& session)
{
assert(!transactionStack.empty());
assert(transactionStack.back().session == &session);
}
void TransactionChecker::checkReadTransaction(Session& session)
{
checkReadTransaction(session.getDboSession());
}
}
+4 -4
View File
@@ -23,7 +23,7 @@
#include "services/database/Release.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "IdTypeTraits.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp"
@@ -42,14 +42,14 @@ namespace Database {
std::size_t User::getCount(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().query<int>("SELECT COUNT(*) FROM user");
}
RangeResults<UserId> User::find(Session& session, const FindParameters& params)
{
session.checkSharedLocked();
session.checkReadTransaction();
auto query{ session.getDboSession().query<UserId>("SELECT id FROM user") };
@@ -63,7 +63,7 @@ namespace Database {
User::pointer User::findDemoUser(Session& session)
{
session.checkSharedLocked();
session.checkReadTransaction();
return session.getDboSession().find<User>().where("type = ?").bind(UserType::DEMO).resultValue();
}
@@ -100,7 +100,7 @@ namespace Database
// Get the cluster of the tracks made by this artist
// Each clusters are grouped by cluster type, sorted by the number of occurence
// size is the max number of cluster per cluster type
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(std::vector<ObjectPtr<ClusterType>> clusterTypes, std::size_t size) const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(std::vector<ClusterTypeId> clusterTypeIds, std::size_t size) const;
void setName(std::string_view name) { _name = name; }
void setMBID(const std::optional<UUID>& mbid) { _MBID = mbid ? mbid->getAsString() : ""; }
@@ -38,7 +38,6 @@ namespace Database {
class Track;
class ClusterType;
class ScanSettings;
class Session;
class Cluster final : public Object<Cluster, ClusterId>
@@ -47,12 +46,14 @@ namespace Database {
struct FindParameters
{
std::optional<Range> range;
ClusterTypeId clusterType; // if non empty, clusters that belong to this cluster type
ClusterTypeId clusterType; // if non empty, clusters that belong to this cluster type
std::string clusterTypeName; // if non empty, clusters that belong to this cluster type
TrackId track; // if set, clusters involved in this track
ReleaseId release; // if set, clusters involved in this release
FindParameters& setRange(std::optional<Range> _range) { range = _range; return *this; }
FindParameters& setClusterType(ClusterTypeId _clusterType) { clusterType = _clusterType; return *this; }
FindParameters& setClusterTypeName(std::string_view _name) { clusterTypeName = _name; return *this; }
FindParameters& setTrack(TrackId _track) { track = _track; return *this; }
FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; }
};
@@ -65,7 +66,7 @@ namespace Database {
static RangeResults<pointer> find(Session& session, const FindParameters& params);
static void find(Session& session, const FindParameters& params, std::function<void(const pointer& cluster)> _func);
static pointer find(Session& session, ClusterId id);
static RangeResults<ClusterId> findOrphans(Session& session, std::optional<Range> range = std::nullopt);
static RangeResults<ClusterId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
// May be very slow
static std::size_t computeTrackCount(Session& session, ClusterId id);
@@ -117,16 +118,16 @@ namespace Database {
// Getters
static std::size_t getCount(Session& session);
static RangeResults<ClusterTypeId> find(Session& session, std::optional<Range> range = std::nullopt);
static RangeResults<ClusterTypeId> findIds(Session& session, std::optional<Range> range = std::nullopt);
static pointer find(Session& session, std::string_view name);
static pointer find(Session& session, ClusterTypeId id);
static RangeResults<ClusterTypeId> findOrphans(Session& session, std::optional<Range> range = std::nullopt);
static RangeResults<ClusterTypeId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
static RangeResults<ClusterTypeId> findUsed(Session& session, std::optional<Range> range = std::nullopt);
static void remove(Session& session, const std::string& name);
// Accessors
const std::string& getName() const { return _name; }
std::string_view getName() const { return _name; }
std::vector<Cluster::pointer> getClusters() const;
Cluster::pointer getCluster(const std::string& name) const;
@@ -135,19 +136,17 @@ namespace Database {
{
Wt::Dbo::field(a, _name, "name");
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToOne, "cluster_type");
Wt::Dbo::belongsTo(a, _scanSettings, "scan_settings", Wt::Dbo::OnDeleteCascade);
}
private:
friend class Session;
ClusterType(std::string_view name);
static pointer create(Session& session, const std::string& name);
static pointer create(Session& session, std::string_view name);
static const std::size_t _maxNameLength = 128;
std::string _name;
Wt::Dbo::collection< Wt::Dbo::ptr<Cluster> > _clusters;
Wt::Dbo::ptr<ScanSettings> _scanSettings;
};
} // namespace Database
@@ -22,6 +22,7 @@
#include <Wt/WSignal.h>
#include <Wt/Dbo/ptr.h>
#include "services/database/IdType.hpp"
#include "services/database/TransactionChecker.hpp"
namespace Database
{
@@ -38,9 +39,11 @@ namespace Database
bool operator==(const ObjectPtr& other) const { return _obj == other._obj; }
bool operator!=(const ObjectPtr& other) const { return other._obj != _obj; }
auto modify() { return _obj.modify(); }
auto modify() { TransactionChecker::checkWriteTransaction(*_obj.session()); return _obj.modify(); }
void remove()
{
TransactionChecker::checkWriteTransaction(*_obj.session());
if (_obj->hasOnPreRemove())
_obj.modify()->onPreRemove();
_obj.remove();
@@ -19,6 +19,7 @@
#pragma once
#include <filesystem>
#include <optional>
#include <vector>
@@ -61,9 +62,9 @@ namespace Database
ArtistId artist; // only releases that involved this user
EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
EnumSet<TrackArtistLinkType> excludedTrackArtistLinkTypes; // but not for these link types
std::optional<ReleaseTypePrimary> primaryType; // if, set, matching this primary type
std::optional<ReleaseTypePrimary> primaryType; // if set, matching this primary type
EnumSet<ReleaseTypeSecondary> secondaryTypes; // Matching all this (if any)
FindParameters& setClusters(const std::vector<ClusterId>& _clusters) { clusters = _clusters; return *this; }
FindParameters& setKeywords(const std::vector<std::string_view>& _keywords) { keywords = _keywords; return *this; }
FindParameters& setSortMethod(ReleaseSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; }
@@ -86,18 +87,19 @@ namespace Database
static std::size_t getCount(Session& session);
static bool exists(Session& session, ReleaseId id);
static pointer find(Session& session, const UUID& MBID);
static std::vector<pointer> find(Session& session, const std::string& name);
static std::vector<pointer> find(Session& session, const std::string& name, const std::filesystem::path& releaseDirectory);
static pointer find(Session& session, ReleaseId id);
static RangeResults<pointer> find(Session& session, const FindParameters& parameters);
static void find(Session& session, const FindParameters& parameters, std::function<void(const pointer&)> func);
static RangeResults<ReleaseId> findIds(Session& session, const FindParameters& parameters);
static std::size_t getCount(Session& session, const FindParameters& parameters);
static RangeResults<ReleaseId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt); // not track related
static RangeResults<ReleaseId> findIdsOrderedByArtist(Session& session, std::optional<Range> range = std::nullopt);
// Get the cluster of the tracks that belong to this release
// Each clusters are grouped by cluster type, sorted by the number of occurence (max to min)
// size is the max number of cluster per cluster type
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const;
// Utility functions (if all tracks have the same values, which is legit to not be the case)
Wt::WDate getReleaseDate() const;
@@ -20,6 +20,8 @@
#pragma once
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
#include <Wt/Dbo/Dbo.h>
@@ -30,9 +32,8 @@
LMS_DECLARE_IDTYPE(ScanSettingsId)
namespace Database {
class ClusterType;
namespace Database
{
class Session;
class ScanSettings final : public Object<ScanSettings, ScanSettingsId>
@@ -65,7 +66,7 @@ namespace Database {
std::filesystem::path getMediaDirectory() const { return _mediaDirectory; }
Wt::WTime getUpdateStartTime() const { return _startTime; }
UpdatePeriod getUpdatePeriod() const { return _updatePeriod; }
std::vector<ObjectPtr<ClusterType>> getClusterTypes() const;
std::vector<std::string_view> getExtraTagsToScan() const;
std::vector<std::filesystem::path> getAudioFileExtensions() const;
SimilarityEngineType getSimilarityEngineType() const { return _similarityEngineType; }
@@ -74,7 +75,7 @@ namespace Database {
void setMediaDirectory(const std::filesystem::path& p);
void setUpdateStartTime(Wt::WTime t) { _startTime = t; }
void setUpdatePeriod(UpdatePeriod p) { _updatePeriod = p; }
void setClusterTypes(Session& session, const std::set<std::string>& clusterTypeNames);
void setExtraTagsToScan(const std::vector<std::string_view>& extraTags);
void setSimilarityEngineType(SimilarityEngineType type) { _similarityEngineType = type; }
void incScanVersion();
@@ -87,20 +88,17 @@ namespace Database {
Wt::Dbo::field(a, _updatePeriod, "update_period");
Wt::Dbo::field(a, _audioFileExtensions, "audio_file_extensions");
Wt::Dbo::field(a, _similarityEngineType, "similarity_engine_type");
Wt::Dbo::hasMany(a, _clusterTypes, Wt::Dbo::ManyToOne, "scan_settings");
Wt::Dbo::field(a, _extraTagsToScan, "extra_tags_to_scan");
}
private:
int _scanVersion{};
std::string _mediaDirectory;
Wt::WTime _startTime = Wt::WTime{ 0,0,0 };
UpdatePeriod _updatePeriod{ UpdatePeriod::Never };
int _scanVersion{};
std::string _mediaDirectory;
Wt::WTime _startTime = Wt::WTime{ 0,0,0 };
UpdatePeriod _updatePeriod{ UpdatePeriod::Never };
SimilarityEngineType _similarityEngineType{ SimilarityEngineType::Clusters };
std::string _audioFileExtensions{ ".alac .mp3 .ogg .oga .aac .m4a .m4b .flac .wav .wma .aif .aiff .ape .mpc .shn .opus .wv" };
Wt::Dbo::collection<Wt::Dbo::ptr<ClusterType>> _clusterTypes;
std::string _extraTagsToScan;
};
} // namespace Database
@@ -24,27 +24,37 @@
#include "utils/RecursiveSharedMutex.hpp"
#include "services/database/Object.hpp"
#include "services/database/TransactionChecker.hpp"
namespace Database
{
class UniqueTransaction
class WriteTransaction
{
public:
~WriteTransaction();
private:
friend class Session;
UniqueTransaction(RecursiveSharedMutex& mutex, Wt::Dbo::Session& session);
WriteTransaction(RecursiveSharedMutex& mutex, Wt::Dbo::Session& session);
WriteTransaction(const WriteTransaction&) = delete;
WriteTransaction& operator=(const WriteTransaction&) = delete;
std::unique_lock<RecursiveSharedMutex> _lock;
Wt::Dbo::Transaction _transaction;
};
class SharedTransaction
class ReadTransaction
{
public:
~ReadTransaction();
private:
friend class Session;
SharedTransaction(RecursiveSharedMutex& mutex, Wt::Dbo::Session& session);
ReadTransaction(Wt::Dbo::Session& session);
ReadTransaction(const ReadTransaction&) = delete;
ReadTransaction& operator=(const ReadTransaction&) = delete;
std::shared_lock<RecursiveSharedMutex> _lock;
Wt::Dbo::Transaction _transaction;
};
@@ -54,11 +64,11 @@ namespace Database
public:
Session(Db& database);
[[nodiscard]] UniqueTransaction createUniqueTransaction();
[[nodiscard]] SharedTransaction createSharedTransaction();
[[nodiscard]] WriteTransaction createWriteTransaction();
[[nodiscard]] ReadTransaction createReadTransaction();
void checkUniqueLocked();
void checkSharedLocked();
void checkWriteTransaction() { TransactionChecker::checkWriteTransaction(_session); }
void checkReadTransaction() { TransactionChecker::checkReadTransaction(_session); }
void analyze();
void optimize();
@@ -71,7 +81,7 @@ namespace Database
template <typename Object, typename... Args>
typename Object::pointer create(Args&&... args)
{
checkUniqueLocked();
TransactionChecker::checkWriteTransaction(_session);
typename Object::pointer res{ Object::create(*this, std::forward<Args>(args)...) };
getDboSession().flush();
@@ -90,5 +100,3 @@ namespace Database
Wt::Dbo::Session _session;
};
} // namespace Database
@@ -42,6 +42,7 @@ namespace Database
// Search utility
static std::size_t getCount(Session& session);
static pointer find(Session& session, StarredArtistId id);
static pointer find(Session& session, ArtistId artistId, UserId userId); // current backend
static pointer find(Session& session, ArtistId artistId, UserId userId, FeedbackBackend backend);
// Accessors
@@ -42,6 +42,7 @@ namespace Database
// Search utility
static std::size_t getCount(Session& session);
static pointer find(Session& session, StarredReleaseId id);
static pointer find(Session& session, ReleaseId releaseId, UserId userId); // current feedback backend
static pointer find(Session& session, ReleaseId releaseId, UserId userId, FeedbackBackend backend);
// Accessors
@@ -55,7 +55,9 @@ namespace Database
// Search utility
static std::size_t getCount(Session& session);
static pointer find(Session& session, StarredTrackId id);
static pointer find(Session& session, TrackId trackId, UserId userId); // current feedback backend
static pointer find(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend);
static bool exists(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend);
static RangeResults<StarredTrackId> find(Session& session, const FindParameters& findParams);
// Accessors
@@ -176,7 +176,7 @@ namespace Database {
std::vector<ObjectPtr<Cluster>> getClusters() const;
std::vector<ClusterId> getClusterIds() const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ClusterTypeId>& clusterTypes, std::size_t size) const;
template<class Action>
void persist(Action& a)
@@ -99,7 +99,7 @@ namespace Database {
// Get clusters, order by occurence
std::vector<ObjectPtr<Cluster>> getClusters() const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ObjectPtr<ClusterType>>& clusterTypes, std::size_t size) const;
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const;
bool hasTrack(TrackId trackId) const;
@@ -0,0 +1,53 @@
/*
* Copyright (C) 2023 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <vector>
#include <Wt/Dbo/Session.h>
namespace Database
{
class Session;
class TransactionChecker
{
public:
enum class TransactionType
{
Read,
Write,
};
static void pushWriteTransaction(Wt::Dbo::Session& session);
static void pushReadTransaction(Wt::Dbo::Session& session);
static void popWriteTransaction(Wt::Dbo::Session& session);
static void popReadTransaction(Wt::Dbo::Session& session);
static void checkWriteTransaction(Wt::Dbo::Session& session);
static void checkWriteTransaction(Session& session);
static void checkReadTransaction(Wt::Dbo::Session& session);
static void checkReadTransaction(Session& session);
private:
static void pushTransaction(TransactionType type, Wt::Dbo::Session& session);
static void popTransaction(TransactionType type, Wt::Dbo::Session& session);
};
}
+36 -36
View File
@@ -24,7 +24,7 @@ using namespace Database;
TEST_F(DatabaseFixture, Artist)
{
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_FALSE(Artist::exists(session, 35));
EXPECT_FALSE(Artist::exists(session, 0));
EXPECT_FALSE(Artist::exists(session, 1));
@@ -34,7 +34,7 @@ TEST_F(DatabaseFixture, Artist)
ScopedArtist artist{ session, "MyArtist" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(artist.get());
EXPECT_FALSE(!artist.get());
@@ -45,7 +45,7 @@ TEST_F(DatabaseFixture, Artist)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Artist::findIds(session, Artist::FindParameters {}) };
ASSERT_EQ(artists.results.size(), 1);
@@ -58,7 +58,7 @@ TEST_F(DatabaseFixture, Artist)
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Artist::find(session, Artist::FindParameters {}) };
ASSERT_EQ(artists.results.size(), 1);
@@ -66,7 +66,7 @@ TEST_F(DatabaseFixture, Artist)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
bool visited{};
Artist::find(session, Artist::FindParameters{}, [&](const Artist::pointer& a)
@@ -84,19 +84,19 @@ TEST_F(DatabaseFixture, Artist_singleTrack)
ScopedArtist artist{ session, "MyArtist" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setName("MyTrackName");
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ track->getArtists({TrackArtistLinkType::Artist}) };
ASSERT_EQ(artists.size(), 1);
@@ -113,7 +113,7 @@ TEST_F(DatabaseFixture, Artist_singleTrack)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ track->getArtistIds({TrackArtistLinkType::Artist}) };
ASSERT_EQ(artists.size(), 1);
@@ -125,23 +125,23 @@ TEST_F(DatabaseFixture, Artist_singleTrack)
}
{
auto transaction{ session.createSharedTransaction() };
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.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtistFoo")) };
EXPECT_EQ(tracks.results.size(), 0);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackNameFoo").setArtistName("MyArtist")) };
EXPECT_EQ(tracks.results.size(), 0);
}
{
auto transaction{ session.createSharedTransaction() };
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());
@@ -153,7 +153,7 @@ TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
ScopedTrack track{ session, "MyTrack" };
ScopedArtist artist{ session, "MyArtist" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist);
@@ -161,12 +161,12 @@ TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Artist::findOrphanIds(session, Range{}).results.empty());
}
{
auto transaction{ session.createSharedTransaction() };
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);
@@ -175,7 +175,7 @@ TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ track->getArtists({TrackArtistLinkType::Artist}) };
ASSERT_EQ(artists.size(), 1);
@@ -202,7 +202,7 @@ TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EnumSet<TrackArtistLinkType> types{ TrackArtistLink::findUsedTypes(session, artist.getId()) };
EXPECT_TRUE(types.contains(TrackArtistLinkType::ReleaseArtist));
EXPECT_TRUE(types.contains(TrackArtistLinkType::Artist));
@@ -219,19 +219,19 @@ TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists)
ASSERT_NE(artist1.getId(), artist2.getId());
{
auto transaction{ session.createUniqueTransaction() };
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.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ track->getArtists({TrackArtistLinkType::Artist}) };
ASSERT_EQ(artists.size(), 2);
@@ -246,7 +246,7 @@ TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist1->getId())) };
ASSERT_EQ(tracks.results.size(), 1);
@@ -276,13 +276,13 @@ TEST_F(DatabaseFixture, Artist_findByName)
ScopedTrack track{ session, "MyTrack" }; // filters does not work on orphans
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
artist.get().modify()->setSortName("ZZZ");
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "N" })).results.empty());
@@ -310,7 +310,7 @@ TEST_F(DatabaseFixture, Artist_findByNameEscaped)
ScopedArtist artist6{ session, R"(%AMyArtist)" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
const auto artists{ Artist::find(session, R"(MyArtist%)") };
ASSERT_TRUE(artists.size() == 1);
@@ -332,7 +332,7 @@ TEST_F(DatabaseFixture, Artist_findByNameEscaped)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"MyArtist"})) };
EXPECT_EQ(artists.results.size(), 6);
@@ -366,14 +366,14 @@ TEST_F(DatabaseFixture, Artist_sortMethod)
ScopedArtist artistB{ session, "artistB" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
artistA.get().modify()->setSortName("sortNameB");
artistB.get().modify()->setSortName("sortNameA");
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto allArtistsByName{ Artist::findIds(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::ByName)) };
auto allArtistsBySortName{ Artist::findIds(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::BySortName)) };
@@ -396,14 +396,14 @@ TEST_F(DatabaseFixture, Artist_nonReleaseTracks)
ScopedRelease release{ session, "MyRelease" };
{
auto transaction{ session.createSharedTransaction() };
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.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, track1.get(), artist.get(), TrackArtistLinkType::Artist);
TrackArtistLink::create(session, track2.get(), artist.get(), TrackArtistLinkType::Artist);
@@ -412,7 +412,7 @@ TEST_F(DatabaseFixture, Artist_nonReleaseTracks)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist.getId()).setNonRelease(true)) };
ASSERT_EQ(tracks.results.size(), 1);
@@ -427,29 +427,29 @@ TEST_F(DatabaseFixture, Artist_findByRelease)
ScopedRelease release{ session, "MyRelease" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setRelease(release.getId())) };
EXPECT_EQ(artists.results.size(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setRelease(release.getId())) };
EXPECT_EQ(artists.results.size(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setRelease(release.get());
}
{
auto transaction{ session.createSharedTransaction() };
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());
+109 -78
View File
@@ -27,7 +27,7 @@ using namespace Database;
TEST_F(DatabaseFixture, Cluster)
{
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
EXPECT_EQ(Cluster::getCount(session), 0);
EXPECT_EQ(ClusterType::getCount(session), 0);
}
@@ -35,7 +35,7 @@ TEST_F(DatabaseFixture, Cluster)
ScopedClusterType clusterType{ session, "MyType" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
EXPECT_EQ(ClusterType::getCount(session), 1);
}
@@ -43,7 +43,7 @@ TEST_F(DatabaseFixture, Cluster)
ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
EXPECT_EQ(Cluster::getCount(session), 1);
EXPECT_EQ(cluster->getType()->getId(), clusterType.getId());
@@ -55,12 +55,12 @@ TEST_F(DatabaseFixture, Cluster)
}
{
const auto clusters{ Cluster::findOrphans(session) };
const auto clusters{ Cluster::findOrphanIds(session) };
ASSERT_EQ(clusters.results.size(), 1);
EXPECT_EQ(clusters.results.front(), cluster.getId());
}
auto clusterTypes{ ClusterType::find(session) };
auto clusterTypes{ ClusterType::findIds(session) };
ASSERT_EQ(clusterTypes.results.size(), 1);
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
@@ -68,15 +68,15 @@ TEST_F(DatabaseFixture, Cluster)
ASSERT_EQ(clusterTypes.results.size(), 1);
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
clusterTypes = ClusterType::findOrphans(session);
clusterTypes = ClusterType::findOrphanIds(session);
EXPECT_TRUE(clusterTypes.results.empty());
}
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
auto clusterTypes{ ClusterType::findOrphans(session) };
auto clusterTypes{ ClusterType::findOrphanIds(session) };
ASSERT_EQ(clusterTypes.results.size(), 1);
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
@@ -90,9 +90,9 @@ TEST_F(DatabaseFixture, Cluster_singleTrack)
ScopedClusterType clusterType{ session, "MyClusterType" };
{
auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session).results.empty());
auto clusterTypes{ ClusterType::findOrphans(session) };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty());
auto clusterTypes{ ClusterType::findOrphanIds(session) };
ASSERT_EQ(clusterTypes.results.size(), 1);
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
}
@@ -101,8 +101,8 @@ TEST_F(DatabaseFixture, Cluster_singleTrack)
ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" };
{
auto transaction{ session.createSharedTransaction() };
auto clusters{ Cluster::findOrphans(session) };
auto transaction{ session.createReadTransaction() };
auto clusters{ Cluster::findOrphanIds(session) };
EXPECT_EQ(clusters.results.size(), 2);
EXPECT_TRUE(track->getClusters().empty());
EXPECT_TRUE(track->getClusterIds().empty());
@@ -111,13 +111,13 @@ TEST_F(DatabaseFixture, Cluster_singleTrack)
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster1.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
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());
@@ -126,16 +126,16 @@ TEST_F(DatabaseFixture, Cluster_singleTrack)
}
{
auto transaction{ session.createSharedTransaction() };
auto clusters{ Cluster::findOrphans(session) };
auto transaction{ session.createReadTransaction() };
auto clusters{ Cluster::findOrphanIds(session) };
ASSERT_EQ(clusters.results.size(), 1);
EXPECT_EQ(clusters.results.front(), cluster2.getId());
EXPECT_TRUE(ClusterType::findOrphans(session).results.empty());
EXPECT_TRUE(ClusterType::findOrphanIds(session).results.empty());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Track::findIds(session, Track::FindParameters {}.setClusters({cluster1.getId()})) };
ASSERT_EQ(tracks.results.size(), 1);
@@ -146,7 +146,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrack)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto clusters{ track->getClusters() };
ASSERT_EQ(clusters.size(), 1);
@@ -169,20 +169,20 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters)
const std::vector<ClusterId> clusterIds{ cluster1.getId(), cluster2.getId() };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) };
EXPECT_TRUE(tracks.results.empty());
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster1.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) };
EXPECT_TRUE(tracks.results.empty());
@@ -191,13 +191,13 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters)
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster2.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(clusterIds)) };
ASSERT_FALSE(tracks.results.empty());
@@ -218,14 +218,14 @@ TEST_F(DatabaseFixture, Cluster_multiTracks)
tracks.emplace_back(session, "MyTrack" + std::to_string(i));
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster.get().modify()->addTrack(tracks.back().get());
}
}
{
auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session).results.empty());
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty());
EXPECT_EQ(Cluster::computeTrackCount(session, cluster.getId()), tracks.size());
@@ -237,14 +237,45 @@ TEST_F(DatabaseFixture, Cluster_multiTracks)
}
}
TEST_F(DatabaseFixture, ClusterType_singleTrack)
{
{
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::find(session, Cluster::FindParameters{}).results.empty());
EXPECT_TRUE(Cluster::find(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).results.empty());
}
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.createSharedTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session).results.empty());
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty());
}
ScopedClusterType clusterType{ session, "MyClusterType" };
@@ -252,8 +283,8 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
ScopedCluster unusedCluster{ session, clusterType.lockAndGet(), "MyClusterUnused" };
{
auto transaction{ session.createSharedTransaction() };
ASSERT_EQ(Cluster::findOrphans(session).results.size(), 2);
auto transaction{ session.createReadTransaction() };
ASSERT_EQ(Cluster::findOrphanIds(session).results.size(), 2);
EXPECT_TRUE(Release::find(session, Release::FindParameters{}.setClusters({ unusedCluster.getId() })).results.empty());
EXPECT_EQ(Release::find(session, Release::FindParameters{}).results.size(), 1);
EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0);
@@ -261,17 +292,17 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setRelease(release.get());
cluster.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
auto clusters{ Cluster::findOrphans(session) };
auto clusters{ Cluster::findOrphanIds(session) };
ASSERT_EQ(clusters.results.size(), 1);
EXPECT_EQ(clusters.results.front(), unusedCluster.getId());
}
@@ -280,7 +311,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto clusters{ Cluster::findIds(session, Cluster::FindParameters{}.setRelease(release.getId())) };
ASSERT_EQ(clusters.results.size(), 1);
@@ -288,7 +319,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters({cluster.getId()})) };
ASSERT_EQ(releases.results.size(), 1);
@@ -296,14 +327,14 @@ TEST_F(DatabaseFixture, Cluster_singleTrackSingleReleaseSingleCluster)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters({unusedCluster.getId()})) };
EXPECT_EQ(releases.results.size(), 0);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 1);
EXPECT_EQ(Cluster::computeTrackCount(session, cluster.getId()), 1);
@@ -321,28 +352,28 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters)
ScopedCluster cluster2{ session, clusterType.lockAndGet(), "Cluster2" };
ScopedCluster cluster3{ session, clusterType.lockAndGet(), "Cluster3" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) };
cluster1.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(ClusterType::findOrphans(session).results.empty());
EXPECT_EQ(Cluster::findOrphans(session).results.size(), 2);
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(ClusterType::findOrphanIds(session).results.empty());
EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 2);
EXPECT_TRUE(Release::findOrphanIds(session).results.empty());
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(track->getClusters().size(), 1);
EXPECT_EQ(track->getClusterIds().size(), 1);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createWriteTransaction() };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster1.getId()})) };
ASSERT_EQ(artists.results.size(), 1);
@@ -355,7 +386,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiClusters)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster1.getId()})) };
ASSERT_EQ(artists.results.size(), 1);
@@ -381,7 +412,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiRolesMultiClusters)
ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist);
@@ -389,14 +420,14 @@ TEST_F(DatabaseFixture, SingleTrackSingleArtistMultiRolesMultiClusters)
}
{
auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session).results.empty());
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty());
EXPECT_TRUE(Release::findOrphanIds(session).results.empty());
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster.getId()})) };
ASSERT_EQ(artists.results.size(), 1);
@@ -421,7 +452,7 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistMultiClusters)
{
tracks.emplace_back(session, "MyTrackFile" + std::to_string(i));
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist);
for (auto& cluster : clusters)
@@ -429,13 +460,13 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistMultiClusters)
}
{
auto transaction{ session.createSharedTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session).results.empty());
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty());
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
std::vector<ClusterId> clusterIds;
std::transform(std::cbegin(clusters), std::cend(clusters), std::back_inserter(clusterIds), [](const ScopedCluster& cluster) { return cluster.getId(); });
@@ -458,13 +489,13 @@ TEST_F(DatabaseFixture, MultipleTracksSingleClusterSimilarity)
tracks.emplace_back(session, "MyTrack" + std::to_string(i));
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster.get().modify()->addTrack(tracks.back().get());
}
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}) };
EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1);
@@ -487,7 +518,7 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleClustersSimilarity)
tracks.emplace_back(session, "MyTrack" + std::to_string(i));
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster1.get().modify()->addTrack(tracks.back().get());
}
}
@@ -497,14 +528,14 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleClustersSimilarity)
tracks.emplace_back(session, "MyTrack" + std::to_string(i));
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster1.get().modify()->addTrack(tracks.back().get());
cluster2.get().modify()->addTrack(tracks.back().get());
}
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.back().getId()}, Range {0, 4}) };
@@ -531,7 +562,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster)
ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
track.get().modify()->setRelease(release.get());
@@ -539,16 +570,16 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistSingleCluster)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Cluster::findOrphans(session).results.empty());
EXPECT_TRUE(ClusterType::findOrphans(session).results.empty());
EXPECT_TRUE(Cluster::findOrphanIds(session).results.empty());
EXPECT_TRUE(ClusterType::findOrphanIds(session).results.empty());
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
EXPECT_TRUE(Release::findOrphanIds(session).results.empty());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters({cluster.getId()})) };
ASSERT_EQ(artists.results.size(), 1);
@@ -574,7 +605,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistMultiClusters)
ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) };
track.get().modify()->setRelease(release.get());
@@ -583,7 +614,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtistMultiClusters)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) };
ASSERT_EQ(releases.results.size(), 1);
@@ -607,7 +638,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackSingleCluster)
{
tracks.emplace_back(session, "MyTrack" + std::to_string(i));
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
if (i < 5)
session.create<TrackListEntry>(tracks.back().get(), trackList.get());
@@ -617,7 +648,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackSingleCluster)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto similarTracks{ trackList->getSimilarTracks() };
EXPECT_EQ(similarTracks.size(), 5);
@@ -640,7 +671,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters)
{
tracks.emplace_back(session, "MyTrack" + std::to_string(i));
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
if (i < 5)
session.create<TrackListEntry>(tracks.back().get(), trackList.get());
@@ -657,7 +688,7 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrackMultiClusters)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
const auto similarTracks{ trackList->getSimilarTracks(0, 5) };
@@ -689,7 +720,7 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters)
ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(artist1->findSimilarArtistIds().results.empty());
EXPECT_TRUE(artist2->findSimilarArtistIds().results.empty());
EXPECT_TRUE(artist3->findSimilarArtistIds().results.empty());
@@ -700,7 +731,7 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters)
{
tracks.emplace_back(session, "MyTrack" + std::to_string(i));
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
if (i < 5)
TrackArtistLink::create(session, tracks.back().get(), artist1.get(), TrackArtistLinkType::Artist);
@@ -715,13 +746,13 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleArtistsMultiClusters)
tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size()));
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, tracks.back().get(), artist3.get(), TrackArtistLinkType::Artist);
cluster2.get().modify()->addTrack(tracks.back().get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
auto artists{ artist1->findSimilarArtistIds() };
@@ -770,7 +801,7 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleReleasesMultiClusters)
ScopedCluster cluster2{ session, clusterType.lockAndGet(), "MyCluster2" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(release1->getSimilarReleases().empty());
EXPECT_TRUE(release2->getSimilarReleases().empty());
EXPECT_TRUE(release3->getSimilarReleases().empty());
@@ -781,7 +812,7 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleReleasesMultiClusters)
{
tracks.emplace_back(session, "MyTrack" + std::to_string(i));
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
if (i < 5)
tracks.back().get().modify()->setRelease(release1.get());
@@ -796,13 +827,13 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleReleasesMultiClusters)
tracks.emplace_back(session, "MyTrack" + std::to_string(tracks.size()));
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
tracks.back().get().modify()->setRelease(release3.get());
cluster2.get().modify()->addTrack(tracks.back().get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
auto releases{ release1->getSimilarReleases() };
+1 -12
View File
@@ -59,17 +59,6 @@ void DatabaseFixture::SetUpTestCase()
Database::Session s{ _tmpDb->getDb() };
s.prepareTables();
s.analyze();
// remove default created entries
{
auto transaction{ s.createUniqueTransaction() };
for (const Database::ClusterTypeId clusterTypeId : Database::ClusterType::find(s).results)
{
auto clusterType{ Database::ClusterType::find(s, clusterTypeId) };
clusterType.remove();
}
}
}
}
@@ -82,7 +71,7 @@ void DatabaseFixture::testDatabaseEmpty()
{
using namespace Database;
auto uniqueTransaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
EXPECT_EQ(Artist::getCount(session), 0);
EXPECT_EQ(Cluster::getCount(session), 0);
+4 -4
View File
@@ -49,7 +49,7 @@ class ScopedEntity
ScopedEntity(Database::Session& session, Args&& ...args)
: _session {session}
{
auto transaction {_session.createUniqueTransaction()};
auto transaction {_session.createWriteTransaction()};
auto entity {_session.create<T>(std::forward<Args>(args)...)};
EXPECT_TRUE(entity);
@@ -58,7 +58,7 @@ class ScopedEntity
~ScopedEntity()
{
auto transaction {_session.createUniqueTransaction()};
auto transaction {_session.createWriteTransaction()};
auto entity {T::find(_session, _id)};
// could not be here due to "on delete cascade" constraints...
@@ -73,13 +73,13 @@ class ScopedEntity
typename T::pointer lockAndGet()
{
auto transaction {_session.createSharedTransaction()};
auto transaction {_session.createReadTransaction()};
return get();
}
typename T::pointer get()
{
_session.checkSharedLocked();
_session.checkReadTransaction();
auto entity {T::find(_session, _id)};
EXPECT_TRUE(entity);
@@ -34,20 +34,20 @@ TEST_F(DatabaseFixture, MultiTracksSingleArtistSingleRelease)
{
tracks.emplace_back(session, "MyTrack" + std::to_string(i));
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist);
tracks.back().get().modify()->setRelease(release.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Release::findOrphanIds(session).results.empty());
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) };
ASSERT_EQ(releases.results.size(), 1);
@@ -65,14 +65,14 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtist)
ScopedArtist artist{ session, "MyArtist" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) };
track.get().modify()->setRelease(release.get());
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) };
ASSERT_EQ(releases.results.size(), 1);
@@ -87,7 +87,7 @@ TEST_F(DatabaseFixture, SingleTrackSingleReleaseSingleArtist)
TEST_F(DatabaseFixture, SingleUser)
{
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(User::find(session, User::FindParameters{}).results.empty());
EXPECT_EQ(User::getCount(session), 0);
}
@@ -95,7 +95,7 @@ TEST_F(DatabaseFixture, SingleUser)
ScopedUser user{ session, "MyUser" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(User::find(session, User::FindParameters{}).results.size(), 1);
EXPECT_EQ(User::getCount(session), 1);
+97 -97
View File
@@ -30,7 +30,7 @@ TEST_F(DatabaseFixture, Listen_getAll)
ScopedUser user{ session, "MyUser" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(Listen::getCount(session), 0);
}
@@ -38,20 +38,20 @@ TEST_F(DatabaseFixture, Listen_getAll)
ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(Listen::getCount(session), 1);
}
{
auto transaction{ session.createUniqueTransaction() };
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.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(Listen::getCount(session), 1);
}
@@ -64,14 +64,14 @@ TEST_F(DatabaseFixture, Listen_get)
ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} };
{
auto transaction{ session.createSharedTransaction() };
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.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::Internal)) };
@@ -100,7 +100,7 @@ TEST_F(DatabaseFixture, Listen_get_multi)
ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 2}} };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto listens{ Listen::find(session, Listen::FindParameters{}.setUser(user->getId()).setScrobblingBackend(ScrobblingBackend::Internal)) };
ASSERT_EQ(listens.results.size(), 3);
@@ -122,7 +122,7 @@ TEST_F(DatabaseFixture, Listen_get_byDateTime)
ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
Listen::pointer listen{ Listen::find(session, user.getId(), track.getId(), ScrobblingBackend::Internal, dateTime1) };
@@ -151,7 +151,7 @@ TEST_F(DatabaseFixture, Listen_getTopArtists)
ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime1 };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getTopArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt) };
EXPECT_EQ(artists.results.size(), 0);
@@ -163,26 +163,26 @@ TEST_F(DatabaseFixture, Listen_getTopArtists)
ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime1.addSecs(1) };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, track2.get(), artist1.get(), TrackArtistLinkType::Artist);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getTopArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results[0], artist1->getId());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getTopArtists(session, user->getId(), ScrobblingBackend::ListenBrainz, {}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 0);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getTopArtists(session, user->getId(), ScrobblingBackend::Internal, {}, TrackArtistLinkType::Producer) };
EXPECT_EQ(artists.results.size(), 0);
@@ -192,7 +192,7 @@ TEST_F(DatabaseFixture, Listen_getTopArtists)
ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getTopArtists(session, user->getId(), ScrobblingBackend::Internal, {cluster->getId()}, std::nullopt) };
EXPECT_EQ(artists.results.size(), 0);
@@ -210,13 +210,13 @@ TEST_F(DatabaseFixture, Listen_getTopArtists_multi)
const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} };
{
auto transaction{ session.createUniqueTransaction() };
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.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getTopArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt) };
EXPECT_EQ(artists.results.size(), 0);
@@ -224,7 +224,7 @@ TEST_F(DatabaseFixture, Listen_getTopArtists_multi)
ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getTopArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 1);
@@ -233,7 +233,7 @@ TEST_F(DatabaseFixture, Listen_getTopArtists_multi)
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.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getTopArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 2);
@@ -241,7 +241,7 @@ TEST_F(DatabaseFixture, Listen_getTopArtists_multi)
EXPECT_EQ(artists.results[1], artist1->getId());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getTopArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt, Range {0, 1}) };
ASSERT_EQ(artists.results.size(), 1);
@@ -261,22 +261,22 @@ TEST_F(DatabaseFixture, Listen_getTopArtists_cluster)
ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getTopArtists(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getTopArtists(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 1);
@@ -291,12 +291,12 @@ TEST_F(DatabaseFixture, Listen_getTopReleases)
const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} };
ScopedRelease release{ session, "MyRelease" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setRelease(release.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getTopReleases(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -306,7 +306,7 @@ TEST_F(DatabaseFixture, Listen_getTopReleases)
ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getTopReleases(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -314,7 +314,7 @@ TEST_F(DatabaseFixture, Listen_getTopReleases)
EXPECT_EQ(releases.results[0], release.getId());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getTopReleases(session, user->getId(), ScrobblingBackend::ListenBrainz, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -333,13 +333,13 @@ TEST_F(DatabaseFixture, Listen_getTopReleases_multi)
ScopedRelease release2{ session, "MyRelease2" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track1.get().modify()->setRelease(release1.get());
track2.get().modify()->setRelease(release2.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getTopReleases(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -349,7 +349,7 @@ TEST_F(DatabaseFixture, Listen_getTopReleases_multi)
ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime };
ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getTopReleases(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -360,7 +360,7 @@ TEST_F(DatabaseFixture, Listen_getTopReleases_multi)
ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime };
ScopedListen listen5{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getTopReleases(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -381,23 +381,23 @@ TEST_F(DatabaseFixture, Listen_getTopReleases_cluster)
ScopedRelease release{ session, "MyRelease" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setRelease(release.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getTopReleases(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}) };
EXPECT_EQ(releases.results.size(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getTopReleases(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}) };
EXPECT_EQ(releases.results.size(), 1);
@@ -412,7 +412,7 @@ TEST_F(DatabaseFixture, Listen_getTopTracks)
const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getTopTracks(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -422,7 +422,7 @@ TEST_F(DatabaseFixture, Listen_getTopTracks)
ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getTopTracks(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -430,7 +430,7 @@ TEST_F(DatabaseFixture, Listen_getTopTracks)
EXPECT_EQ(tracks.results[0], track.getId());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getTopTracks(session, user->getId(), ScrobblingBackend::ListenBrainz, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -447,7 +447,7 @@ TEST_F(DatabaseFixture, Listen_getTopTrack_multi)
ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getTopTracks(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -457,7 +457,7 @@ TEST_F(DatabaseFixture, Listen_getTopTrack_multi)
ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime };
ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getTopTracks(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -468,7 +468,7 @@ TEST_F(DatabaseFixture, Listen_getTopTrack_multi)
ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime };
ScopedListen listen5{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getTopTracks(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -488,18 +488,18 @@ TEST_F(DatabaseFixture, Listen_getTopTracks_cluster)
ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getTopTracks(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}) };
EXPECT_EQ(tracks.results.size(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getTopTracks(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}) };
EXPECT_EQ(tracks.results.size(), 1);
@@ -514,12 +514,12 @@ TEST_F(DatabaseFixture, Listen_getRecentArtists)
ScopedArtist artist{ session, "MyArtist" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getRecentArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt) };
EXPECT_EQ(artists.results.size(), 0);
@@ -530,20 +530,20 @@ TEST_F(DatabaseFixture, Listen_getRecentArtists)
ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getRecentArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 1);
EXPECT_EQ(artists.results[0], artist->getId());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getRecentArtists(session, user->getId(), ScrobblingBackend::ListenBrainz, {}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 0);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getRecentArtists(session, user->getId(), ScrobblingBackend::Internal, {}, TrackArtistLinkType::Producer) };
EXPECT_EQ(artists.results.size(), 0);
@@ -553,7 +553,7 @@ TEST_F(DatabaseFixture, Listen_getRecentArtists)
ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getRecentArtists(session, user->getId(), ScrobblingBackend::Internal, {cluster->getId()}, std::nullopt) };
EXPECT_EQ(artists.results.size(), 0);
@@ -571,13 +571,13 @@ TEST_F(DatabaseFixture, Listen_getRecentArtists_multi)
const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} };
{
auto transaction{ session.createUniqueTransaction() };
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.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getRecentArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt) };
EXPECT_EQ(artists.results.size(), 0);
@@ -585,7 +585,7 @@ TEST_F(DatabaseFixture, Listen_getRecentArtists_multi)
ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getRecentArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 1);
@@ -593,7 +593,7 @@ TEST_F(DatabaseFixture, Listen_getRecentArtists_multi)
}
ScopedListen listen2{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(2) };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getRecentArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 2);
@@ -602,7 +602,7 @@ TEST_F(DatabaseFixture, Listen_getRecentArtists_multi)
}
ScopedListen listen3{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(-1) };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getRecentArtists(session, user->getId(), ScrobblingBackend::Internal, {}, std::nullopt, Range {0, 1}) };
ASSERT_EQ(artists.results.size(), 1);
@@ -622,22 +622,22 @@ TEST_F(DatabaseFixture, Listen_getRecentArtists_cluster)
ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getRecentArtists(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Listen::getRecentArtists(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}, std::nullopt) };
ASSERT_EQ(artists.results.size(), 1);
@@ -652,12 +652,12 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases)
ScopedRelease release{ session, "MyRelease" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setRelease(release.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getRecentReleases(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -668,7 +668,7 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases)
ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getRecentReleases(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -676,7 +676,7 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases)
EXPECT_EQ(releases.results[0], release.getId());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getRecentReleases(session, user->getId(), ScrobblingBackend::ListenBrainz, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -692,12 +692,12 @@ TEST_F(DatabaseFixture, Listen_getMostRecentRelease)
ScopedRelease release{ session, "MyRelease" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setRelease(release.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) };
EXPECT_FALSE(listen);
@@ -707,7 +707,7 @@ TEST_F(DatabaseFixture, Listen_getMostRecentRelease)
ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) };
EXPECT_TRUE(listen);
@@ -718,7 +718,7 @@ TEST_F(DatabaseFixture, Listen_getMostRecentRelease)
ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) };
EXPECT_TRUE(listen);
@@ -729,7 +729,7 @@ TEST_F(DatabaseFixture, Listen_getMostRecentRelease)
ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime3 };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, release.getId()) };
EXPECT_TRUE(listen);
@@ -746,7 +746,7 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases_multi)
ScopedRelease release2{ session, "MyRelease2" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track1.get().modify()->setRelease(release1.get());
track2.get().modify()->setRelease(release2.get());
}
@@ -755,7 +755,7 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases_multi)
ScopedListen listen1{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getRecentReleases(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -765,7 +765,7 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases_multi)
ScopedListen listen2{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getRecentReleases(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -776,7 +776,7 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases_multi)
ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(2) };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getRecentReleases(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -787,7 +787,7 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases_multi)
ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(-1) };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getRecentReleases(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(releases.moreResults, false);
@@ -806,11 +806,11 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases_cluster)
ScopedRelease release{ session, "MyRelease" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setRelease(release.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getRecentReleases(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}) };
EXPECT_EQ(releases.results.size(), 0);
@@ -820,18 +820,18 @@ TEST_F(DatabaseFixture, Listen_getRecentReleases_cluster)
ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getRecentReleases(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}) };
EXPECT_EQ(releases.results.size(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Listen::getRecentReleases(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}) };
EXPECT_EQ(releases.results.size(), 1);
@@ -845,7 +845,7 @@ TEST_F(DatabaseFixture, Listen_getRecentTracks)
ScopedUser user{ session, "MyUser" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getRecentTracks(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -856,7 +856,7 @@ TEST_F(DatabaseFixture, Listen_getRecentTracks)
ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getRecentTracks(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -865,7 +865,7 @@ TEST_F(DatabaseFixture, Listen_getRecentTracks)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getRecentTracks(session, user->getId(), ScrobblingBackend::ListenBrainz, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -879,7 +879,7 @@ TEST_F(DatabaseFixture, Listen_getCount_track)
ScopedUser user{ session, "MyUser" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const std::size_t count{ Listen::getCount(session, user->getId(), ScrobblingBackend::Internal, track.getId()) };
EXPECT_EQ(count, 0);
@@ -889,7 +889,7 @@ TEST_F(DatabaseFixture, Listen_getCount_track)
ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const std::size_t count{ Listen::getCount(session, user->getId(), ScrobblingBackend::Internal, track.getId()) };
EXPECT_EQ(count, 1);
@@ -905,7 +905,7 @@ TEST_F(DatabaseFixture, Listen_getCount_release)
auto getReleaseListenCount{ [&]
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
return Listen::getCount(session, user->getId(), ScrobblingBackend::Internal, release.getId());
} };
@@ -917,14 +917,14 @@ TEST_F(DatabaseFixture, Listen_getCount_release)
EXPECT_EQ(getReleaseListenCount(), 0);
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track1.get().modify()->setRelease(release.get());
}
EXPECT_EQ(getReleaseListenCount(), 1);
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track2.get().modify()->setRelease(release.get());
}
@@ -944,7 +944,7 @@ TEST_F(DatabaseFixture, Listen_getMostRecentTrack)
ScopedUser user{ session, "MyUser" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) };
EXPECT_FALSE(listen);
@@ -954,7 +954,7 @@ TEST_F(DatabaseFixture, Listen_getMostRecentTrack)
ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) };
EXPECT_TRUE(listen);
@@ -965,7 +965,7 @@ TEST_F(DatabaseFixture, Listen_getMostRecentTrack)
ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) };
EXPECT_TRUE(listen);
@@ -976,7 +976,7 @@ TEST_F(DatabaseFixture, Listen_getMostRecentTrack)
ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime3 };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto listen{ Listen::getMostRecentListen(session, user->getId(), ScrobblingBackend::Internal, track.getId()) };
EXPECT_TRUE(listen);
@@ -994,7 +994,7 @@ TEST_F(DatabaseFixture, Listen_getRecentTracks_multi)
ScopedListen listen1{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getRecentTracks(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -1004,7 +1004,7 @@ TEST_F(DatabaseFixture, Listen_getRecentTracks_multi)
ScopedListen listen2{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(1) };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getRecentTracks(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -1015,7 +1015,7 @@ TEST_F(DatabaseFixture, Listen_getRecentTracks_multi)
ScopedListen listen3{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(2) };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getRecentTracks(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -1026,7 +1026,7 @@ TEST_F(DatabaseFixture, Listen_getRecentTracks_multi)
ScopedListen listen4{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime.addSecs(-1) };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getRecentTracks(session, user->getId(), ScrobblingBackend::Internal, {}) };
EXPECT_EQ(tracks.moreResults, false);
@@ -1046,18 +1046,18 @@ TEST_F(DatabaseFixture, Listen_getRecentTracks_cluster)
ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getRecentTracks(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}) };
EXPECT_EQ(tracks.results.size(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
cluster.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Listen::getRecentTracks(session, user->getId(), ScrobblingBackend::Internal, {cluster.getId()}) };
EXPECT_EQ(tracks.results.size(), 1);
+91 -57
View File
@@ -24,9 +24,10 @@ using namespace Database;
TEST_F(DatabaseFixture, Release)
{
{
auto transaction{ session.createSharedTransaction() };
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));
}
@@ -34,9 +35,10 @@ TEST_F(DatabaseFixture, Release)
ScopedRelease release{ session, "MyRelease" };
{
auto transaction{ session.createSharedTransaction() };
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()));
{
@@ -77,7 +79,7 @@ TEST_F(DatabaseFixture, Release_singleTrack)
{
ScopedTrack track{ session, "MyTrack" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setRelease(release.get());
track.get().modify()->setName("MyTrackName");
@@ -85,7 +87,7 @@ TEST_F(DatabaseFixture, Release_singleTrack)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Release::findOrphanIds(session).results.empty());
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) };
@@ -94,32 +96,32 @@ TEST_F(DatabaseFixture, Release_singleTrack)
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
ASSERT_TRUE(track->getRelease());
EXPECT_EQ(track->getRelease()->getId(), release.getId());
}
{
auto transaction{ session.createUniqueTransaction() };
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.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setReleaseName("MyReleaseFoo")) };
EXPECT_EQ(tracks.results.size(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackFoo").setReleaseName("MyReleaseName")) };
EXPECT_EQ(tracks.results.size(), 0);
}
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) };
EXPECT_TRUE(tracks.results.empty());
@@ -130,6 +132,39 @@ TEST_F(DatabaseFixture, Release_singleTrack)
}
}
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" };
@@ -148,7 +183,7 @@ TEST_F(DatabaseFixture, MulitpleReleaseSearchByName)
ScopedTrack track6{ session, "MyTrack" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track1.get().modify()->setRelease(release1.get());
track2.get().modify()->setRelease(release2.get());
@@ -159,7 +194,7 @@ TEST_F(DatabaseFixture, MulitpleReleaseSearchByName)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"Release"})) };
@@ -201,33 +236,33 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack)
ScopedRelease release1{ session, "MyRelease" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_FALSE(release1->getTotalDisc());
}
ScopedTrack track1{ session, "MyTrack" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track1.get().modify()->setRelease(release1.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_FALSE(release1->getTotalDisc());
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track1.get().modify()->setTotalTrack(36);
release1.get().modify()->setTotalDisc(6);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
ASSERT_TRUE(track1->getTotalTrack());
EXPECT_EQ(*track1->getTotalTrack(), 36);
@@ -237,7 +272,7 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack)
ScopedTrack track2{ session, "MyTrack2" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track2.get().modify()->setRelease(release1.get());
track2.get().modify()->setTotalTrack(37);
@@ -245,7 +280,7 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
ASSERT_TRUE(track1->getTotalTrack());
EXPECT_EQ(*track1->getTotalTrack(), 36);
@@ -255,21 +290,21 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseTotalDiscTrack)
ScopedRelease release2{ session, "MyRelease2" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_FALSE(release2->getTotalDisc());
}
ScopedTrack track3{ session, "MyTrack3" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track3.get().modify()->setRelease(release2.get());
track3.get().modify()->setTotalTrack(7);
release2.get().modify()->setTotalDisc(5);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
ASSERT_TRUE(track1->getTotalTrack());
EXPECT_EQ(*track1->getTotalTrack(), 36);
@@ -293,14 +328,14 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack)
ScopedTrack track2B{ session, "MyTrack2B" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(Track::findIds(session, Track::FindParameters{}.setRelease(release1.getId())).results.empty());
EXPECT_TRUE(Track::findIds(session, Track::FindParameters{}.setRelease(release2.getId())).results.empty());
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track1A.get().modify()->setRelease(release1.get());
track1B.get().modify()->setRelease(release1.get());
@@ -317,7 +352,7 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release1.getId()).setSortMethod(TrackSortMethod::Release)) };
@@ -346,14 +381,14 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseDate)
ScopedTrack track2B{ session, "MyTrack2B" };
{
auto transaction{ session.createSharedTransaction() };
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.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track1A.get().modify()->setRelease(release1.get());
track1B.get().modify()->setRelease(release1.get());
@@ -371,7 +406,7 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseDate)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(1950, 2000))) };
ASSERT_EQ(releases.results.size(), 1);
@@ -394,25 +429,25 @@ TEST_F(DatabaseFixture, Release_writtenAfter)
const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 1}, Wt::WTime {12, 30, 20} };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setLastWriteTime(dateTime);
track.get().modify()->setRelease(release.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto releases{ Release::findIds(session, Release::FindParameters {}) };
EXPECT_EQ(releases.results.size(), 1);
}
{
auto transaction{ session.createSharedTransaction() };
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.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setWrittenAfter(dateTime.addSecs(+1))) };
EXPECT_EQ(releases.results.size(), 0);
}
@@ -425,12 +460,12 @@ TEST_F(DatabaseFixture, Release_artist)
ScopedArtist artist{ session, "MyArtist" };
ScopedArtist artist2{ session, "MyArtist2" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setRelease(release.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Artist})) };
EXPECT_EQ(releases.results.size(), 0);
@@ -440,7 +475,7 @@ TEST_F(DatabaseFixture, Release_artist)
}
{
auto transaction{ session.createUniqueTransaction() };
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})) };
@@ -486,40 +521,40 @@ TEST_F(DatabaseFixture, Release_getDiscCount)
ScopedTrack track2{ session, "MyTrack2" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(release.get()->getDiscCount(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setRelease(release.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(release.get()->getDiscCount(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setDiscNumber(5);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(release.get()->getDiscCount(), 1);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track2.get().modify()->setRelease(release.get());
track2.get().modify()->setDiscNumber(5);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(release.get()->getDiscCount(), 1);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track2.get().modify()->setDiscNumber(6);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(release.get()->getDiscCount(), 2);
}
}
@@ -529,19 +564,19 @@ TEST_F(DatabaseFixture, Release_releaseType)
ScopedRelease release{ session, "MyRelease" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(release.get()->getPrimaryType(), std::nullopt);
EXPECT_EQ(release.get()->getSecondaryTypes(), EnumSet<ReleaseTypeSecondary> {});
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
release.get().modify()->setPrimaryType({ ReleaseTypePrimary::Album });
release.get().modify()->setSecondaryTypes({ ReleaseTypeSecondary::Compilation });
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(release.get()->getPrimaryType(), ReleaseTypePrimary::Album);
EXPECT_TRUE(release.get()->getSecondaryTypes().contains(ReleaseTypeSecondary::Compilation));
}
@@ -563,7 +598,7 @@ TEST_F(DatabaseFixture, Release_sortMethod)
ASSERT_GT(release2Date, release1OriginalDate);
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track1.get().modify()->setRelease(release1.get());
track1.get().modify()->setOriginalDate(release1OriginalDate);
@@ -574,7 +609,7 @@ TEST_F(DatabaseFixture, Release_sortMethod)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Name)) };
ASSERT_EQ(releases.results.size(), 2);
@@ -583,14 +618,14 @@ TEST_F(DatabaseFixture, Release_sortMethod)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Random)) };
ASSERT_EQ(releases.results.size(), 2);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Date)) };
ASSERT_EQ(releases.results.size(), 2);
@@ -599,7 +634,7 @@ TEST_F(DatabaseFixture, Release_sortMethod)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::OriginalDate)) };
ASSERT_EQ(releases.results.size(), 2);
@@ -607,7 +642,7 @@ TEST_F(DatabaseFixture, Release_sortMethod)
EXPECT_EQ(releases.results.back(), release2.getId());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::OriginalDateDesc)) };
ASSERT_EQ(releases.results.size(), 2);
@@ -616,7 +651,6 @@ TEST_F(DatabaseFixture, Release_sortMethod)
}
}
TEST_F(DatabaseFixture, Release_meanBitrate)
{
ScopedRelease release1{ session, "MyRelease1" };
@@ -626,14 +660,14 @@ TEST_F(DatabaseFixture, Release_meanBitrate)
auto checkExpectedBitrate = [&](std::size_t bitrate)
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(release1->getMeanBitrate(), bitrate);
};
checkExpectedBitrate(0);
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track1.get().modify()->setBitrate(128);
track1.get().modify()->setRelease(release1.get());
}
@@ -641,14 +675,14 @@ TEST_F(DatabaseFixture, Release_meanBitrate)
checkExpectedBitrate(128);
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track2.get().modify()->setBitrate(256);
track2.get().modify()->setRelease(release1.get());
}
checkExpectedBitrate(192);
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track3.get().modify()->setBitrate(0);
track3.get().modify()->setRelease(release1.get());
}
@@ -31,7 +31,7 @@ TEST_F(DatabaseFixture, StarredArtist)
ScopedUser user2{ session, "MyUser2" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto starredArtist{ StarredArtist::find(session, artist->getId(), user->getId(), FeedbackBackend::Internal) };
EXPECT_FALSE(starredArtist);
@@ -43,7 +43,7 @@ TEST_F(DatabaseFixture, StarredArtist)
ScopedStarredArtist starredArtist{ session, artist.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto gotArtist{ StarredArtist::find(session, artist->getId(), user->getId(), FeedbackBackend::Internal) };
EXPECT_EQ(gotArtist->getId(), starredArtist->getId());
@@ -51,7 +51,7 @@ TEST_F(DatabaseFixture, StarredArtist)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto artists{ Artist::findIds(session, Artist::FindParameters {}) };
EXPECT_EQ(artists.results.size(), 1);
@@ -62,6 +62,29 @@ TEST_F(DatabaseFixture, StarredArtist)
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());
}
}
TEST_F(DatabaseFixture, StarredArtist_PendingDestroy)
@@ -71,7 +94,7 @@ TEST_F(DatabaseFixture, StarredArtist_PendingDestroy)
ScopedStarredArtist starredArtist{ session, artist.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
auto artists{ Artist::findIds(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) };
EXPECT_EQ(artists.results.size(), 1);
@@ -94,14 +117,14 @@ TEST_F(DatabaseFixture, StarredArtist_dateTime)
const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} };
{
auto transaction{ session.createSharedTransaction() };
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.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
starredArtist1.get().modify()->setDateTime(dateTime);
starredArtist2.get().modify()->setDateTime(dateTime.addSecs(-1));
@@ -112,7 +135,7 @@ TEST_F(DatabaseFixture, StarredArtist_dateTime)
EXPECT_EQ(artists.results[1], starredArtist2->getArtist()->getId());
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
starredArtist1.get().modify()->setDateTime(dateTime);
starredArtist2.get().modify()->setDateTime(dateTime.addSecs(1));
@@ -31,7 +31,7 @@ TEST_F(DatabaseFixture, StarredRelease)
ScopedUser user2{ session, "MyUser2" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto starredRelease{ StarredRelease::find(session, release->getId(), user->getId(), FeedbackBackend::Internal) };
EXPECT_FALSE(starredRelease);
@@ -43,7 +43,7 @@ TEST_F(DatabaseFixture, StarredRelease)
ScopedStarredRelease starredRelease{ session, release.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto gotRelease{ StarredRelease::find(session, release->getId(), user->getId(), FeedbackBackend::Internal) };
EXPECT_EQ(gotRelease->getId(), starredRelease->getId());
@@ -51,7 +51,7 @@ TEST_F(DatabaseFixture, StarredRelease)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto releases{ Release::find(session, Release::FindParameters {}) };
EXPECT_EQ(releases.results.size(), 1);
@@ -62,6 +62,18 @@ TEST_F(DatabaseFixture, StarredRelease)
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{});
}
}
TEST_F(DatabaseFixture, Starredrelease_PendingDestroy)
@@ -71,7 +83,7 @@ TEST_F(DatabaseFixture, Starredrelease_PendingDestroy)
ScopedStarredRelease starredRelease{ session, release.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
auto releases{ Release::find(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) };
EXPECT_EQ(releases.results.size(), 1);
@@ -94,14 +106,14 @@ TEST_F(DatabaseFixture, StarredRelease_dateTime)
const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} };
{
auto transaction{ session.createSharedTransaction() };
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.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
starredRelease1.get().modify()->setDateTime(dateTime);
starredRelease2.get().modify()->setDateTime(dateTime.addSecs(-1));
@@ -112,7 +124,7 @@ TEST_F(DatabaseFixture, StarredRelease_dateTime)
EXPECT_EQ(releases.results[1], starredRelease2->getRelease()->getId());
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
starredRelease1.get().modify()->setDateTime(dateTime);
starredRelease2.get().modify()->setDateTime(dateTime.addSecs(1));
@@ -26,98 +26,110 @@ using ScopedStarredTrack = ScopedEntity<Database::StarredTrack>;
TEST_F(DatabaseFixture, StarredTrack)
{
ScopedTrack track {session, "MyTrack"};
ScopedUser user {session, "MyUser"};
ScopedUser user2 {session, "MyUser2"};
ScopedTrack track{ session, "MyTrack" };
ScopedUser user{ session, "MyUser" };
ScopedUser user2{ session, "MyUser2" };
{
auto transaction {session.createSharedTransaction()};
auto transaction {session.createReadTransaction()};
auto starredTrack {StarredTrack::find(session, track->getId(), user->getId(), FeedbackBackend::Internal)};
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 {})};
auto tracks{ Track::findIds(session, Track::FindParameters {}) };
EXPECT_EQ(tracks.results.size(), 1);
}
ScopedStarredTrack starredTrack {session, track.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal};
ScopedStarredTrack starredTrack{ session, track.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal };
{
auto transaction {session.createSharedTransaction()};
auto transaction {session.createReadTransaction()};
auto gotTrack {StarredTrack::find(session, track->getId(), user->getId(), 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.createSharedTransaction()};
auto transaction {session.createReadTransaction()};
auto tracks {Track::findIds(session, Track::FindParameters {})};
auto tracks{ Track::findIds(session, Track::FindParameters {}) };
EXPECT_EQ(tracks.results.size(), 1);
tracks = Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal));
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));
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{});
}
}
TEST_F(DatabaseFixture, Starredtrack_PendingDestroy)
{
ScopedTrack track {session, "MyTrack"};
ScopedUser user {session, "MyUser"};
ScopedStarredTrack starredTrack {session, track.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal};
ScopedTrack track{ session, "MyTrack" };
ScopedUser user{ session, "MyUser" };
ScopedStarredTrack starredTrack{ session, track.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal };
{
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createWriteTransaction()};
auto tracks {Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal))};
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));
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"};
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};
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}};
const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} };
{
auto transaction {session.createSharedTransaction()};
auto transaction {session.createReadTransaction()};
auto tracks {Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal))};
auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) };
EXPECT_EQ(tracks.results.size(), 2);
}
{
auto transaction {session.createUniqueTransaction()};
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))};
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.createUniqueTransaction()};
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))};
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());
+12 -12
View File
@@ -26,7 +26,7 @@ using namespace Database;
TEST_F(DatabaseFixture, Track)
{
{
auto transaction{ session.createSharedTransaction() };
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);
@@ -42,7 +42,7 @@ TEST_F(DatabaseFixture, Track)
ScopedTrack track{ session, "MyTrackFile" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(Track::find(session, Track::FindParameters{}).results.size(), 1);
EXPECT_EQ(Track::getCount(session), 1);
@@ -69,7 +69,7 @@ TEST_F(DatabaseFixture, MultipleTracks)
ScopedTrack track2{ session, "MyTrackFile2" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_TRUE(track1.getId() != track2.getId());
EXPECT_TRUE(track1.get() != track2.get());
@@ -87,7 +87,7 @@ TEST_F(DatabaseFixture, MultipleTracksSearchByFilter)
ScopedTrack track6{ session, "" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track1.get().modify()->setName("MyTrack");
track2.get().modify()->setName("MyTrack%");
track3.get().modify()->setName("MyTrack%Foo");
@@ -97,7 +97,7 @@ TEST_F(DatabaseFixture, MultipleTracksSearchByFilter)
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
{
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"Track"})) };
@@ -128,19 +128,19 @@ TEST_F(DatabaseFixture, Track_date)
ScopedTrack track{ session, "MyTrack" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(track->getYear(), std::nullopt);
EXPECT_EQ(track->getOriginalYear(), std::nullopt);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setDate(Wt::WDate{ 1995, 5, 5 });
track.get().modify()->setOriginalDate(Wt::WDate{ 1994, 2, 2 });
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(track->getYear(), 1995);
EXPECT_EQ(track->getOriginalYear(), 1994);
}
@@ -153,24 +153,24 @@ TEST_F(DatabaseFixture, Track_writtenAfter)
const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 1}, Wt::WTime {12, 30, 20} };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
track.get().modify()->setLastWriteTime(dateTime);
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto tracks{ Track::findIds(session, Track::FindParameters {}) };
EXPECT_EQ(tracks.results.size(), 1);
}
{
auto transaction{ session.createSharedTransaction() };
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.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setWrittenAfter(dateTime.addSecs(+1))) };
EXPECT_EQ(tracks.results.size(), 0);
}
@@ -31,21 +31,21 @@ TEST_F(DatabaseFixture, TrackBookmark)
ScopedUser user {session, "MyUser"};
{
auto transaction {session.createSharedTransaction()};
auto transaction {session.createReadTransaction()};
EXPECT_EQ(TrackBookmark::getCount(session), 0);
}
ScopedTrackBookmark bookmark {session, user.lockAndGet(), track.lockAndGet()};
{
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createWriteTransaction()};
bookmark.get().modify()->setComment("MyComment");
bookmark.get().modify()->setOffset(std::chrono::milliseconds {5});
}
{
auto transaction {session.createSharedTransaction()};
auto transaction {session.createReadTransaction()};
EXPECT_EQ(TrackBookmark::getCount(session), 1);
@@ -54,7 +54,7 @@ TEST_F(DatabaseFixture, TrackBookmark)
EXPECT_EQ(bookmarks.results.front(), bookmark.getId());
}
{
auto transaction {session.createSharedTransaction()};
auto transaction {session.createReadTransaction()};
auto userBookmark {TrackBookmark::find(session, user.getId(), track.getId())};
ASSERT_TRUE(userBookmark);
@@ -31,14 +31,14 @@ TEST_F(DatabaseFixture, TrackFeatures)
ScopedUser user {session, "MyUser"};
{
auto transaction {session.createSharedTransaction()};
auto transaction {session.createReadTransaction()};
EXPECT_EQ(TrackFeatures::getCount(session), 0);
}
ScopedTrackFeatures trackFeatures {session, track.lockAndGet(), ""};
{
auto transaction {session.createUniqueTransaction()};
auto transaction {session.createWriteTransaction()};
EXPECT_EQ(TrackFeatures::getCount(session), 1);
auto allTrackFeatures {TrackFeatures::find(session)};
+20 -20
View File
@@ -27,14 +27,14 @@ TEST_F(DatabaseFixture, SingleTrackList)
{
ScopedUser user{ session, "MyUser" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(TrackList::getCount(session), 0);
}
ScopedTrackList trackList{ session, "MytrackList", TrackListType::Playlist, false, user.lockAndGet() };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
EXPECT_EQ(TrackList::getCount(session), 1);
}
}
@@ -47,7 +47,7 @@ TEST_F(DatabaseFixture, SingleTrackListSingleTrack)
ScopedTrack track{ session, "MyTrack" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) };
EXPECT_EQ(tracks.results.size(), 0);
@@ -57,13 +57,13 @@ TEST_F(DatabaseFixture, SingleTrackListSingleTrack)
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
session.create<TrackListEntry>(track.get(), trackList1.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) };
ASSERT_EQ(tracks.results.size(), 1);
@@ -82,7 +82,7 @@ TEST_F(DatabaseFixture, TrackList_SortMethod)
ScopedTrack track{ session, "MyTrack" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::Name)) };
ASSERT_EQ(trackLists.results.size(), 2);
@@ -91,14 +91,14 @@ TEST_F(DatabaseFixture, TrackList_SortMethod)
}
{
auto transaction{ session.createUniqueTransaction() };
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.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) };
ASSERT_EQ(trackLists.results.size(), 2);
@@ -107,14 +107,14 @@ TEST_F(DatabaseFixture, TrackList_SortMethod)
}
{
auto transaction{ session.createUniqueTransaction() };
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.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) };
ASSERT_EQ(trackLists.results.size(), 2);
@@ -133,12 +133,12 @@ TEST_F(DatabaseFixture, SingleTrackListMultipleTrack)
{
tracks.emplace_back(session, "MyTrack" + std::to_string(i));
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
session.create<TrackListEntry>(tracks.back().get(), trackList.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
ASSERT_EQ(trackList->getCount(), tracks.size());
const auto trackIds{ trackList->getTrackIds() };
@@ -161,21 +161,21 @@ TEST_F(DatabaseFixture, SingleTrackListSingleTrackWithCluster)
ScopedTrack track{ session, "MyTrack" };
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) };
EXPECT_EQ(trackLists.results.size(), 0);
}
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
session.create<TrackListEntry>(track.get(), trackList1.get());
cluster.get().modify()->addTrack(track.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters({cluster.getId()})) };
ASSERT_EQ(trackLists.results.size(), 1);
@@ -191,24 +191,24 @@ TEST_F(DatabaseFixture, SingleTrackList_getEntries)
ScopedTrack track2{ session, "MyTrack" };
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
session.create<TrackListEntry>(track1.get(), trackList.get());
}
{
auto transaction{ session.createSharedTransaction() };
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.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
session.create<TrackListEntry>(track2.get(), trackList.get());
}
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
auto entries{ trackList.get()->getEntries() };
ASSERT_EQ(entries.size(), 2);
EXPECT_EQ(entries[0]->getTrack()->getId(), track1.getId());
@@ -216,7 +216,7 @@ TEST_F(DatabaseFixture, SingleTrackList_getEntries)
}
{
auto transaction{ session.createSharedTransaction() };
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());
@@ -29,7 +29,7 @@
#include "services/database/StarredTrack.hpp"
#include "services/database/Track.hpp"
#include "services/database/User.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "internal/InternalBackend.hpp"
#include "listenbrainz/ListenBrainzBackend.hpp"
@@ -44,15 +44,15 @@ namespace Feedback
FeedbackService::FeedbackService(boost::asio::io_context& ioContext, Db& db)
: _db{ db }
{
LMS_LOG(SCROBBLING, INFO) << "Starting service...";
LMS_LOG(SCROBBLING, INFO, "Starting service...");
_backends.emplace(Database::FeedbackBackend::Internal, std::make_unique<InternalBackend>(_db));
_backends.emplace(Database::FeedbackBackend::ListenBrainz, std::make_unique<ListenBrainz::ListenBrainzBackend>(ioContext, _db));
LMS_LOG(SCROBBLING, INFO) << "Service started!";
LMS_LOG(SCROBBLING, INFO, "Service started!");
}
FeedbackService::~FeedbackService()
{
LMS_LOG(SCROBBLING, INFO) << "Service stopped!";
LMS_LOG(SCROBBLING, INFO, "Service stopped!");
}
std::optional<Database::FeedbackBackend> FeedbackService::getUserFeedbackBackend(UserId userId)
@@ -60,7 +60,7 @@ namespace Feedback
std::optional<Database::FeedbackBackend> feedbackBackend;
Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
if (const User::pointer user{ User::find(session, userId) })
feedbackBackend = user->getFeedbackBackend();
@@ -101,7 +101,7 @@ namespace Feedback
searchParams.setRange(params.range);
Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
return Artist::findIds(session, searchParams);
}
@@ -139,7 +139,7 @@ namespace Feedback
searchParams.setRange(params.range);
Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
return Release::findIds(session, searchParams);
}
@@ -177,7 +177,7 @@ namespace Feedback
searchParams.setRange(params.range);
Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
return Track::findIds(session, searchParams);
}
@@ -37,7 +37,7 @@ namespace Feedback
typename StarredObjType::IdType starredObjId;
{
Session& session{ _db.getTLSSession() };
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
typename StarredObjType::pointer starredObj{ StarredObjType::find(session, objId, userId, *backend) };
if (!starredObj)
@@ -68,7 +68,7 @@ namespace Feedback
typename StarredObjType::IdType starredObjId;
{
Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
typename StarredObjType::pointer starredObj{ StarredObjType::find(session, objId, userId, *backend) };
if (!starredObj)
@@ -82,28 +82,20 @@ namespace Feedback
template <typename ObjType, typename ObjIdType, typename StarredObjType>
bool FeedbackService::isStarred(UserId userId, ObjIdType objId)
{
const auto backend{ getUserFeedbackBackend(userId) };
if (!backend)
return false;
Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
typename StarredObjType::pointer starredObj{ StarredObjType::find(session, objId, userId, *backend) };
typename StarredObjType::pointer starredObj{ StarredObjType::find(session, objId, userId) };
return starredObj && (starredObj->getSyncState() != SyncState::PendingRemove);
}
template <typename ObjType, typename ObjIdType, typename StarredObjType>
Wt::WDateTime FeedbackService::getStarredDateTime(UserId userId, ObjIdType objId)
{
const auto backend{ getUserFeedbackBackend(userId) };
if (!backend)
return {};
Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
typename StarredObjType::pointer starredObj{ StarredObjType::find(session, objId, userId, *backend) };
typename StarredObjType::pointer starredObj{ StarredObjType::find(session, objId, userId) };
if (starredObj && (starredObj->getSyncState() != SyncState::PendingRemove))
return starredObj->getDateTime();
@@ -32,7 +32,7 @@ namespace Feedback
template <typename StarredObjType>
void onStarred(Database::Session& session, typename StarredObjType::IdType id)
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
if (auto starredObj{ StarredObjType::find(session, id) })
starredObj.modify()->setSyncState(Database::SyncState::Synchronized);
@@ -41,7 +41,7 @@ namespace Feedback
template <typename StarredObjType>
void onUnstarred(Database::Session& session, typename StarredObjType::IdType id)
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
if (auto starredObj{ StarredObjType::find(session, id) })
starredObj.remove();
@@ -57,7 +57,7 @@ namespace Feedback::ListenBrainz
const Wt::Json::Array& feedbacks = root.get("feedback");
LOG(DEBUG) << "Got " << feedbacks.size() << " feedbacks";
LOG(DEBUG, "Got " << feedbacks.size() << " feedbacks");
if (feedbacks.empty())
return res;
@@ -72,17 +72,17 @@ namespace Feedback::ListenBrainz
}
catch (const Exception& e)
{
LOG(DEBUG) << "Cannot parse feedback: " << e.what() << ", skipping";
LOG(DEBUG, "Cannot parse feedback: " << e.what() << ", skipping");
}
catch (const Wt::WException& e)
{
LOG(DEBUG) << "Cannot parse feedback: " << e.what() << ", skipping";
LOG(DEBUG, "Cannot parse feedback: " << e.what() << ", skipping");
}
}
}
catch (const Wt::WException& error)
{
LOG(ERROR) << "Cannot parse 'feedback' result: " << error.what();
LOG(ERROR, "Cannot parse 'feedback' result: " << error.what());
}
return res;
@@ -53,7 +53,7 @@ namespace Feedback::ListenBrainz
}
catch (const Wt::WException& e)
{
LOG(ERROR) << "Cannot parse listen count response: " << e.what();
LOG(ERROR, "Cannot parse listen count response: " << e.what());
return std::nullopt;
}
}
@@ -66,7 +66,7 @@ namespace Feedback::ListenBrainz
, _maxSyncFeedbackCount{ Service<IConfig>::get()->getULong("listenbrainz-max-sync-feedback-count", 1000) }
, _syncFeedbacksPeriod{ Service<IConfig>::get()->getULong("listenbrainz-sync-feedbacks-period-hours", 1) }
{
LOG(INFO) << "Starting Feedbacks synchronizer, maxSyncFeedbackCount = " << _maxSyncFeedbackCount << ", _syncFeedbacksPeriod = " << _syncFeedbacksPeriod.count() << " hours";
LOG(INFO, "Starting Feedbacks synchronizer, maxSyncFeedbackCount = " << _maxSyncFeedbackCount << ", _syncFeedbacksPeriod = " << _syncFeedbacksPeriod.count() << " hours");
scheduleSync(std::chrono::seconds{ 30 });
}
@@ -77,7 +77,7 @@ namespace Feedback::ListenBrainz
{
Database::Session& session{ _db.getTLSSession() };
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
Database::StarredTrack::pointer starredTrack{ Database::StarredTrack::find(session, starredTrackId) };
if (!starredTrack)
@@ -95,7 +95,7 @@ namespace Feedback::ListenBrainz
case FeedbackType::Erase:
if (!recordingMBID)
{
LOG(DEBUG) << "Track has no recording MBID: erasing star";
LOG(DEBUG, "Track has no recording MBID: erasing star");
starredTrack.remove();
}
else
@@ -112,7 +112,7 @@ namespace Feedback::ListenBrainz
if (!recordingMBID)
{
LOG(DEBUG) << "Track has no recording MBID: skipping";
LOG(DEBUG, "Track has no recording MBID: skipping");
return;
}
@@ -142,7 +142,7 @@ namespace Feedback::ListenBrainz
}
catch (Exception& e)
{
LOG(DEBUG) << "Cannot send feedback: " << e.what();
LOG(DEBUG, "Cannot send feedback: " << e.what());
}
}
@@ -151,12 +151,12 @@ namespace Feedback::ListenBrainz
assert(_strand.running_in_this_thread());
Database::Session& session{ _db.getTLSSession() };
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
Database::StarredTrack::pointer starredTrack{ Database::StarredTrack::find(session, starredTrackId) };
if (!starredTrack)
{
LOG(DEBUG) << "Starred track not found. deleted?";
LOG(DEBUG, "Starred track not found. deleted?");
return;
}
@@ -166,23 +166,23 @@ namespace Feedback::ListenBrainz
{
case FeedbackType::Love:
starredTrack.modify()->setSyncState(Database::SyncState::Synchronized);
LOG(DEBUG) << "State set to synchronized";
LOG(DEBUG, "State set to synchronized");
if (userContext.feedbackCount)
{
(*userContext.feedbackCount)++;
LOG(DEBUG) << "Feedback count set to " << *userContext.feedbackCount << " for user '" << userContext.listenBrainzUserName << "'";
LOG(DEBUG, "Feedback count set to " << *userContext.feedbackCount << " for user '" << userContext.listenBrainzUserName << "'");
}
break;
case FeedbackType::Erase:
starredTrack.remove();
LOG(DEBUG) << "Removed starred track";
LOG(DEBUG, "Removed starred track");
if (userContext.feedbackCount && *userContext.feedbackCount > 0)
{
(*userContext.feedbackCount)--;
LOG(DEBUG) << "Feedback count set to " << *userContext.feedbackCount << " for user '" << userContext.listenBrainzUserName << "'";
LOG(DEBUG, "Feedback count set to " << *userContext.feedbackCount << " for user '" << userContext.listenBrainzUserName << "'");
}
break;
@@ -202,7 +202,7 @@ namespace Feedback::ListenBrainz
{
Database::Session& session {_db.getTLSSession()};
auto transaction {session.createSharedTransaction()};
auto transaction {session.createReadTransaction()};
StarredTrack::FindParameters params;
params.setFeedbackBackend(Database::FeedbackBackend::ListenBrainz, scrobblingState)
@@ -211,7 +211,7 @@ namespace Feedback::ListenBrainz
pendingFeedbacks = StarredTrack::find(session, params);
}
LOG(DEBUG) << "Queing " << pendingFeedbacks.results.size() << " pending '" << (feedbackType == FeedbackType::Love ? "love" : "erase") << "' feedbacks";
LOG(DEBUG, "Queing " << pendingFeedbacks.results.size() << " pending '" << (feedbackType == FeedbackType::Love ? "love" : "erase") << "' feedbacks");
for (const StarredTrackId starredTrackId : pendingFeedbacks.results)
enqueFeedback(feedbackType, starredTrackId);
@@ -247,13 +247,13 @@ namespace Feedback::ListenBrainz
if (_syncFeedbacksPeriod.count() == 0 || _maxSyncFeedbackCount == 0)
return;
LOG(DEBUG) << "Scheduled sync in " << fromNow.count() << " seconds...";
LOG(DEBUG, "Scheduled sync in " << fromNow.count() << " seconds...");
_syncTimer.expires_after(fromNow);
_syncTimer.async_wait(boost::asio::bind_executor(_strand, [this](const boost::system::error_code& ec)
{
if (ec == boost::asio::error::operation_aborted)
{
LOG(DEBUG) << "getFeedbacks aborted";
LOG(DEBUG, "getFeedbacks aborted");
return;
}
else if (ec)
@@ -267,7 +267,7 @@ namespace Feedback::ListenBrainz
void FeedbacksSynchronizer::startSync()
{
LOG(DEBUG) << "Starting sync!";
LOG(DEBUG, "Starting sync!");
assert(!isSyncing());
assert(_strand.running_in_this_thread());
@@ -277,7 +277,7 @@ namespace Feedback::ListenBrainz
Database::RangeResults<Database::UserId> userIds;
{
Database::Session& session{ _db.getTLSSession() };
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
userIds = Database::User::find(_db.getTLSSession(), Database::User::FindParameters{}.setFeedbackBackend(Database::FeedbackBackend::ListenBrainz));
}
@@ -303,7 +303,7 @@ namespace Feedback::ListenBrainz
{
_strand.dispatch([this, &context]
{
LOG(INFO) << "Feedback sync done for user '" << context.listenBrainzUserName << "', fetched: " << context.fetchedFeedbackCount << ", matched: " << context.matchedFeedbackCount << ", imported: " << context.importedFeedbackCount;
LOG(INFO, "Feedback sync done for user '" << context.listenBrainzUserName << "', fetched: " << context.fetchedFeedbackCount << ", matched: " << context.matchedFeedbackCount << ", imported: " << context.importedFeedbackCount);
context.syncing = false;
if (!isSyncing())
@@ -356,11 +356,11 @@ namespace Feedback::ListenBrainz
std::string msgBodyCopy{ msgBody };
_strand.dispatch([this, msgBodyCopy, &context]
{
LOG(DEBUG) << "Current feedback count = " << (context.feedbackCount ? *context.feedbackCount : 0) << " for user '" << context.listenBrainzUserName << "'";
LOG(DEBUG, "Current feedback count = " << (context.feedbackCount ? *context.feedbackCount : 0) << " for user '" << context.listenBrainzUserName << "'");
const auto totalFeedbackCount = parseTotalFeedbackCount(msgBodyCopy);
if (totalFeedbackCount)
LOG(DEBUG) << "Feedback count for listenbrainz user '" << context.listenBrainzUserName << "' = " << *totalFeedbackCount;
LOG(DEBUG, "Feedback count for listenbrainz user '" << context.listenBrainzUserName << "' = " << *totalFeedbackCount);
bool needSync{ totalFeedbackCount && (!context.feedbackCount || *context.feedbackCount != *totalFeedbackCount) };
context.feedbackCount = totalFeedbackCount;
@@ -416,7 +416,7 @@ namespace Feedback::ListenBrainz
{
const FeedbacksParser::Result parseResult{ FeedbacksParser::parse(msgBody) };
LOG(DEBUG) << "Parsed " << parseResult.feedbackCount << " feedbacks, found " << parseResult.feedbacks.size() << " usable entries";
LOG(DEBUG, "Parsed " << parseResult.feedbackCount << " feedbacks, found " << parseResult.feedbacks.size() << " usable entries");
context.fetchedFeedbackCount += parseResult.feedbackCount;
for (const Feedback& feedback : parseResult.feedbacks)
@@ -437,23 +437,21 @@ namespace Feedback::ListenBrainz
TrackId trackId;
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const std::vector<Track::pointer> tracks{ Track::findByRecordingMBID(session, feedback.recordingMBID) };
if (tracks.size() > 1)
{
LOG(DEBUG) << "Too many matches for feedback '" << feedback << "': duplicate recording MBIDs found";
LOG(DEBUG, "Too many matches for feedback '" << feedback << "': duplicate recording MBIDs found");
return;
}
else if (tracks.empty())
{
LOG(DEBUG) << "Cannot match feedback '" << feedback << "': no track found for this recording MBID";
LOG(DEBUG, "Cannot match feedback '" << feedback << "': no track found for this recording MBID");
return;
}
trackId = tracks.front()->getId();
const StarredTrack::pointer starredTrack{ StarredTrack::find(session, trackId, context.userId, Database::FeedbackBackend::ListenBrainz) };
needImport = !starredTrack;
needImport = !StarredTrack::exists(session, trackId, context.userId, Database::FeedbackBackend::ListenBrainz);
// don't update starred date time
// no need to update state if it was found as not synchronized
@@ -463,9 +461,9 @@ namespace Feedback::ListenBrainz
if (needImport)
{
LOG(DEBUG) << "Importing feedback '" << feedback << "'";
LOG(DEBUG, "Importing feedback '" << feedback << "'");
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
const Track::pointer track{ Track::find(session, trackId) };
if (!track)
@@ -483,7 +481,7 @@ namespace Feedback::ListenBrainz
}
else
{
LOG(DEBUG) << "No need to import feedback '" << feedback << "', already imported";
LOG(DEBUG, "No need to import feedback '" << feedback << "', already imported");
context.matchedFeedbackCount++;
}
}
@@ -26,7 +26,7 @@
#include "services/database/Track.hpp"
#include "utils/IConfig.hpp"
#include "utils/http/IClient.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Service.hpp"
#include "Utils.hpp"
@@ -37,7 +37,7 @@ namespace Feedback::ListenBrainz
template <typename StarredObjType>
void onStarred(Database::Session& session, typename StarredObjType::IdType id)
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
if (auto starredObj{ StarredObjType::find(session, id) })
{
@@ -49,7 +49,7 @@ namespace Feedback::ListenBrainz
template <typename StarredObjType>
void onUnstarred(Database::Session& session, typename StarredObjType::IdType id)
{
auto transaction{ session.createUniqueTransaction() };
auto transaction{ session.createWriteTransaction() };
if (auto starredObj{ StarredObjType::find(session, id) })
starredObj.remove();
@@ -63,12 +63,12 @@ namespace Feedback::ListenBrainz
, _client{ Http::createClient(_ioContext, _baseAPIUrl) }
, _feedbacksSynchronizer{ _ioContext, db, *_client }
{
LOG(INFO) << "Starting ListenBrainz feedback backend... API endpoint = '" << _baseAPIUrl << "'";
LOG(INFO, "Starting ListenBrainz feedback backend... API endpoint = '" << _baseAPIUrl << "'");
}
ListenBrainzBackend::~ListenBrainzBackend()
{
LOG(INFO) << "Stopped ListenBrainz feedback backend!";
LOG(INFO, "Stopped ListenBrainz feedback backend!");
}
void ListenBrainzBackend::onStarred(Database::StarredArtistId starredArtistId)
@@ -29,7 +29,7 @@ namespace Feedback::ListenBrainz::Utils
{
std::optional<UUID> getListenBrainzToken(Database::Session& session, Database::UserId userId)
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
const Database::User::pointer user{ Database::User::find(session, userId) };
if (!user)
@@ -46,13 +46,13 @@ namespace Feedback::ListenBrainz::Utils
Wt::Json::Object root;
if (!Wt::Json::parse(std::string{ msgBody }, root, error))
{
LOG(ERROR) << "Cannot parse 'validate-token' result: " << error.what();
LOG(ERROR, "Cannot parse 'validate-token' result: " << error.what());
return listenBrainzUserName;
}
if (!root.get("valid").orIfNull(false))
{
LOG(INFO) << "Invalid listenbrainz user";
LOG(INFO, "Invalid listenbrainz user");
return listenBrainzUserName;
}
@@ -20,10 +20,10 @@
#pragma once
#include "services/database/UserId.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/UUID.hpp"
#define LOG(sev) LMS_LOG(FEEDBACK, sev) << "[listenbrainz] "
#define LOG(sev, message) LMS_LOG(FEEDBACK, sev, "[listenbrainz] " << message)
namespace Database
{
@@ -26,7 +26,7 @@
#include "playlist-constraints/ConsecutiveArtists.hpp"
#include "playlist-constraints/ConsecutiveReleases.hpp"
#include "playlist-constraints/DuplicateTracks.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Recommendation
{
@@ -48,7 +48,7 @@ namespace Recommendation
std::vector<TrackId> PlaylistGeneratorService::extendPlaylist(TrackListId tracklistId, std::size_t maxCount) const
{
LMS_LOG(RECOMMENDATION, DEBUG) << "Requested to extend playlist by " << maxCount << " similar tracks";
LMS_LOG(RECOMMENDATION, DEBUG, "Requested to extend playlist by " << maxCount << " similar tracks");
// supposed to be ordered from most similar to least similar
std::vector<TrackId> similarTracks{ _recommendationService.findSimilarTracks(tracklistId, maxCount * 2) }; // ask for more tracks than we need as it will be easier to respect constraints
@@ -100,7 +100,7 @@ namespace Recommendation
TrackContainer tracks;
Session& dbSession{ _db.getTLSSession() };
auto transaction{ dbSession.createSharedTransaction() };
auto transaction{ dbSession.createReadTransaction() };
Track::FindParameters params;
params.setTrackList(tracklistId);
@@ -29,7 +29,7 @@
#include "services/database/Session.hpp"
#include "services/database/ScanSettings.hpp"
#include "utils/Exception.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Recommendation
{
@@ -37,7 +37,7 @@ namespace Recommendation
{
Database::ScanSettings::SimilarityEngineType getSimilarityEngineType(Database::Session& session)
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
return Database::ScanSettings::get(session)->getSimilarityEngineType();
}
@@ -42,7 +42,7 @@ namespace Recommendation {
return {};
Session& dbSession{ _db.getTLSSession() };
auto transaction{ dbSession.createSharedTransaction() };
auto transaction{ dbSession.createReadTransaction() };
const auto similarTrackIds{ Track::findSimilarTrackIds(dbSession, trackIds, Range {0, maxCount}) };
return std::move(similarTrackIds.results);
@@ -56,7 +56,7 @@ namespace Recommendation {
{
Session& dbSession{ _db.getTLSSession() };
auto transaction{ dbSession.createSharedTransaction() };
auto transaction{ dbSession.createReadTransaction() };
const TrackList::pointer trackList{ TrackList::find(dbSession, tracklistId) };
if (!trackList)
@@ -78,7 +78,7 @@ namespace Recommendation {
{
Session& dbSession{ _db.getTLSSession() };
auto transaction{ dbSession.createSharedTransaction() };
auto transaction{ dbSession.createReadTransaction() };
auto release{ Release::find(dbSession, releaseId) };
if (!release)
@@ -98,7 +98,7 @@ namespace Recommendation {
return {};
Session& dbSession{ _db.getTLSSession() };
auto transaction{ dbSession.createSharedTransaction() };
auto transaction{ dbSession.createReadTransaction() };
auto artist{ Artist::find(dbSession, artistId) };
if (!artist)
@@ -30,397 +30,384 @@
#include "services/database/TrackFeatures.hpp"
#include "services/database/TrackList.hpp"
#include "som/DataNormalizer.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Random.hpp"
namespace Recommendation {
using namespace Database;
std::unique_ptr<IEngine> createFeaturesEngine(Db& db)
namespace Recommendation
{
return std::make_unique<FeaturesEngine>(db);
}
const FeatureSettingsMap&
FeaturesEngine::getDefaultTrainFeatureSettings()
{
static const FeatureSettingsMap defaultTrainFeatureSettings
{
{ "lowlevel.spectral_energyband_high.mean", {1}},
{ "lowlevel.spectral_rolloff.median", {1}},
{ "lowlevel.spectral_contrast_valleys.var", {1}},
{ "lowlevel.erbbands.mean", {1}},
{ "lowlevel.gfcc.mean", {1}},
};
return defaultTrainFeatureSettings;
}
static
std::optional<SOM::InputVector>
convertFeatureValuesMapToInputVector(const FeatureValuesMap& featureValuesMap, std::size_t nbDimensions)
{
std::size_t i {};
std::optional<SOM::InputVector> res {SOM::InputVector {nbDimensions}};
for (const auto& [featureName, values] : featureValuesMap)
{
if (values.size() != getFeatureDef(featureName).nbDimensions)
{
LMS_LOG(RECOMMENDATION, WARNING) << "Dimension mismatch for feature '" << featureName << "'. Expected " << getFeatureDef(featureName).nbDimensions << ", got " << values.size();
res.reset();
break;
}
for (double val : values)
(*res)[i++] = val;
}
return res;
}
static
SOM::InputVector
getInputVectorWeights(const FeatureSettingsMap& featureSettingsMap, std::size_t nbDimensions)
{
SOM::InputVector weights {nbDimensions};
std::size_t index {};
for (const auto& [featureName, featureSettings] : featureSettingsMap)
{
const std::size_t featureNbDimensions {getFeatureDef(featureName).nbDimensions};
for (std::size_t i {}; i < featureNbDimensions; ++i)
weights[index++] = (1. / featureNbDimensions * featureSettings.weight);
}
assert(index == nbDimensions);
return weights;
}
void
FeaturesEngine::loadFromTraining(const TrainSettings& trainSettings, const ProgressCallback& progressCallback)
{
LMS_LOG(RECOMMENDATION, INFO) << "Constructing features classifier...";
std::unordered_set<FeatureName> featureNames;
std::transform(std::cbegin(trainSettings.featureSettingsMap), std::cend(trainSettings.featureSettingsMap), std::inserter(featureNames, std::begin(featureNames)),
[](const auto& itFeatureSetting) { return itFeatureSetting.first; });
const std::size_t nbDimensions {std::accumulate(std::cbegin(featureNames), std::cend(featureNames), std::size_t {0},
[](std::size_t sum, const FeatureName& featureName) { return sum + getFeatureDef(featureName).nbDimensions; })};
LMS_LOG(RECOMMENDATION, DEBUG) << "Features dimension = " << nbDimensions;
Session& session {_db.getTLSSession()};
RangeResults<TrackFeaturesId> trackFeaturesIds;
{
auto transaction {session.createSharedTransaction()};
LMS_LOG(RECOMMENDATION, DEBUG) << "Getting Track features...";
trackFeaturesIds = TrackFeatures::find(session);
LMS_LOG(RECOMMENDATION, DEBUG) << "Getting Track features DONE (found " << trackFeaturesIds.results.size() << " track features)";
}
std::vector<SOM::InputVector> samples;
std::vector<TrackId> samplesTrackIds;
samples.reserve(trackFeaturesIds.results.size());
samplesTrackIds.reserve(trackFeaturesIds.results.size());
LMS_LOG(RECOMMENDATION, DEBUG) << "Extracting features...";
// TODO handle errors using exceptions
for (const TrackFeaturesId trackFeaturesId : trackFeaturesIds.results)
{
if (_loadCancelled)
return;
auto transaction {session.createSharedTransaction()};
TrackFeatures::pointer trackFeatures {TrackFeatures::find(session, trackFeaturesId)};
if (!trackFeatures)
continue;
FeatureValuesMap featureValuesMap {trackFeatures->getFeatureValuesMap(featureNames)};
if (featureValuesMap.empty())
continue;
std::optional<SOM::InputVector> inputVector {convertFeatureValuesMapToInputVector(featureValuesMap, nbDimensions)};
if (!inputVector)
continue;
samples.emplace_back(std::move(*inputVector));
samplesTrackIds.emplace_back(trackFeatures->getTrack()->getId());
}
LMS_LOG(RECOMMENDATION, DEBUG) << "Extracting features DONE";
if (samples.empty())
{
LMS_LOG(RECOMMENDATION, INFO) << "Nothing to classify!";
return;
}
LMS_LOG(RECOMMENDATION, DEBUG) << "Normalizing data...";
SOM::DataNormalizer dataNormalizer {nbDimensions};
dataNormalizer.computeNormalizationFactors(samples);
for (auto& sample : samples)
dataNormalizer.normalizeData(sample);
using namespace Database;
std::unique_ptr<IEngine> createFeaturesEngine(Db& db)
{
return std::make_unique<FeaturesEngine>(db);
}
namespace
{
std::optional<SOM::InputVector> convertFeatureValuesMapToInputVector(const FeatureValuesMap& featureValuesMap, std::size_t nbDimensions)
{
std::size_t i{};
std::optional<SOM::InputVector> res{ SOM::InputVector {nbDimensions} };
for (const auto& [featureName, values] : featureValuesMap)
{
if (values.size() != getFeatureDef(featureName).nbDimensions)
{
LMS_LOG(RECOMMENDATION, WARNING, "Dimension mismatch for feature '" << featureName << "'. Expected " << getFeatureDef(featureName).nbDimensions << ", got " << values.size());
res.reset();
break;
}
for (double val : values)
(*res)[i++] = val;
}
return res;
}
SOM::InputVector getInputVectorWeights(const FeatureSettingsMap& featureSettingsMap, std::size_t nbDimensions)
{
SOM::InputVector weights{ nbDimensions };
std::size_t index{};
for (const auto& [featureName, featureSettings] : featureSettingsMap)
{
const std::size_t featureNbDimensions{ getFeatureDef(featureName).nbDimensions };
for (std::size_t i{}; i < featureNbDimensions; ++i)
weights[index++] = (1. / featureNbDimensions * featureSettings.weight);
}
assert(index == nbDimensions);
return weights;
}
}
const FeatureSettingsMap& FeaturesEngine::getDefaultTrainFeatureSettings()
{
static const FeatureSettingsMap defaultTrainFeatureSettings
{
{ "lowlevel.spectral_energyband_high.mean", {1}},
{ "lowlevel.spectral_rolloff.median", {1}},
{ "lowlevel.spectral_contrast_valleys.var", {1}},
{ "lowlevel.erbbands.mean", {1}},
{ "lowlevel.gfcc.mean", {1}},
};
return defaultTrainFeatureSettings;
}
void FeaturesEngine::loadFromTraining(const TrainSettings& trainSettings, const ProgressCallback& progressCallback)
{
LMS_LOG(RECOMMENDATION, INFO, "Constructing features classifier...");
std::unordered_set<FeatureName> featureNames;
std::transform(std::cbegin(trainSettings.featureSettingsMap), std::cend(trainSettings.featureSettingsMap), std::inserter(featureNames, std::begin(featureNames)),
[](const auto& itFeatureSetting) { return itFeatureSetting.first; });
const std::size_t nbDimensions{ std::accumulate(std::cbegin(featureNames), std::cend(featureNames), std::size_t {0},
[](std::size_t sum, const FeatureName& featureName) { return sum + getFeatureDef(featureName).nbDimensions; }) };
LMS_LOG(RECOMMENDATION, DEBUG, "Features dimension = " << nbDimensions);
Session & session{ _db.getTLSSession() };
RangeResults<TrackFeaturesId> trackFeaturesIds;
{
auto transaction{ session.createReadTransaction() };
LMS_LOG(RECOMMENDATION, DEBUG, "Getting Track features...");
trackFeaturesIds = TrackFeatures::find(session);
LMS_LOG(RECOMMENDATION, DEBUG, "Getting Track features DONE (found " << trackFeaturesIds.results.size() << " track features)");
}
std::vector<SOM::InputVector> samples;
std::vector<TrackId> samplesTrackIds;
samples.reserve(trackFeaturesIds.results.size());
samplesTrackIds.reserve(trackFeaturesIds.results.size());
LMS_LOG(RECOMMENDATION, DEBUG, "Extracting features...");
// TODO handle errors using exceptions
for (const TrackFeaturesId trackFeaturesId : trackFeaturesIds.results)
{
if (_loadCancelled)
return;
auto transaction{ session.createReadTransaction() };
TrackFeatures::pointer trackFeatures{ TrackFeatures::find(session, trackFeaturesId) };
if (!trackFeatures)
continue;
FeatureValuesMap featureValuesMap{ trackFeatures->getFeatureValuesMap(featureNames) };
if (featureValuesMap.empty())
continue;
std::optional<SOM::InputVector> inputVector{ convertFeatureValuesMapToInputVector(featureValuesMap, nbDimensions) };
if (!inputVector)
continue;
samples.emplace_back(std::move(*inputVector));
samplesTrackIds.emplace_back(trackFeatures->getTrack()->getId());
}
LMS_LOG(RECOMMENDATION, DEBUG, "Extracting features DONE");
if (samples.empty())
{
LMS_LOG(RECOMMENDATION, INFO, "Nothing to classify!");
return;
}
SOM::Coordinate size {static_cast<SOM::Coordinate>(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";
size = 2;
}
LMS_LOG(RECOMMENDATION, INFO) << "Found " << samples.size() << " tracks, constructing a " << size << "*" << size << " network";
LMS_LOG(RECOMMENDATION, DEBUG, "Normalizing data...");
SOM::DataNormalizer dataNormalizer{ nbDimensions };
SOM::Network network {size, size, nbDimensions};
SOM::InputVector weights {getInputVectorWeights(trainSettings.featureSettingsMap, nbDimensions)};
network.setDataWeights(weights);
auto somProgressCallback{[&](const SOM::Network::CurrentIteration& iter)
{
LMS_LOG(RECOMMENDATION, DEBUG) << "Current pass = " << iter.idIteration << " / " << iter.iterationCount;
progressCallback(Progress {iter.idIteration, iter.iterationCount});
}};
dataNormalizer.computeNormalizationFactors(samples);
for (auto& sample : samples)
dataNormalizer.normalizeData(sample);
LMS_LOG(RECOMMENDATION, DEBUG) << "Training network...";
network.train(samples, trainSettings.iterationCount,
progressCallback ? somProgressCallback : SOM::Network::ProgressCallback {},
[this] { return _loadCancelled; });
LMS_LOG(RECOMMENDATION, DEBUG) << "Training network DONE";
SOM::Coordinate size{ static_cast<SOM::Coordinate>(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");
size = 2;
}
LMS_LOG(RECOMMENDATION, INFO, "Found " << samples.size() << " tracks, constructing a " << size << "*" << size << " network");
LMS_LOG(RECOMMENDATION, DEBUG) << "Classifying tracks...";
TrackPositions trackPositions;
for (std::size_t i {}; i < samples.size(); ++i)
{
if (_loadCancelled)
return;
SOM::Network network{ size, size, nbDimensions };
const SOM::Position position {network.getClosestRefVectorPosition(samples[i])};
SOM::InputVector weights{ getInputVectorWeights(trainSettings.featureSettingsMap, nbDimensions) };
network.setDataWeights(weights);
trackPositions[samplesTrackIds[i]].push_back(position);
}
LMS_LOG(RECOMMENDATION, DEBUG) << "Classifying tracks DONE";
auto somProgressCallback{ [&](const SOM::Network::CurrentIteration& iter)
{
LMS_LOG(RECOMMENDATION, DEBUG, "Current pass = " << iter.idIteration << " / " << iter.iterationCount);
progressCallback(Progress {iter.idIteration, iter.iterationCount});
} };
LMS_LOG(RECOMMENDATION, DEBUG, "Training network...");
network.train(samples, trainSettings.iterationCount,
progressCallback ? somProgressCallback : SOM::Network::ProgressCallback{},
[this] { return _loadCancelled; });
LMS_LOG(RECOMMENDATION, DEBUG, "Training network DONE");
load(std::move(network), std::move(trackPositions));
}
LMS_LOG(RECOMMENDATION, DEBUG, "Classifying tracks...");
TrackPositions trackPositions;
for (std::size_t i{}; i < samples.size(); ++i)
{
if (_loadCancelled)
return;
void
FeaturesEngine::loadFromCache(FeaturesEngineCache&& cache)
{
LMS_LOG(RECOMMENDATION, INFO) << "Constructing features classifier from cache...";
load(std::move(cache._network), cache._trackPositions);
}
TrackContainer
FeaturesEngine::findSimilarTracksFromTrackList(TrackListId trackListId, std::size_t maxCount) const
{
const TrackContainer trackIds {[&]
{
TrackContainer res;
Session& session {_db.getTLSSession()};
auto transaction {session.createSharedTransaction()};
const TrackList::pointer trackList {TrackList::find(session, trackListId)};
if (trackList)
res = trackList->getTrackIds();
return res;
}()};
return findSimilarTracks(trackIds, maxCount);
}
TrackContainer
FeaturesEngine::findSimilarTracks(const std::vector<TrackId>& tracksIds, std::size_t maxCount) const
{
auto similarTrackIds {getSimilarObjects(tracksIds, _trackMatrix, _trackPositions, maxCount)};
Session& session {_db.getTLSSession()};
{
// Report only existing ids, as tracks may have been removed a long time ago (refreshing the SOM takes some time)
auto transaction {session.createSharedTransaction()};
similarTrackIds.erase(std::remove_if(std::begin(similarTrackIds), std::end(similarTrackIds),
[&](TrackId trackId)
{
return !Track::exists(session, trackId);
}), std::end(similarTrackIds));
}
return similarTrackIds;
}
ReleaseContainer
FeaturesEngine::getSimilarReleases(ReleaseId releaseId, std::size_t maxCount) const
{
auto similarReleaseIds {getSimilarObjects({releaseId}, _releaseMatrix, _releasePositions, maxCount)};
Session& session {_db.getTLSSession()};
if (!similarReleaseIds.empty())
{
// Report only existing ids
auto transaction {session.createSharedTransaction()};
similarReleaseIds.erase(std::remove_if(std::begin(similarReleaseIds), std::end(similarReleaseIds),
[&](ReleaseId releaseId)
{
return !Release::exists(session, releaseId);
}), std::end(similarReleaseIds));
}
return similarReleaseIds;
}
ArtistContainer
FeaturesEngine::getSimilarArtists(ArtistId artistId, EnumSet<TrackArtistLinkType> linkTypes, std::size_t maxCount) const
{
auto getSimilarArtistIdsForLinkType {[&] (TrackArtistLinkType linkType)
{
ArtistContainer similarArtistIds;
const auto itArtists {_artistMatrix.find(linkType)};
if (itArtists == std::cend(_artistMatrix))
{
return similarArtistIds;
}
return getSimilarObjects({artistId}, itArtists->second, _artistPositions, maxCount);
}};
std::unordered_set<ArtistId> similarArtistIds;
for (TrackArtistLinkType linkType : linkTypes)
{
const auto similarArtistIdsForLinkType {getSimilarArtistIdsForLinkType(linkType)};
similarArtistIds.insert(std::begin(similarArtistIdsForLinkType), std::end(similarArtistIdsForLinkType));
}
ArtistContainer res(std::cbegin(similarArtistIds), std::cend(similarArtistIds));
Session& session {_db.getTLSSession()};
{
// Report only existing ids
auto transaction {session.createSharedTransaction()};
res.erase(std::remove_if(std::begin(res), std::end(res),
[&](ArtistId artistId)
{
return !Artist::exists(session, artistId);
}), std::end(res));
}
while (res.size() > maxCount)
res.erase(Random::pickRandom(res));
return res;
}
FeaturesEngineCache
FeaturesEngine::toCache() const
{
return FeaturesEngineCache {*_network, _trackPositions};
}
void
FeaturesEngine::load(bool forceReload, const ProgressCallback& progressCallback)
{
if (forceReload)
{
FeaturesEngineCache::invalidate();
}
else if (std::optional<FeaturesEngineCache> cache {FeaturesEngineCache::read()})
{
loadFromCache(std::move(*cache));
return;
}
TrainSettings trainSettings;
trainSettings.featureSettingsMap = getDefaultTrainFeatureSettings();
loadFromTraining(trainSettings, progressCallback);
if (!_loadCancelled && _network)
toCache().write();
}
void
FeaturesEngine::requestCancelLoad()
{
LMS_LOG(RECOMMENDATION, DEBUG) << "Requesting init cancellation";
_loadCancelled = true;
}
void
FeaturesEngine::load(const SOM::Network& network, const TrackPositions& trackPositions)
{
using namespace Database;
_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()};
_releaseMatrix = ReleaseMatrix {width, height};
_trackMatrix = TrackMatrix {width, height};
LMS_LOG(RECOMMENDATION, DEBUG) << "Constructing maps...";
Session& session {_db.getTLSSession()};
for (const auto& [trackId, positions] : trackPositions)
{
if (_loadCancelled)
return;
auto transaction {session.createSharedTransaction()};
const Track::pointer track {Track::find(session, trackId)};
if (!track)
continue;
for (const SOM::Position& position : positions)
{
Utils::push_back_if_not_present(_trackPositions[trackId], position);
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);
}
for (const TrackArtistLink::pointer& artistLink : track->getArtistLinks())
{
const ArtistId artistId {artistLink->getArtist()->getId()};
Utils::push_back_if_not_present(_artistPositions[artistId], position);
auto itArtists {_artistMatrix.find(artistLink->getType())};
if (itArtists == std::cend(_artistMatrix))
{
[[maybe_unused]] auto [it, inserted] = _artistMatrix.try_emplace(artistLink->getType(), ArtistMatrix {width, height});
assert(inserted);
itArtists = it;
}
Utils::push_back_if_not_present(itArtists->second[position], artistId);
}
}
}
_network = std::make_unique<SOM::Network>(network);
LMS_LOG(RECOMMENDATION, INFO) << "Classifier successfully loaded!";
}
const SOM::Position position{ network.getClosestRefVectorPosition(samples[i]) };
trackPositions[samplesTrackIds[i]].push_back(position);
}
LMS_LOG(RECOMMENDATION, DEBUG, "Classifying tracks DONE");
load(std::move(network), std::move(trackPositions));
}
void FeaturesEngine::loadFromCache(FeaturesEngineCache&& cache)
{
LMS_LOG(RECOMMENDATION, INFO, "Constructing features classifier from cache...");
load(std::move(cache._network), cache._trackPositions);
}
TrackContainer FeaturesEngine::findSimilarTracksFromTrackList(TrackListId trackListId, std::size_t maxCount) const
{
const TrackContainer trackIds{ [&]
{
TrackContainer res;
Session& session {_db.getTLSSession()};
auto transaction {session.createReadTransaction()};
const TrackList::pointer trackList {TrackList::find(session, trackListId)};
if (trackList)
res = trackList->getTrackIds();
return res;
}() };
return findSimilarTracks(trackIds, maxCount);
}
TrackContainer FeaturesEngine::findSimilarTracks(const std::vector<TrackId>& tracksIds, std::size_t maxCount) const
{
auto similarTrackIds{ getSimilarObjects(tracksIds, _trackMatrix, _trackPositions, maxCount) };
Session& session{ _db.getTLSSession() };
{
// Report only existing ids, as tracks may have been removed a long time ago (refreshing the SOM takes some time)
auto transaction{ session.createReadTransaction() };
similarTrackIds.erase(std::remove_if(std::begin(similarTrackIds), std::end(similarTrackIds),
[&](TrackId trackId)
{
return !Track::exists(session, trackId);
}), std::end(similarTrackIds));
}
return similarTrackIds;
}
ReleaseContainer FeaturesEngine::getSimilarReleases(ReleaseId releaseId, std::size_t maxCount) const
{
auto similarReleaseIds{ getSimilarObjects({releaseId}, _releaseMatrix, _releasePositions, maxCount) };
Session& session{ _db.getTLSSession() };
if (!similarReleaseIds.empty())
{
// Report only existing ids
auto transaction{ session.createReadTransaction() };
similarReleaseIds.erase(std::remove_if(std::begin(similarReleaseIds), std::end(similarReleaseIds),
[&](ReleaseId releaseId)
{
return !Release::exists(session, releaseId);
}), std::end(similarReleaseIds));
}
return similarReleaseIds;
}
ArtistContainer FeaturesEngine::getSimilarArtists(ArtistId artistId, EnumSet<TrackArtistLinkType> linkTypes, std::size_t maxCount) const
{
auto getSimilarArtistIdsForLinkType{ [&](TrackArtistLinkType linkType)
{
ArtistContainer similarArtistIds;
const auto itArtists {_artistMatrix.find(linkType)};
if (itArtists == std::cend(_artistMatrix))
{
return similarArtistIds;
}
return getSimilarObjects({artistId}, itArtists->second, _artistPositions, maxCount);
} };
std::unordered_set<ArtistId> similarArtistIds;
for (TrackArtistLinkType linkType : linkTypes)
{
const auto similarArtistIdsForLinkType{ getSimilarArtistIdsForLinkType(linkType) };
similarArtistIds.insert(std::begin(similarArtistIdsForLinkType), std::end(similarArtistIdsForLinkType));
}
ArtistContainer res(std::cbegin(similarArtistIds), std::cend(similarArtistIds));
Session& session{ _db.getTLSSession() };
{
// Report only existing ids
auto transaction{ session.createReadTransaction() };
res.erase(std::remove_if(std::begin(res), std::end(res),
[&](ArtistId artistId)
{
return !Artist::exists(session, artistId);
}), std::end(res));
}
while (res.size() > maxCount)
res.erase(Random::pickRandom(res));
return res;
}
FeaturesEngineCache FeaturesEngine::toCache() const
{
return FeaturesEngineCache{ *_network, _trackPositions };
}
void FeaturesEngine::load(bool forceReload, const ProgressCallback& progressCallback)
{
if (forceReload)
{
FeaturesEngineCache::invalidate();
}
else if (std::optional<FeaturesEngineCache> cache{ FeaturesEngineCache::read() })
{
loadFromCache(std::move(*cache));
return;
}
TrainSettings trainSettings;
trainSettings.featureSettingsMap = getDefaultTrainFeatureSettings();
loadFromTraining(trainSettings, progressCallback);
if (!_loadCancelled && _network)
toCache().write();
}
void FeaturesEngine::requestCancelLoad()
{
LMS_LOG(RECOMMENDATION, DEBUG, "Requesting init cancellation");
_loadCancelled = true;
}
void FeaturesEngine::load(const SOM::Network& network, const TrackPositions& trackPositions)
{
using namespace Database;
_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() };
_releaseMatrix = ReleaseMatrix{ width, height };
_trackMatrix = TrackMatrix{ width, height };
LMS_LOG(RECOMMENDATION, DEBUG, "Constructing maps...");
Session & session{ _db.getTLSSession() };
for (const auto& [trackId, positions] : trackPositions)
{
if (_loadCancelled)
return;
auto transaction{ session.createReadTransaction() };
const Track::pointer track{ Track::find(session, trackId) };
if (!track)
continue;
for (const SOM::Position& position : positions)
{
Utils::push_back_if_not_present(_trackPositions[trackId], position);
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);
}
for (const TrackArtistLink::pointer& artistLink : track->getArtistLinks())
{
const ArtistId artistId{ artistLink->getArtist()->getId() };
Utils::push_back_if_not_present(_artistPositions[artistId], position);
auto itArtists{ _artistMatrix.find(artistLink->getType()) };
if (itArtists == std::cend(_artistMatrix))
{
[[maybe_unused]] auto [it, inserted] = _artistMatrix.try_emplace(artistLink->getType(), ArtistMatrix{ width, height });
assert(inserted);
itArtists = it;
}
Utils::push_back_if_not_present(itArtists->second[position], artistId);
}
}
}
_network = std::make_unique<SOM::Network>(network);
LMS_LOG(RECOMMENDATION, INFO, "Classifier successfully loaded!");
}
} // ns Recommendation
@@ -23,233 +23,226 @@
#include <boost/property_tree/xml_parser.hpp>
#include "utils/IConfig.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Service.hpp"
namespace Recommendation {
static
std::filesystem::path getCacheDirectory()
namespace Recommendation
{
return Service<IConfig>::get()->getPath("working-dir") / "cache" / "features";
}
namespace
{
std::filesystem::path getCacheDirectory()
{
return Service<IConfig>::get()->getPath("working-dir") / "cache" / "features";
}
static std::filesystem::path getCacheNetworkFilePath()
{
return getCacheDirectory() / "network";
}
std::filesystem::path getCacheNetworkFilePath()
{
return getCacheDirectory() / "network";
}
static std::filesystem::path getCacheTrackPositionsFilePath()
{
return getCacheDirectory() / "track_positions";
}
std::filesystem::path getCacheTrackPositionsFilePath()
{
return getCacheDirectory() / "track_positions";
}
static
bool
networkToCacheFile(const SOM::Network& network, std::filesystem::path path)
{
try
{
boost::property_tree::ptree root;
bool networkToCacheFile(const SOM::Network& network, std::filesystem::path path)
{
try
{
boost::property_tree::ptree root;
root.put("width", network.getWidth());
root.put("height", network.getHeight());
root.put("dim_count", network.getInputDimCount());
root.put("width", network.getWidth());
root.put("height", network.getHeight());
root.put("dim_count", network.getInputDimCount());
for (SOM::InputVector::value_type weight : network.getDataWeights())
root.add("weights.weight", weight);
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 y = 0; y < network.getWidth(); ++y)
{
const auto& refVector = network.getRefVector({x, y});
for (SOM::Coordinate x = 0; x < network.getWidth(); ++x)
{
for (SOM::Coordinate y = 0; y < network.getWidth(); ++y)
{
const auto& refVector = network.getRefVector({ x, y });
boost::property_tree::ptree node;
for (auto value : refVector)
node.add("values.value", value);
boost::property_tree::ptree node;
for (const auto& value : refVector)
node.add("values.value", value);
node.put("coord_x", x);
node.put("coord_y", y);
node.put("coord_x", x);
node.put("coord_y", y);
root.add_child("ref_vectors.ref_vector", node);
}
}
root.add_child("ref_vectors.ref_vector", node);
}
}
boost::property_tree::write_xml(path.string(), root);
boost::property_tree::write_xml(path.string(), root);
LMS_LOG(RECOMMENDATION, DEBUG) << "Created network cache";
return true;
}
catch (boost::property_tree::ptree_error& error)
{
LMS_LOG(RECOMMENDATION, ERROR) << "Cannot create network cache: " << error.what();
return false;
}
}
LMS_LOG(RECOMMENDATION, DEBUG, "Created network cache");
return true;
}
catch (boost::property_tree::ptree_error& error)
{
LMS_LOG(RECOMMENDATION, ERROR, "Cannot create network cache: " << error.what());
return false;
}
}
}
std::optional<SOM::Network>
FeaturesEngineCache::createNetworkFromCacheFile(const std::filesystem::path& path)
{
if (!std::filesystem::exists(path))
return std::nullopt;
std::optional<SOM::Network> FeaturesEngineCache::createNetworkFromCacheFile(const std::filesystem::path& path)
{
if (!std::filesystem::exists(path))
return std::nullopt;
try
{
LMS_LOG(RECOMMENDATION, INFO) << "Reading network from cache...";
try
{
LMS_LOG(RECOMMENDATION, INFO, "Reading network from cache...");
boost::property_tree::ptree root;
boost::property_tree::ptree root;
boost::property_tree::read_xml(path.string(), root);
boost::property_tree::read_xml(path.string(), root);
SOM::Coordinate width {root.get<SOM::Coordinate>("width")};
SOM::Coordinate height {root.get<SOM::Coordinate>("height")};
std::size_t dimCount {root.get<std::size_t>("dim_count")};
SOM::Coordinate width{ root.get<SOM::Coordinate>("width") };
SOM::Coordinate height{ root.get<SOM::Coordinate>("height") };
std::size_t dimCount{ root.get<std::size_t>("dim_count") };
SOM::Network res {width, height, dimCount};
SOM::Network res{ width, height, dimCount };
{
SOM::InputVector weights {dimCount};
std::size_t i {};
for (const auto& val : root.get_child("weights"))
weights[i++] = val.second.get_value<double>();
{
SOM::InputVector weights{ dimCount };
std::size_t i{};
for (const auto& val : root.get_child("weights"))
weights[i++] = val.second.get_value<double>();
res.setDataWeights(weights);
}
res.setDataWeights(weights);
}
for (const auto& node : root.get_child("ref_vectors"))
{
SOM::Coordinate x {node.second.get<SOM::Coordinate>("coord_x")};
SOM::Coordinate y {node.second.get<SOM::Coordinate>("coord_y")};
for (const auto& node : root.get_child("ref_vectors"))
{
SOM::Coordinate x{ node.second.get<SOM::Coordinate>("coord_x") };
SOM::Coordinate y{ node.second.get<SOM::Coordinate>("coord_y") };
SOM::InputVector refVector {dimCount};
std::size_t i {};
for (const auto& val : node.second.get_child("values"))
refVector[i++] = val.second.get_value<SOM::InputVector::value_type>();
SOM::InputVector refVector{ dimCount };
std::size_t i{};
for (const auto& val : node.second.get_child("values"))
refVector[i++] = val.second.get_value<SOM::InputVector::value_type>();
res.setRefVector({x, y}, refVector);
}
res.setRefVector({ x, y }, refVector);
}
LMS_LOG(RECOMMENDATION, INFO) << "Successfully read network from cache";
LMS_LOG(RECOMMENDATION, INFO, "Successfully read network from cache");
return res;
}
catch (boost::property_tree::ptree_error& error)
{
LMS_LOG(RECOMMENDATION, ERROR) << "Cannot read network cache: " << error.what();
return std::nullopt;
}
}
return res;
}
catch (boost::property_tree::ptree_error& error)
{
LMS_LOG(RECOMMENDATION, ERROR, "Cannot read network cache: " << error.what());
return std::nullopt;
}
}
bool
FeaturesEngineCache::objectPositionToCacheFile(const TrackPositions& trackPositions, const std::filesystem::path& path)
{
try
{
boost::property_tree::ptree root;
bool FeaturesEngineCache::objectPositionToCacheFile(const TrackPositions& trackPositions, const std::filesystem::path& path)
{
try
{
boost::property_tree::ptree root;
for (const auto& [id, positions] : trackPositions)
{
boost::property_tree::ptree node;
for (const auto& [id, positions] : trackPositions)
{
boost::property_tree::ptree node;
node.put("id", id.getValue());
node.put("id", id.getValue());
for (const SOM::Position& position : positions)
{
boost::property_tree::ptree positionNode;
positionNode.put("x", position.x);
positionNode.put("y", position.y);
for (const SOM::Position& position : positions)
{
boost::property_tree::ptree positionNode;
positionNode.put("x", position.x);
positionNode.put("y", position.y);
node.add_child("position.position", positionNode);
}
node.add_child("position.position", positionNode);
}
root.add_child("objects.object", node);
}
root.add_child("objects.object", node);
}
boost::property_tree::write_xml(path.string(), root);
return true;
}
catch (boost::property_tree::ptree_error& error)
{
LMS_LOG(RECOMMENDATION, ERROR) << "Cannot cache object position: " << error.what();
return false;
}
}
boost::property_tree::write_xml(path.string(), root);
return true;
}
catch (boost::property_tree::ptree_error& error)
{
LMS_LOG(RECOMMENDATION, ERROR, "Cannot cache object position: " << error.what());
return false;
}
}
std::optional<FeaturesEngineCache::TrackPositions>
FeaturesEngineCache::createObjectPositionsFromCacheFile(const std::filesystem::path& path)
{
try
{
LMS_LOG(RECOMMENDATION, INFO) << "Reading object position from cache...";
std::optional<FeaturesEngineCache::TrackPositions> FeaturesEngineCache::createObjectPositionsFromCacheFile(const std::filesystem::path& path)
{
try
{
LMS_LOG(RECOMMENDATION, INFO, "Reading object position from cache...");
boost::property_tree::ptree root;
boost::property_tree::ptree root;
boost::property_tree::read_xml(path.string(), root);
boost::property_tree::read_xml(path.string(), root);
TrackPositions res;
TrackPositions res;
for (const auto& object : root.get_child("objects"))
{
const Database::TrackId id {object.second.get<Database::IdType::ValueType>("id")};
for (const auto& position : object.second.get_child("position"))
{
auto x = position.second.get<SOM::Coordinate>("x");
auto y = position.second.get<SOM::Coordinate>("y");
for (const auto& object : root.get_child("objects"))
{
const Database::TrackId id{ object.second.get<Database::IdType::ValueType>("id") };
for (const auto& position : object.second.get_child("position"))
{
auto x = position.second.get<SOM::Coordinate>("x");
auto y = position.second.get<SOM::Coordinate>("y");
res[id].push_back({x, y});
}
}
res[id].push_back({ x, y });
}
}
LMS_LOG(RECOMMENDATION, INFO) << "Successfully read object position from cache";
LMS_LOG(RECOMMENDATION, INFO, "Successfully read object position from cache");
return res;
}
catch (boost::property_tree::ptree_error& error)
{
LMS_LOG(RECOMMENDATION, ERROR) << "Cannot create object position from cache file: " << error.what();
return std::nullopt;
}
}
return res;
}
catch (boost::property_tree::ptree_error& error)
{
LMS_LOG(RECOMMENDATION, ERROR, "Cannot create object position from cache file: " << error.what());
return std::nullopt;
}
}
void
FeaturesEngineCache::invalidate()
{
std::filesystem::remove(getCacheNetworkFilePath());
std::filesystem::remove(getCacheTrackPositionsFilePath());
}
void FeaturesEngineCache::invalidate()
{
std::filesystem::remove(getCacheNetworkFilePath());
std::filesystem::remove(getCacheTrackPositionsFilePath());
}
std::optional<FeaturesEngineCache>
FeaturesEngineCache::read()
{
auto network{createNetworkFromCacheFile(getCacheNetworkFilePath())};
if (!network)
return std::nullopt;
std::optional<FeaturesEngineCache> FeaturesEngineCache::read()
{
auto network{ createNetworkFromCacheFile(getCacheNetworkFilePath()) };
if (!network)
return std::nullopt;
auto trackPositions{createObjectPositionsFromCacheFile(getCacheTrackPositionsFilePath())};
if (!trackPositions)
return std::nullopt;
auto trackPositions{ createObjectPositionsFromCacheFile(getCacheTrackPositionsFilePath()) };
if (!trackPositions)
return std::nullopt;
return FeaturesEngineCache {std::move(*network), std::move(*trackPositions)};
}
return FeaturesEngineCache{ std::move(*network), std::move(*trackPositions) };
}
void
FeaturesEngineCache::write() const
{
std::filesystem::create_directories(Service<IConfig>::get()->getPath("working-dir") / "cache" / "features");
void FeaturesEngineCache::write() const
{
std::filesystem::create_directories(Service<IConfig>::get()->getPath("working-dir") / "cache" / "features");
if (!networkToCacheFile(_network, getCacheNetworkFilePath())
|| !objectPositionToCacheFile(_trackPositions, getCacheTrackPositionsFilePath()))
{
invalidate();
}
}
if (!networkToCacheFile(_network, getCacheNetworkFilePath())
|| !objectPositionToCacheFile(_trackPositions, getCacheTrackPositionsFilePath()))
{
invalidate();
}
}
FeaturesEngineCache::FeaturesEngineCache(SOM::Network network, TrackPositions trackPositions)
: _network {std::move(network)},
_trackPositions {std::move(trackPositions)}
{
}
FeaturesEngineCache::FeaturesEngineCache(SOM::Network network, TrackPositions trackPositions)
: _network{ std::move(network) },
_trackPositions{ std::move(trackPositions) }
{
}
} // namespace Recommendation
@@ -25,7 +25,7 @@
#include "services/database/Release.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Recommendation::PlaylistGeneratorConstraint
{
@@ -80,7 +80,7 @@ namespace Recommendation::PlaylistGeneratorConstraint
ArtistContainer res;
Session& dbSession {_db.getTLSSession()};
auto transaction {dbSession.createSharedTransaction()};
auto transaction {dbSession.createReadTransaction()};
const Track::pointer track {Track::find(dbSession, trackId)};
if (!track)
@@ -23,7 +23,7 @@
#include "services/database/Release.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Recommendation::PlaylistGeneratorConstraint
{
@@ -61,7 +61,7 @@ namespace Recommendation::PlaylistGeneratorConstraint
using namespace Database;
Session& dbSession {_db.getTLSSession()};
auto transaction {dbSession.createSharedTransaction()};
auto transaction {dbSession.createReadTransaction()};
const Track::pointer track {Track::find(dbSession, trackId)};
if (!track)
@@ -22,34 +22,36 @@
#include "services/database/Db.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
namespace Scanner
{
void
ScanStepCheckDuplicatedDbFiles::process(ScanContext& context)
{
using namespace Database;
void ScanStepCheckDuplicatedDbFiles::process(ScanContext& context)
{
using namespace Database;
if (_abortScan)
return;
if (_abortScan)
return;
Session& session {_db.getTLSSession()};
auto transaction {session.createSharedTransaction()};
auto transaction {session.createReadTransaction()};
const RangeResults<TrackId> tracks = Track::findIdsTrackMBIDDuplicates(session);
for (const TrackId trackId : tracks.results)
{
const Track::pointer track {Track::find(session, trackId)};
if (auto trackMBID {track->getTrackMBID()})
{
LMS_LOG(DBUPDATER, INFO) << "Found duplicated track MBID [" << trackMBID->getAsString() << "], file: " << track->getPath().string() << " - " << track->getName();
context.stats.duplicates.emplace_back(ScanDuplicate {track->getId(), DuplicateReason::SameTrackMBID});
context.currentStepStats.processedElems++;
_progressCallback(context.currentStepStats);
}
}
const RangeResults<TrackId> tracks = Track::findIdsTrackMBIDDuplicates(session);
for (const TrackId trackId : tracks.results)
{
if (_abortScan)
break;
LMS_LOG(DBUPDATER, DEBUG) << "Found " << context.currentStepStats.processedElems << " duplicated audio files";
}
const Track::pointer track{ Track::find(session, trackId) };
if (auto trackMBID{ track->getTrackMBID() })
{
LMS_LOG(DBUPDATER, INFO, "Found duplicated track MBID [" << trackMBID->getAsString() << "], file: " << track->getPath().string() << " - " << track->getName());
context.stats.duplicates.emplace_back(ScanDuplicate{ track->getId(), DuplicateReason::SameTrackMBID });
context.currentStepStats.processedElems++;
_progressCallback(context.currentStepStats);
}
}
LMS_LOG(DBUPDATER, DEBUG, "Found " << context.currentStepStats.processedElems << " duplicated audio files");
}
}
@@ -21,7 +21,7 @@
#include "services/database/Db.hpp"
#include "services/database/Cluster.hpp"
#include "services/database/Session.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Path.hpp"
namespace Scanner
@@ -36,7 +36,7 @@ namespace Scanner
Session& dbSession{ _db.getTLSSession() };
const std::size_t clusterCount{ [&] {
auto transaction{ dbSession.createSharedTransaction() };
auto transaction{ dbSession.createReadTransaction() };
return Cluster::getCount(dbSession);
}() };
@@ -50,25 +50,28 @@ namespace Scanner
params.setRange(range);
{
auto transaction{ dbSession.createSharedTransaction() };
auto transaction{ dbSession.createReadTransaction() };
return std::move(Cluster::findIds(dbSession, params).results);
}
}() };
for (const ClusterId clusterId : clusterIds)
{
if (_abortScan)
break;
std::size_t trackCount;
std::size_t releaseCount;
{
auto transaction{ dbSession.createSharedTransaction() };
auto transaction{ dbSession.createReadTransaction() };
trackCount = Cluster::computeTrackCount(dbSession, clusterId);
releaseCount = Cluster::computeReleaseCount(dbSession, clusterId);
}
{
auto transaction{ dbSession.createUniqueTransaction() };
auto transaction{ dbSession.createWriteTransaction() };
auto cluster{ Cluster::find(dbSession, clusterId) };
cluster.modify()->setTrackCount(trackCount);
@@ -81,6 +84,6 @@ namespace Scanner
return true;
});
LMS_LOG(DBUPDATER, DEBUG) << "Recomputed stats for " << clusterCount << " clusters!";
LMS_LOG(DBUPDATER, DEBUG, "Recomputed stats for " << context.currentStepStats.processedElems << " clusters!");
}
}
@@ -18,31 +18,30 @@
*/
#include "ScanStepDiscoverFiles.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Path.hpp"
namespace Scanner
{
void
ScanStepDiscoverFiles::process(ScanContext& context)
{
context.stats.filesScanned = 0;
PathUtils::exploreFilesRecursive(context.directory, [&](std::error_code ec, const std::filesystem::path& path)
{
if (_abortScan)
return false;
void ScanStepDiscoverFiles::process(ScanContext& context)
{
context.stats.filesScanned = 0;
PathUtils::exploreFilesRecursive(context.directory, [&](std::error_code ec, const std::filesystem::path& path)
{
if (_abortScan)
return false;
if (!ec && PathUtils::hasFileAnyExtension(path, _settings.supportedExtensions))
{
context.currentStepStats.processedElems++;
_progressCallback(context.currentStepStats);
}
if (!ec && PathUtils::hasFileAnyExtension(path, _settings.supportedExtensions))
{
context.currentStepStats.processedElems++;
_progressCallback(context.currentStepStats);
}
return true;
}, &excludeDirFileName);
return true;
}, &excludeDirFileName);
context.stats.filesScanned = context.currentStepStats.processedElems;
context.stats.filesScanned = context.currentStepStats.processedElems;
LMS_LOG(DBUPDATER, DEBUG) << "Discovered " << context.stats.filesScanned << " files in '" << context.directory << "'";
}
LMS_LOG(DBUPDATER, DEBUG, "Discovered " << context.stats.filesScanned << " files in '" << context.directory << "'");
}
}
@@ -25,15 +25,56 @@
#include "services/database/Release.hpp"
#include "services/database/Session.hpp"
#include "services/database/Track.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Path.hpp"
namespace Scanner
{
using namespace Database;
namespace
{
constexpr std::size_t batchSize = 100;
template <typename T>
void removeOrphanEntries(Session& session, bool& abortScan)
{
using IdType = typename T::IdType;
RangeResults<IdType> entries;
while (!abortScan)
{
{
auto transaction{ session.createReadTransaction() };
entries = T::findOrphanIds(session, Range{ 0, batchSize });
};
{
auto transaction{ session.createWriteTransaction() };
for (const IdType objectId : entries.results)
{
if (abortScan)
break;
typename T::pointer entry{ T::find(session, objectId) };
entry.remove();
}
}
if (!entries.moreResults)
break;
}
}
}
void ScanStepRemoveOrphanDbFiles::process(ScanContext& context)
{
removeOrphanTracks(context);
removeOrphanClusters();
removeOrphanClusterTypes();
removeOrphanArtists();
removeOrphanReleases();
}
@@ -45,17 +86,16 @@ namespace Scanner
if (_abortScan)
return;
static constexpr std::size_t batchSize{ 50 };
Session& session{ _db.getTLSSession() };
LMS_LOG(DBUPDATER, DEBUG) << "Checking tracks to be removed...";
LMS_LOG(DBUPDATER, DEBUG, "Checking tracks to be removed...");
std::size_t trackCount{};
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
trackCount = Track::getCount(session);
}
LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks to be checked...";
LMS_LOG(DBUPDATER, DEBUG, trackCount << " tracks to be checked...");
context.currentStepStats.totalElems = trackCount;
@@ -68,7 +108,7 @@ namespace Scanner
tracksToRemove.clear();
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createReadTransaction() };
trackPaths = Track::findPaths(session, Range{ i, batchSize });
}
@@ -85,7 +125,7 @@ namespace Scanner
if (!tracksToRemove.empty())
{
auto transaction{ session.createSharedTransaction() };
auto transaction{ session.createWriteTransaction() };
for (const TrackId trackId : tracksToRemove)
{
@@ -104,62 +144,31 @@ namespace Scanner
break;
}
LMS_LOG(DBUPDATER, DEBUG) << trackCount << " tracks checked!";
LMS_LOG(DBUPDATER, DEBUG, trackCount << " tracks checked!");
}
void ScanStepRemoveOrphanDbFiles::removeOrphanClusters()
{
using namespace Database;
LMS_LOG(DBUPDATER, DEBUG, "Checking orphan clusters...");
removeOrphanEntries<Database::Cluster>(_db.getTLSSession(), _abortScan);
}
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters...";
Session& session{ _db.getTLSSession() };
auto transaction{ session.createUniqueTransaction() };
// Now process orphan Cluster (no track)
auto clusterIds{ Cluster::findOrphans(session) };
for (ClusterId clusterId : clusterIds.results)
{
Cluster::pointer cluster{ Cluster::find(session, clusterId) };
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan cluster '" << cluster->getName() << "'";
cluster.remove();
}
void ScanStepRemoveOrphanDbFiles::removeOrphanClusterTypes()
{
LMS_LOG(DBUPDATER, DEBUG, "Checking orphan cluster types...");
removeOrphanEntries<Database::ClusterType>(_db.getTLSSession(), _abortScan);
}
void ScanStepRemoveOrphanDbFiles::removeOrphanArtists()
{
using namespace Database;
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan artists...";
Session& session{ _db.getTLSSession() };
auto transaction{ session.createUniqueTransaction() };
auto artistIds{ Artist::findOrphanIds(session) };
for (const ArtistId artistId : artistIds.results)
{
Artist::pointer artist{ Artist::find(session, artistId) };
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan artist '" << artist->getName() << "'";
artist.remove();
}
LMS_LOG(DBUPDATER, DEBUG, "Checking orphan artists...");
removeOrphanEntries<Database::Artist>(_db.getTLSSession(), _abortScan);
}
void ScanStepRemoveOrphanDbFiles::removeOrphanReleases()
{
using namespace Database;
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan releases...";
// TODO, by batch
Session& session{ _db.getTLSSession() };
auto transaction{ session.createUniqueTransaction() };
auto releases{ Release::findOrphanIds(session) };
for (const ReleaseId releaseId : releases.results)
{
Release::pointer release{ Release::find(session, releaseId) };
LMS_LOG(DBUPDATER, DEBUG) << "Removing orphan release '" << release->getName() << "'";
release.remove();
}
LMS_LOG(DBUPDATER, DEBUG, "Checking orphan releases...");
removeOrphanEntries<Database::Release>(_db.getTLSSession(), _abortScan);
}
bool ScanStepRemoveOrphanDbFiles::checkFile(const std::filesystem::path& p)
@@ -168,22 +177,21 @@ namespace Scanner
{
// For each track, make sure the the file still exists
// and still belongs to a media directory
if (!std::filesystem::exists(p)
|| !std::filesystem::is_regular_file(p))
if (!std::filesystem::exists(p) || !std::filesystem::is_regular_file(p))
{
LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': missing";
LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': missing");
return false;
}
if (!PathUtils::isPathInRootPath(p, _settings.mediaDirectory, &excludeDirFileName))
{
LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': out of media directory";
LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': out of media directory");
return false;
}
if (!PathUtils::hasFileAnyExtension(p, _settings.supportedExtensions))
{
LMS_LOG(DBUPDATER, INFO) << "Removing '" << p.string() << "': file format no longer handled";
LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': file format no longer handled");
return false;
}
@@ -191,7 +199,7 @@ namespace Scanner
}
catch (std::filesystem::filesystem_error& e)
{
LMS_LOG(DBUPDATER, ERROR) << "Caught exception while checking file '" << p.string() << "': " << e.what();
LMS_LOG(DBUPDATER, ERROR, "Caught exception while checking file '" << p.string() << "': " << e.what());
return false;
}
}
@@ -37,6 +37,7 @@ namespace Scanner
void removeOrphanTracks(ScanContext& context);
void removeOrphanClusters();
void removeOrphanClusterTypes();
void removeOrphanArtists();
void removeOrphanReleases();
bool checkFile(const std::filesystem::path& p);
@@ -30,264 +30,259 @@
#include "services/database/TrackArtistLink.hpp"
#include "utils/Exception.hpp"
#include "utils/IConfig.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Path.hpp"
using namespace Database;
namespace
namespace Scanner
{
Artist::pointer
createArtist(Session& session, const MetaData::Artist& artistInfo)
namespace
{
Artist::pointer artist{ session.create<Artist>(artistInfo.name) };
if (artistInfo.mbid)
artist.modify()->setMBID(*artistInfo.mbid);
if (artistInfo.sortName)
artist.modify()->setSortName(*artistInfo.sortName);
return artist;
}
void
updateArtistIfNeeded(Artist::pointer artist, const MetaData::Artist& artistInfo)
{
// Name may have been updated
if (artist->getName() != artistInfo.name)
Artist::pointer createArtist(Session& session, const MetaData::Artist& artistInfo)
{
artist.modify()->setName(artistInfo.name);
}
Artist::pointer artist{ session.create<Artist>(artistInfo.name) };
// Sortname may have been updated
if (artistInfo.sortName && *artistInfo.sortName != artist->getSortName())
{
artist.modify()->setSortName(*artistInfo.sortName);
}
}
std::vector<Artist::pointer>
getOrCreateArtists(Session& session, const std::vector<MetaData::Artist>& artistsInfo, bool allowFallbackOnMBIDEntries)
{
std::vector<Artist::pointer> artists;
for (const MetaData::Artist& artistInfo : artistsInfo)
{
Artist::pointer artist;
// First try to get by MBID
if (artistInfo.mbid)
{
artist = Artist::find(session, *artistInfo.mbid);
if (!artist)
artist = createArtist(session, artistInfo);
else
updateArtistIfNeeded(artist, artistInfo);
artist.modify()->setMBID(*artistInfo.mbid);
if (artistInfo.sortName)
artist.modify()->setSortName(*artistInfo.sortName);
artists.emplace_back(std::move(artist));
continue;
return artist;
}
void updateArtistIfNeeded(Artist::pointer artist, const MetaData::Artist& artistInfo)
{
// Name may have been updated
if (artist->getName() != artistInfo.name)
{
artist.modify()->setName(artistInfo.name);
}
// Fall back on artist name (collisions may occur)
if (!artistInfo.name.empty())
// Sortname may have been updated
if (artistInfo.sortName && *artistInfo.sortName != artist->getSortName())
{
for (const Artist::pointer& sameNamedArtist : Artist::find(session, artistInfo.name))
{
// Do not fallback on artist that is correctly tagged
if (!allowFallbackOnMBIDEntries && sameNamedArtist->getMBID())
continue;
artist.modify()->setSortName(*artistInfo.sortName);
}
}
artist = sameNamedArtist;
break;
std::vector<Artist::pointer> getOrCreateArtists(Session& session, const std::vector<MetaData::Artist>& artistsInfo, bool allowFallbackOnMBIDEntries)
{
std::vector<Artist::pointer> artists;
for (const MetaData::Artist& artistInfo : artistsInfo)
{
Artist::pointer artist;
// First try to get by MBID
if (artistInfo.mbid)
{
artist = Artist::find(session, *artistInfo.mbid);
if (!artist)
artist = createArtist(session, artistInfo);
else
updateArtistIfNeeded(artist, artistInfo);
artists.emplace_back(std::move(artist));
continue;
}
// No Artist found with the same name and without MBID -> creating
if (!artist)
artist = createArtist(session, artistInfo);
else
updateArtistIfNeeded(artist, artistInfo);
// Fall back on artist name (collisions may occur)
if (!artistInfo.name.empty())
{
for (const Artist::pointer& sameNamedArtist : Artist::find(session, artistInfo.name))
{
// Do not fallback on artist that is correctly tagged
if (!allowFallbackOnMBIDEntries && sameNamedArtist->getMBID())
continue;
artists.emplace_back(std::move(artist));
continue;
artist = sameNamedArtist;
break;
}
// No Artist found with the same name and without MBID -> creating
if (!artist)
artist = createArtist(session, artistInfo);
else
updateArtistIfNeeded(artist, artistInfo);
artists.emplace_back(std::move(artist));
continue;
}
}
return artists;
}
return artists;
}
ReleaseTypePrimary convertReleaseTypePrimary(MetaData::Release::PrimaryType type)
{
switch (type)
{
case MetaData::Release::PrimaryType::Album: return ReleaseTypePrimary::Album;
case MetaData::Release::PrimaryType::Single: return ReleaseTypePrimary::Single;
case MetaData::Release::PrimaryType::EP: return ReleaseTypePrimary::EP;
case MetaData::Release::PrimaryType::Broadcast: return ReleaseTypePrimary::Broadcast;
case MetaData::Release::PrimaryType::Other: return ReleaseTypePrimary::Other;
}
return ReleaseTypePrimary::Other;
}
EnumSet<ReleaseTypeSecondary> convertReleaseTypesSecondary(EnumSet<MetaData::Release::SecondaryType> types)
{
EnumSet<ReleaseTypeSecondary> res;
for (MetaData::Release::SecondaryType type : types)
ReleaseTypePrimary convertReleaseTypePrimary(MetaData::Release::PrimaryType type)
{
switch (type)
{
case MetaData::Release::SecondaryType::Compilation:
res.insert(ReleaseTypeSecondary::Compilation);
break;
case MetaData::Release::SecondaryType::Soundtrack:
res.insert(ReleaseTypeSecondary::Soundtrack);
break;
case MetaData::Release::SecondaryType::Spokenword:
res.insert(ReleaseTypeSecondary::Spokenword);
break;
case MetaData::Release::SecondaryType::Interview:
res.insert(ReleaseTypeSecondary::Interview);
break;
case MetaData::Release::SecondaryType::Audiobook:
res.insert(ReleaseTypeSecondary::Audiobook);
break;
case MetaData::Release::SecondaryType::AudioDrama:
res.insert(ReleaseTypeSecondary::AudioDrama);
break;
case MetaData::Release::SecondaryType::Live:
res.insert(ReleaseTypeSecondary::Live);
break;
case MetaData::Release::SecondaryType::Remix:
res.insert(ReleaseTypeSecondary::Remix);
break;
case MetaData::Release::SecondaryType::DJMix:
res.insert(ReleaseTypeSecondary::DJMix);
break;
case MetaData::Release::SecondaryType::Mixtape_Street:
res.insert(ReleaseTypeSecondary::Mixtape_Street);
break;
case MetaData::Release::SecondaryType::Demo:
res.insert(ReleaseTypeSecondary::Demo);
break;
case MetaData::Release::PrimaryType::Album: return ReleaseTypePrimary::Album;
case MetaData::Release::PrimaryType::Single: return ReleaseTypePrimary::Single;
case MetaData::Release::PrimaryType::EP: return ReleaseTypePrimary::EP;
case MetaData::Release::PrimaryType::Broadcast: return ReleaseTypePrimary::Broadcast;
case MetaData::Release::PrimaryType::Other: return ReleaseTypePrimary::Other;
}
return ReleaseTypePrimary::Other;
}
return res;
}
void
updateReleaseIfNeeded(Release::pointer release, const MetaData::Release& releaseInfo)
{
if (release->getName() != releaseInfo.name)
release.modify()->setName(releaseInfo.name);
if (release->getTotalDisc() != releaseInfo.mediumCount)
release.modify()->setTotalDisc(releaseInfo.mediumCount);
if (releaseInfo.primaryType)
EnumSet<ReleaseTypeSecondary> convertReleaseTypesSecondary(EnumSet<MetaData::Release::SecondaryType> types)
{
const ReleaseTypePrimary primaryType{ convertReleaseTypePrimary(*releaseInfo.primaryType) };
if (release->getPrimaryType() != primaryType)
release.modify()->setPrimaryType(primaryType);
}
const EnumSet<ReleaseTypeSecondary> secondaryTypes{ convertReleaseTypesSecondary(releaseInfo.secondaryTypes) };
if (release->getSecondaryTypes() != secondaryTypes)
release.modify()->setSecondaryTypes(secondaryTypes);
if (release->getArtistDisplayName() != releaseInfo.artistDisplayName)
release.modify()->setArtistDisplayName(releaseInfo.artistDisplayName);
}
EnumSet<ReleaseTypeSecondary> res;
Release::pointer
getOrCreateRelease(Session& session, const MetaData::Release& releaseInfo)
{
Release::pointer release;
// First try to get by MBID
if (releaseInfo.mbid)
{
release = Release::find(session, *releaseInfo.mbid);
if (!release)
release = session.create<Release>(releaseInfo.name, releaseInfo.mbid);
updateReleaseIfNeeded(release, releaseInfo);
return release;
}
// Fall back on release name (collisions may occur)
if (!releaseInfo.name.empty())
{
for (const Release::pointer& sameNamedRelease : Release::find(session, releaseInfo.name))
for (MetaData::Release::SecondaryType type : types)
{
// do not fallback on properly tagged releases
if (sameNamedRelease->getMBID())
continue;
release = sameNamedRelease;
break;
switch (type)
{
case MetaData::Release::SecondaryType::Compilation:
res.insert(ReleaseTypeSecondary::Compilation);
break;
case MetaData::Release::SecondaryType::Soundtrack:
res.insert(ReleaseTypeSecondary::Soundtrack);
break;
case MetaData::Release::SecondaryType::Spokenword:
res.insert(ReleaseTypeSecondary::Spokenword);
break;
case MetaData::Release::SecondaryType::Interview:
res.insert(ReleaseTypeSecondary::Interview);
break;
case MetaData::Release::SecondaryType::Audiobook:
res.insert(ReleaseTypeSecondary::Audiobook);
break;
case MetaData::Release::SecondaryType::AudioDrama:
res.insert(ReleaseTypeSecondary::AudioDrama);
break;
case MetaData::Release::SecondaryType::Live:
res.insert(ReleaseTypeSecondary::Live);
break;
case MetaData::Release::SecondaryType::Remix:
res.insert(ReleaseTypeSecondary::Remix);
break;
case MetaData::Release::SecondaryType::DJMix:
res.insert(ReleaseTypeSecondary::DJMix);
break;
case MetaData::Release::SecondaryType::Mixtape_Street:
res.insert(ReleaseTypeSecondary::Mixtape_Street);
break;
case MetaData::Release::SecondaryType::Demo:
res.insert(ReleaseTypeSecondary::Demo);
break;
}
}
// No release found with the same name and without MBID -> creating
if (!release)
release = session.create<Release>(releaseInfo.name);
updateReleaseIfNeeded(release, releaseInfo);
return release;
return res;
}
return Release::pointer{};
}
std::vector<Cluster::pointer>
getOrCreateClusters(Session& session, const MetaData::Tags& tags)
{
std::vector<Cluster::pointer> clusters;
for (const auto& [tag, values] : tags)
void updateReleaseIfNeeded(Release::pointer release, const MetaData::Release& releaseInfo)
{
auto clusterType = ClusterType::find(session, tag);
if (!clusterType)
continue;
for (auto clusterName : values)
if (release->getName() != releaseInfo.name)
release.modify()->setName(releaseInfo.name);
if (release->getTotalDisc() != releaseInfo.mediumCount)
release.modify()->setTotalDisc(releaseInfo.mediumCount);
if (releaseInfo.primaryType)
{
auto cluster = clusterType->getCluster(clusterName);
if (!cluster)
cluster = session.create<Cluster>(clusterType, clusterName);
clusters.push_back(cluster);
const ReleaseTypePrimary primaryType{ convertReleaseTypePrimary(*releaseInfo.primaryType) };
if (release->getPrimaryType() != primaryType)
release.modify()->setPrimaryType(primaryType);
}
const EnumSet<ReleaseTypeSecondary> secondaryTypes{ convertReleaseTypesSecondary(releaseInfo.secondaryTypes) };
if (release->getSecondaryTypes() != secondaryTypes)
release.modify()->setSecondaryTypes(secondaryTypes);
if (release->getArtistDisplayName() != releaseInfo.artistDisplayName)
release.modify()->setArtistDisplayName(releaseInfo.artistDisplayName);
}
return clusters;
}
Release::pointer getOrCreateRelease(Session& session, const MetaData::Release& releaseInfo, const std::filesystem::path& expectedReleaseDirectory)
{
Release::pointer release;
MetaData::ParserReadStyle
getParserReadStyle()
{
std::string_view readStyle{ Service<IConfig>::get()->getString("scanner-parser-read-style", "accurate") };
// First try to get by MBID
if (releaseInfo.mbid)
{
release = Release::find(session, *releaseInfo.mbid);
if (!release)
release = session.create<Release>(releaseInfo.name, releaseInfo.mbid);
if (readStyle == "fast")
return MetaData::ParserReadStyle::Fast;
else if (readStyle == "average")
return MetaData::ParserReadStyle::Average;
else if (readStyle == "accurate")
return MetaData::ParserReadStyle::Accurate;
updateReleaseIfNeeded(release, releaseInfo);
return release;
}
throw LmsException{ "Invalid value for 'scanner-parser-read-style'" };
}
} // namespace
// Fall back on release name (collisions may occur), if and only if it is in the current directory
if (!releaseInfo.name.empty())
{
for (const Release::pointer& sameNamedRelease : Release::find(session, releaseInfo.name, expectedReleaseDirectory))
{
// do not fallback on properly tagged releases
if (sameNamedRelease->getMBID())
continue;
release = sameNamedRelease;
break;
}
// No release found with the same name and without MBID -> creating
if (!release)
release = session.create<Release>(releaseInfo.name);
updateReleaseIfNeeded(release, releaseInfo);
return release;
}
return Release::pointer{};
}
std::vector<Cluster::pointer> getOrCreateClusters(Session& session, const MetaData::Tags& tags)
{
std::vector<Cluster::pointer> clusters;
for (const auto& [tag, values] : tags)
{
auto clusterType = ClusterType::find(session, tag);
if (!clusterType)
clusterType = session.create<ClusterType>(tag);
for (const auto& clusterName : values)
{
auto cluster = clusterType->getCluster(clusterName);
if (!cluster)
cluster = session.create<Cluster>(clusterType, clusterName);
clusters.push_back(cluster);
}
}
return clusters;
}
MetaData::ParserReadStyle getParserReadStyle()
{
std::string_view readStyle{ Service<IConfig>::get()->getString("scanner-parser-read-style", "average") };
if (readStyle == "fast")
return MetaData::ParserReadStyle::Fast;
else if (readStyle == "average")
return MetaData::ParserReadStyle::Average;
else if (readStyle == "accurate")
return MetaData::ParserReadStyle::Accurate;
throw LmsException{ "Invalid value for 'scanner-parser-read-style'" };
}
} // namespace
namespace Scanner
{
ScanStepScanFiles::ScanStepScanFiles(InitParams& initParams)
: ScanStepBase{ initParams }
, _metadataParser{ MetaData::createParser(MetaData::ParserType::TagLib, getParserReadStyle()) } // For now, always use TagLib
{
}
void
ScanStepScanFiles::process(ScanContext& context)
void ScanStepScanFiles::process(ScanContext& context)
{
_metadataParser->setClusterTypeNames(_settings.clusterTypeNames);
std::vector<std::string> tagsToParse{ _tagsToParse };
tagsToParse.insert(std::end(tagsToParse), std::cbegin(_settings.extraTags), std::cend(_settings.extraTags));
_metadataParser->setExtraTags(tagsToParse);
context.currentStepStats.totalElems = context.stats.filesScanned;
@@ -298,7 +293,7 @@ namespace Scanner
if (ec)
{
LMS_LOG(DBUPDATER, ERROR) << "Cannot process entry '" << path.string() << "': " << ec.message();
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))
@@ -317,8 +312,7 @@ namespace Scanner
}, &excludeDirFileName);
}
void
ScanStepScanFiles::scanAudioFile(const std::filesystem::path& file, ScanContext& context)
void ScanStepScanFiles::scanAudioFile(const std::filesystem::path& file, ScanContext& context)
{
ScanStats& stats{ context.stats };
Wt::WDateTime lastWriteTime;
@@ -328,7 +322,7 @@ namespace Scanner
}
catch (LmsException& e)
{
LMS_LOG(DBUPDATER, ERROR) << e.what();
LMS_LOG(DBUPDATER, ERROR, e.what());
stats.skips++;
return;
}
@@ -337,7 +331,7 @@ namespace Scanner
{
// Skip file if last write is the same
Database::Session& dbSession{ _db.getTLSSession() };
auto transaction{ _db.getTLSSession().createSharedTransaction() };
auto transaction{ _db.getTLSSession().createReadTransaction() };
const Track::pointer track{ Track::findByPath(dbSession, file) };
@@ -359,7 +353,7 @@ namespace Scanner
stats.scans++;
Database::Session& dbSession{ _db.getTLSSession() };
auto uniqueTransaction{ dbSession.createUniqueTransaction() };
auto transaction{ dbSession.createWriteTransaction() };
Track::pointer track{ Track::findByPath(dbSession, file) };
@@ -374,7 +368,7 @@ namespace Scanner
std::error_code ec;
if (!std::filesystem::exists(otherTrack->getPath(), ec))
{
LMS_LOG(DBUPDATER, DEBUG) << "Considering track '" << file.string() << "' moved from '" << otherTrack->getPath() << "'";
LMS_LOG(DBUPDATER, DEBUG, "Considering track '" << file.string() << "' moved from '" << otherTrack->getPath() << "'");
track = otherTrack;
track.modify()->setPath(file);
}
@@ -393,7 +387,7 @@ namespace Scanner
if (!PathUtils::isPathInRootPath(file, _settings.mediaDirectory, &excludeDirFileName))
continue;
LMS_LOG(DBUPDATER, DEBUG) << "Skipped '" << file.string() << "' (similar MBID in '" << otherTrack->getPath().string() << "')";
LMS_LOG(DBUPDATER, DEBUG, "Skipped '" << file.string() << "' (similar MBID in '" << otherTrack->getPath().string() << "')");
// As this MBID already exists, just remove what we just scanned
if (track)
{
@@ -408,7 +402,7 @@ namespace Scanner
// We estimate this is an audio file if the duration is not null
if (trackInfo->duration == std::chrono::milliseconds::zero())
{
LMS_LOG(DBUPDATER, DEBUG) << "Skipped '" << file.string() << "' (duration is 0)";
LMS_LOG(DBUPDATER, DEBUG, "Skipped '" << file.string() << "' (duration is 0)");
// If Track exists here, delete it!
if (track)
@@ -436,12 +430,12 @@ namespace Scanner
if (!track)
{
track = dbSession.create<Track>(file);
LMS_LOG(DBUPDATER, DEBUG) << "Adding '" << file.string() << "'";
LMS_LOG(DBUPDATER, DEBUG, "Adding '" << file.string() << "'");
stats.additions++;
}
else
{
LMS_LOG(DBUPDATER, DEBUG) << "Updating '" << file.string() << "'";
LMS_LOG(DBUPDATER, DEBUG, "Updating '" << file.string() << "'");
stats.updates++;
}
@@ -488,7 +482,7 @@ namespace Scanner
track.modify()->setScanVersion(_settings.scanVersion);
if (trackInfo->medium && trackInfo->medium->release)
track.modify()->setRelease(getOrCreateRelease(dbSession, *trackInfo->medium->release));
track.modify()->setRelease(getOrCreateRelease(dbSession, *trackInfo->medium->release, file.parent_path()));
else
track.modify()->setRelease({});
track.modify()->setTotalTrack(trackInfo->medium ? trackInfo->medium->trackCount : std::nullopt);
@@ -20,24 +20,27 @@
#pragma once
#include <filesystem>
#include <string>
#include <vector>
#include "metadata/IParser.hpp"
#include "ScanStepBase.hpp"
namespace Scanner
{
class ScanStepScanFiles : public ScanStepBase
{
public:
ScanStepScanFiles(InitParams& initParams);
class ScanStepScanFiles : public ScanStepBase
{
public:
ScanStepScanFiles(InitParams& initParams);
private:
ScanStep getStep() const override { return ScanStep::ScanningFiles; }
std::string_view getStepName() const override { return "Scanning files"; }
void process(ScanContext& context) override;
private:
ScanStep getStep() const override { return ScanStep::ScanningFiles; }
std::string_view getStepName() const override { return "Scanning files"; }
void process(ScanContext& context) override;
void scanAudioFile(const std::filesystem::path& file, ScanContext& context);
void scanAudioFile(const std::filesystem::path& file, ScanContext& context);
std::unique_ptr<MetaData::IParser> _metadataParser;
};
std::unique_ptr<MetaData::IParser> _metadataParser;
const std::vector<std::string> _tagsToParse{ "GENRE", "MOOD", "LANGUAGE", "ALBUMGROUPING" };
};
}
@@ -27,7 +27,7 @@
#include "services/database/ScanSettings.hpp"
#include "utils/Exception.hpp"
#include "utils/IConfig.hpp"
#include "utils/Logger.hpp"
#include "utils/ILogger.hpp"
#include "utils/Path.hpp"
#include "utils/Tuple.hpp"
@@ -82,9 +82,9 @@ namespace Scanner
ScannerService::~ScannerService()
{
LMS_LOG(DBUPDATER, INFO) << "Stopping service...";
LMS_LOG(DBUPDATER, INFO, "Stopping service...");
stop();
LMS_LOG(DBUPDATER, INFO) << "Service stopped!";
LMS_LOG(DBUPDATER, INFO, "Service stopped!");
}
void ScannerService::start()
@@ -113,15 +113,15 @@ namespace Scanner
void ScannerService::abortScan()
{
LMS_LOG(DBUPDATER, DEBUG) << "Aborting scan...";
LMS_LOG(DBUPDATER, DEBUG, "Aborting scan...");
std::scoped_lock lock{ _controlMutex };
LMS_LOG(DBUPDATER, DEBUG) << "Waiting for the scan to abort...";
LMS_LOG(DBUPDATER, DEBUG, "Waiting for the scan to abort...");
_abortScan = true;
_scheduleTimer.cancel();
_ioService.stop();
LMS_LOG(DBUPDATER, DEBUG) << "Scan abort done!";
LMS_LOG(DBUPDATER, DEBUG, "Scan abort done!");
_abortScan = false;
_ioService.start();
@@ -167,7 +167,7 @@ namespace Scanner
void ScannerService::scheduleNextScan()
{
LMS_LOG(DBUPDATER, DEBUG) << "Scheduling next scan";
LMS_LOG(DBUPDATER, DEBUG, "Scheduling next scan");
refreshScanSettings();
@@ -202,7 +202,7 @@ namespace Scanner
break;
case ScanSettings::UpdatePeriod::Never:
LMS_LOG(DBUPDATER, INFO) << "Auto scan disabled!";
LMS_LOG(DBUPDATER, INFO, "Auto scan disabled!");
break;
}
@@ -230,7 +230,7 @@ namespace Scanner
if (dateTime.isNull())
{
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan right now";
LMS_LOG(DBUPDATER, INFO, "Scheduling next scan right now");
_scheduleTimer.expires_from_now(std::chrono::seconds{ 0 });
_scheduleTimer.async_wait(cb);
}
@@ -240,7 +240,7 @@ namespace Scanner
std::time_t t{ std::chrono::system_clock::to_time_t(timePoint) };
char ctimeStr[26];
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan at " << std::string(::ctime_r(&t, ctimeStr));
LMS_LOG(DBUPDATER, INFO, "Scheduling next scan at " << std::string(::ctime_r(&t, ctimeStr)));
_scheduleTimer.expires_at(timePoint);
_scheduleTimer.async_wait(cb);
}
@@ -257,7 +257,7 @@ namespace Scanner
}
LMS_LOG(UI, INFO) << "New scan started!";
LMS_LOG(UI, INFO, "New scan started!");
refreshScanSettings();
@@ -267,16 +267,16 @@ namespace Scanner
for (auto& scanStep : _scanSteps)
{
LMS_LOG(DBUPDATER, DEBUG) << "Starting scan step '" << scanStep->getStepName() << "'";
LMS_LOG(DBUPDATER, DEBUG, "Starting scan step '" << scanStep->getStepName() << "'");
scanContext.currentStepStats = ScanStepStats{ Wt::WDateTime::currentDateTime(), scanStep->getStep() };
notifyInProgress(scanContext.currentStepStats);
scanStep->process(scanContext);
notifyInProgress(scanContext.currentStepStats);
LMS_LOG(DBUPDATER, DEBUG) << "Completed scan step '" << scanStep->getStepName() << "'";
LMS_LOG(DBUPDATER, DEBUG, "Completed scan step '" << scanStep->getStepName() << "'");
}
LMS_LOG(DBUPDATER, INFO) << "Scan " << (_abortScan ? "aborted" : "complete") << ". Changes = " << stats.nbChanges() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.errors.size() << "), features fetched = " << stats.featuresFetched << ", duplicates = " << stats.duplicates.size();
LMS_LOG(DBUPDATER, INFO, "Scan " << (_abortScan ? "aborted" : "complete") << ". Changes = " << stats.nbChanges() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.errors.size() << "), features fetched = " << stats.featuresFetched << ", duplicates = " << stats.duplicates.size());
_dbSession.analyze();
@@ -290,14 +290,14 @@ namespace Scanner
_currentScanStepStats.reset();
}
LMS_LOG(DBUPDATER, DEBUG) << "Scan not aborted, scheduling next scan!";
LMS_LOG(DBUPDATER, DEBUG, "Scan not aborted, scheduling next scan!");
scheduleNextScan();
_events.scanComplete.emit(stats);
}
else
{
LMS_LOG(DBUPDATER, DEBUG) << "Scan aborted, not scheduling next scan!";
LMS_LOG(DBUPDATER, DEBUG, "Scan aborted, not scheduling next scan!");
std::unique_lock lock{ _statusMutex };
@@ -312,9 +312,9 @@ namespace Scanner
if (_settings == newSettings)
return;
LMS_LOG(DBUPDATER, DEBUG) << "Scanner settings updated";
LMS_LOG(DBUPDATER, DEBUG) << "skipDuplicateMBID = " << newSettings.skipDuplicateMBID;
LMS_LOG(DBUPDATER, DEBUG) << "Using scan settings version " << newSettings.scanVersion;
LMS_LOG(DBUPDATER, DEBUG, "Scanner settings updated");
LMS_LOG(DBUPDATER, DEBUG, "skipDuplicateMBID = " << newSettings.skipDuplicateMBID);
LMS_LOG(DBUPDATER, DEBUG, "Using scan settings version " << newSettings.scanVersion);
_settings = std::move(newSettings);
@@ -345,7 +345,7 @@ namespace Scanner
newSettings.skipDuplicateMBID = Service<IConfig>::get()->getBool("scanner-skip-duplicate-mbid", false);
{
auto transaction{ _dbSession.createSharedTransaction() };
auto transaction{ _dbSession.createReadTransaction() };
const ScanSettings::pointer scanSettings{ ScanSettings::get(_dbSession) };
@@ -361,14 +361,10 @@ namespace Scanner
}
newSettings.mediaDirectory = scanSettings->getMediaDirectory();
const auto clusterTypes = scanSettings->getClusterTypes();
std::set<std::string> clusterTypeNames;
std::transform(std::cbegin(clusterTypes), std::cend(clusterTypes),
std::inserter(clusterTypeNames, clusterTypeNames.begin()),
[](ClusterType::pointer clusterType) { return clusterType->getName(); });
newSettings.clusterTypeNames = std::move(clusterTypeNames);
{
const auto& tags{ scanSettings->getExtraTagsToScan() };
std::transform(std::cbegin(tags), std::cend(tags), std::back_inserter(newSettings.extraTags), [](std::string_view tag) { return std::string{ tag };});
}
}
return newSettings;
@@ -20,7 +20,6 @@
#pragma once
#include <filesystem>
#include <set>
#include <string>
#include <vector>
#include <Wt/WDateTime.h>
@@ -36,7 +35,7 @@ namespace Scanner
std::vector<std::filesystem::path> supportedExtensions;
std::filesystem::path mediaDirectory;
bool skipDuplicateMBID {};
std::set<std::string> clusterTypeNames;
std::vector<std::string> extraTags;
bool operator==(const ScannerSettings& rhs) const
{
@@ -46,7 +45,7 @@ namespace Scanner
&& supportedExtensions == rhs.supportedExtensions
&& mediaDirectory == rhs.mediaDirectory
&& skipDuplicateMBID == rhs.skipDuplicateMBID
&& clusterTypeNames == rhs.clusterTypeNames;
&& extraTags == rhs.extraTags;
}
};
}
@@ -67,7 +67,7 @@ namespace Scanner
ReloadingSimilarityEngine,
ComputeClusterStats,
};
static inline constexpr unsigned ScanProgressStepCount{ 6 };
static inline constexpr unsigned ScanProgressStepCount{ 7 };
// reduced scan stats
struct ScanStepStats

Some files were not shown because too many files have changed in this diff Show More