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

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