SubsonicAPI: optimized search for tracks
This commit is contained in:
@@ -34,33 +34,35 @@
|
|||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
#include "utils/Utils.hpp"
|
#include "utils/Utils.hpp"
|
||||||
|
|
||||||
namespace
|
namespace Cover
|
||||||
{
|
{
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
struct TrackInfo
|
struct TrackInfo
|
||||||
{
|
{
|
||||||
bool hasCover {};
|
bool hasCover{};
|
||||||
bool isMultiDisc {};
|
bool isMultiDisc{};
|
||||||
std::filesystem::path trackPath;
|
std::filesystem::path trackPath;
|
||||||
std::optional<Database::ReleaseId> releaseId;
|
std::optional<Database::ReleaseId> releaseId;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::optional<TrackInfo>
|
std::optional<TrackInfo> getTrackInfo(Database::Session& dbSession, Database::TrackId trackId)
|
||||||
getTrackInfo(Database::Session& dbSession, Database::TrackId trackId)
|
|
||||||
{
|
{
|
||||||
std::optional<TrackInfo> res;
|
std::optional<TrackInfo> res;
|
||||||
|
|
||||||
auto transaction {dbSession.createSharedTransaction()};
|
auto transaction{ dbSession.createSharedTransaction() };
|
||||||
|
|
||||||
const Database::Track::pointer track {Database::Track::find(dbSession, trackId)};
|
const Database::Track::pointer track{ Database::Track::find(dbSession, trackId) };
|
||||||
if (!track)
|
if (!track)
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
res = TrackInfo {};
|
res = TrackInfo{};
|
||||||
|
|
||||||
res->hasCover = track->hasCover();
|
res->hasCover = track->hasCover();
|
||||||
res->trackPath = track->getPath();
|
res->trackPath = track->getPath();
|
||||||
|
|
||||||
if (const Database::Release::pointer& release {track->getRelease()})
|
if (const Database::Release::pointer & release{ track->getRelease() })
|
||||||
{
|
{
|
||||||
res->releaseId = release->getId();
|
res->releaseId = release->getId();
|
||||||
if (release->getTotalDisc() > 1)
|
if (release->getTotalDisc() > 1)
|
||||||
@@ -78,39 +80,34 @@ namespace
|
|||||||
[&res](std::string_view fileName)
|
[&res](std::string_view fileName)
|
||||||
{
|
{
|
||||||
res.emplace_back(fileName);
|
res.emplace_back(fileName);
|
||||||
}, {"cover", "front"});
|
}, { "cover", "front" });
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
namespace Cover {
|
bool isFileSupported(const std::filesystem::path& file, const std::vector<std::filesystem::path>& extensions)
|
||||||
|
{
|
||||||
using namespace Image;
|
|
||||||
|
|
||||||
static
|
|
||||||
bool
|
|
||||||
isFileSupported(const std::filesystem::path& file, const std::vector<std::filesystem::path>& extensions)
|
|
||||||
{
|
|
||||||
return (std::find(std::cbegin(extensions), std::cend(extensions), file.extension()) != std::cend(extensions));
|
return (std::find(std::cbegin(extensions), std::cend(extensions), file.extension()) != std::cend(extensions));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<ICoverService>
|
std::unique_ptr<ICoverService> createCoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath)
|
||||||
createCoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath)
|
{
|
||||||
{
|
|
||||||
return std::make_unique<CoverService>(db, execPath, defaultCoverPath);
|
return std::make_unique<CoverService>(db, execPath, defaultCoverPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
CoverService::CoverService(Database::Db& db,
|
using namespace Image;
|
||||||
|
|
||||||
|
CoverService::CoverService(Database::Db& db,
|
||||||
const std::filesystem::path& execPath,
|
const std::filesystem::path& execPath,
|
||||||
const std::filesystem::path& defaultCoverPath)
|
const std::filesystem::path& defaultCoverPath)
|
||||||
: _db {db}
|
: _db{ db }
|
||||||
, _defaultCoverPath {defaultCoverPath}
|
, _defaultCoverPath{ defaultCoverPath }
|
||||||
, _maxCacheSize {Service<IConfig>::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000}
|
, _maxCacheSize{ Service<IConfig>::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 }
|
||||||
, _maxFileSize {Service<IConfig>::get()->getULong("cover-max-file-size", 10) * 1000 * 1000}
|
, _maxFileSize{ Service<IConfig>::get()->getULong("cover-max-file-size", 10) * 1000 * 1000 }
|
||||||
, _preferredFileNames {constructPreferredFileNames()}
|
, _preferredFileNames{ constructPreferredFileNames() }
|
||||||
|
|
||||||
{
|
{
|
||||||
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() << "'";
|
||||||
@@ -132,11 +129,10 @@ CoverService::CoverService(Database::Db& db,
|
|||||||
{
|
{
|
||||||
throw LmsException("Cannot read default cover file '" + _defaultCoverPath.string() + "': " + e.what());
|
throw LmsException("Cannot read default cover file '" + _defaultCoverPath.string() + "': " + e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IEncodedImage>
|
std::unique_ptr<IEncodedImage> CoverService::getFromAvMediaFile(const Av::IAudioFile& input, ImageSize width) const
|
||||||
CoverService::getFromAvMediaFile(const Av::IAudioFile& input, ImageSize width) const
|
{
|
||||||
{
|
|
||||||
std::unique_ptr<IEncodedImage> image;
|
std::unique_ptr<IEncodedImage> image;
|
||||||
|
|
||||||
input.visitAttachedPictures([&](const Av::Picture& picture)
|
input.visitAttachedPictures([&](const Av::Picture& picture)
|
||||||
@@ -146,7 +142,7 @@ CoverService::getFromAvMediaFile(const Av::IAudioFile& input, ImageSize width) c
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::unique_ptr<IRawImage> rawImage {decodeImage(picture.data, picture.dataSize)};
|
std::unique_ptr<IRawImage> rawImage{ decodeImage(picture.data, picture.dataSize) };
|
||||||
rawImage->resize(width);
|
rawImage->resize(width);
|
||||||
image = rawImage->encodeToJPEG(_jpegQuality);
|
image = rawImage->encodeToJPEG(_jpegQuality);
|
||||||
}
|
}
|
||||||
@@ -157,16 +153,15 @@ CoverService::getFromAvMediaFile(const Av::IAudioFile& input, ImageSize width) c
|
|||||||
});
|
});
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IEncodedImage>
|
std::unique_ptr<IEncodedImage> CoverService::getFromCoverFile(const std::filesystem::path& p, ImageSize width) const
|
||||||
CoverService::getFromCoverFile(const std::filesystem::path& p, ImageSize width) const
|
{
|
||||||
{
|
|
||||||
std::unique_ptr<IEncodedImage> image;
|
std::unique_ptr<IEncodedImage> image;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::unique_ptr<IRawImage> rawImage {decodeImage(p)};
|
std::unique_ptr<IRawImage> rawImage{ decodeImage(p) };
|
||||||
rawImage->resize(width);
|
rawImage->resize(width);
|
||||||
image = rawImage->encodeToJPEG(_jpegQuality);
|
image = rawImage->encodeToJPEG(_jpegQuality);
|
||||||
}
|
}
|
||||||
@@ -176,43 +171,41 @@ CoverService::getFromCoverFile(const std::filesystem::path& p, ImageSize width)
|
|||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<IEncodedImage>
|
std::shared_ptr<IEncodedImage> CoverService::getDefault(ImageSize width)
|
||||||
CoverService::getDefault(ImageSize width)
|
|
||||||
{
|
|
||||||
{
|
{
|
||||||
std::shared_lock lock {_cacheMutex};
|
{
|
||||||
|
std::shared_lock lock{ _cacheMutex };
|
||||||
|
|
||||||
if (auto it {_defaultCoverCache.find(width)}; it != std::cend(_defaultCoverCache))
|
if (auto it{ _defaultCoverCache.find(width) }; it != std::cend(_defaultCoverCache))
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unique_lock lock {_cacheMutex};
|
std::unique_lock lock{ _cacheMutex };
|
||||||
|
|
||||||
if (auto it {_defaultCoverCache.find(width)}; it != std::cend(_defaultCoverCache))
|
if (auto it{ _defaultCoverCache.find(width) }; it != std::cend(_defaultCoverCache))
|
||||||
return it->second;
|
return it->second;
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IEncodedImage>
|
std::unique_ptr<IEncodedImage> CoverService::getFromDirectory(const std::filesystem::path& directory, ImageSize width) const
|
||||||
CoverService::getFromDirectory(const std::filesystem::path& directory, ImageSize width) const
|
{
|
||||||
{
|
const std::multimap<std::string, std::filesystem::path> coverPaths{ getCoverPaths(directory) };
|
||||||
const std::multimap<std::string, std::filesystem::path> coverPaths {getCoverPaths(directory)};
|
|
||||||
|
|
||||||
auto tryLoadImageFromFilename = [&](std::string_view fileName)
|
auto tryLoadImageFromFilename = [&](std::string_view fileName)
|
||||||
{
|
{
|
||||||
std::unique_ptr<IEncodedImage> image;
|
std::unique_ptr<IEncodedImage> image;
|
||||||
|
|
||||||
auto range {coverPaths.equal_range(std::string {fileName})};
|
auto range{ coverPaths.equal_range(std::string {fileName}) };
|
||||||
for (auto it {range.first}; it != range.second; ++it)
|
for (auto it{ range.first }; it != range.second; ++it)
|
||||||
{
|
{
|
||||||
image = getFromCoverFile(it->second, width);
|
image = getFromCoverFile(it->second, width);
|
||||||
if (image)
|
if (image)
|
||||||
@@ -239,14 +232,13 @@ CoverService::getFromDirectory(const std::filesystem::path& directory, ImageSize
|
|||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IEncodedImage>
|
std::unique_ptr<IEncodedImage> CoverService::getFromSameNamedFile(const std::filesystem::path& filePath, ImageSize width) const
|
||||||
CoverService::getFromSameNamedFile(const std::filesystem::path& filePath, ImageSize width) const
|
{
|
||||||
{
|
|
||||||
std::unique_ptr<IEncodedImage> res;
|
std::unique_ptr<IEncodedImage> res;
|
||||||
|
|
||||||
std::filesystem::path coverPath {filePath};
|
std::filesystem::path coverPath{ filePath };
|
||||||
for (const std::filesystem::path& extension : _fileExtensions)
|
for (const std::filesystem::path& extension : _fileExtensions)
|
||||||
{
|
{
|
||||||
coverPath.replace_extension(extension);
|
coverPath.replace_extension(extension);
|
||||||
@@ -260,11 +252,10 @@ CoverService::getFromSameNamedFile(const std::filesystem::path& filePath, ImageS
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool CoverService::checkCoverFile(const std::filesystem::path& filePath) const
|
||||||
CoverService::checkCoverFile(const std::filesystem::path& filePath) const
|
{
|
||||||
{
|
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
|
|
||||||
if (!isFileSupported(filePath, _fileExtensions))
|
if (!isFileSupported(filePath, _fileExtensions))
|
||||||
@@ -283,11 +274,10 @@ CoverService::checkCoverFile(const std::filesystem::path& filePath) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::multimap<std::string, std::filesystem::path>
|
std::multimap<std::string, std::filesystem::path> CoverService::getCoverPaths(const std::filesystem::path& directoryPath) const
|
||||||
CoverService::getCoverPaths(const std::filesystem::path& directoryPath) const
|
{
|
||||||
{
|
|
||||||
std::multimap<std::string, std::filesystem::path> res;
|
std::multimap<std::string, std::filesystem::path> res;
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
|
|
||||||
@@ -295,7 +285,7 @@ CoverService::getCoverPaths(const std::filesystem::path& directoryPath) const
|
|||||||
std::filesystem::directory_iterator itEnd;
|
std::filesystem::directory_iterator itEnd;
|
||||||
while (!ec && itPath != itEnd)
|
while (!ec && itPath != itEnd)
|
||||||
{
|
{
|
||||||
const std::filesystem::path& path {*itPath};
|
const std::filesystem::path& path{ *itPath };
|
||||||
|
|
||||||
if (checkCoverFile(path))
|
if (checkCoverFile(path))
|
||||||
res.emplace(std::filesystem::path{ path }.filename().replace_extension("").string(), path);
|
res.emplace(std::filesystem::path{ path }.filename().replace_extension("").string(), path);
|
||||||
@@ -304,11 +294,10 @@ CoverService::getCoverPaths(const std::filesystem::path& directoryPath) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<IEncodedImage>
|
std::unique_ptr<IEncodedImage> CoverService::getFromTrack(const std::filesystem::path& p, ImageSize width) const
|
||||||
CoverService::getFromTrack(const std::filesystem::path& p, ImageSize width) const
|
{
|
||||||
{
|
|
||||||
std::unique_ptr<IEncodedImage> image;
|
std::unique_ptr<IEncodedImage> image;
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -321,26 +310,24 @@ CoverService::getFromTrack(const std::filesystem::path& p, ImageSize width) cons
|
|||||||
}
|
}
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<IEncodedImage>
|
std::shared_ptr<IEncodedImage> CoverService::getFromTrack(Database::TrackId trackId, ImageSize width)
|
||||||
CoverService::getFromTrack(Database::TrackId trackId, ImageSize width)
|
{
|
||||||
{
|
|
||||||
return getFromTrack(_db.getTLSSession(), trackId, width, true /* allow release fallback*/);
|
return getFromTrack(_db.getTLSSession(), trackId, width, true /* allow release fallback*/);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<IEncodedImage>
|
std::shared_ptr<IEncodedImage> CoverService::getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width, bool allowReleaseFallback)
|
||||||
CoverService::getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width, bool allowReleaseFallback)
|
{
|
||||||
{
|
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
const CacheEntryDesc cacheEntryDesc {trackId, width};
|
const CacheEntryDesc cacheEntryDesc{ trackId, width };
|
||||||
|
|
||||||
std::shared_ptr<IEncodedImage> cover {loadFromCache(cacheEntryDesc)};
|
std::shared_ptr<IEncodedImage> cover{ loadFromCache(cacheEntryDesc) };
|
||||||
if (cover)
|
if (cover)
|
||||||
return cover;
|
return cover;
|
||||||
|
|
||||||
if (const std::optional<TrackInfo> trackInfo {getTrackInfo(dbSession, trackId)})
|
if (const std::optional<TrackInfo> trackInfo{ getTrackInfo(dbSession, trackId) })
|
||||||
{
|
{
|
||||||
if (trackInfo->hasCover)
|
if (trackInfo->hasCover)
|
||||||
cover = getFromTrack(trackInfo->trackPath, width);
|
cover = getFromTrack(trackInfo->trackPath, width);
|
||||||
@@ -365,15 +352,14 @@ CoverService::getFromTrack(Database::Session& dbSession, Database::TrackId track
|
|||||||
saveToCache(cacheEntryDesc, cover);
|
saveToCache(cacheEntryDesc, cover);
|
||||||
|
|
||||||
return cover;
|
return cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<IEncodedImage>
|
std::shared_ptr<IEncodedImage> CoverService::getFromRelease(Database::ReleaseId releaseId, ImageSize width)
|
||||||
CoverService::getFromRelease(Database::ReleaseId releaseId, ImageSize width)
|
{
|
||||||
{
|
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
const CacheEntryDesc cacheEntryDesc {releaseId, width};
|
const CacheEntryDesc cacheEntryDesc{ releaseId, width };
|
||||||
|
|
||||||
std::shared_ptr<IEncodedImage> cover {loadFromCache(cacheEntryDesc)};
|
std::shared_ptr<IEncodedImage> cover{ loadFromCache(cacheEntryDesc) };
|
||||||
if (cover)
|
if (cover)
|
||||||
return cover;
|
return cover;
|
||||||
|
|
||||||
@@ -383,30 +369,28 @@ CoverService::getFromRelease(Database::ReleaseId releaseId, ImageSize width)
|
|||||||
std::filesystem::path releaseDirectory;
|
std::filesystem::path releaseDirectory;
|
||||||
};
|
};
|
||||||
|
|
||||||
Session& session {_db.getTLSSession()};
|
Session& session{ _db.getTLSSession() };
|
||||||
|
|
||||||
auto getReleaseInfo {[&]
|
auto getReleaseInfo{ [&]
|
||||||
{
|
{
|
||||||
std::optional<ReleaseInfo> res;
|
std::optional<ReleaseInfo> res;
|
||||||
|
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
const auto tracks {Track::find(session, Track::FindParameters {}.setRelease(releaseId).setRange({0, 1}).setSortMethod(TrackSortMethod::Release))};
|
const auto tracks{ Track::find(session, Track::FindParameters {}.setRelease(releaseId).setRange({0, 1}).setSortMethod(TrackSortMethod::Release)) };
|
||||||
|
|
||||||
if (!tracks.results.empty())
|
if (!tracks.results.empty())
|
||||||
{
|
{
|
||||||
if (const Track::pointer track {Track::find(session, tracks.results.front())})
|
const Track::pointer& track{ tracks.results.front() };
|
||||||
{
|
res = ReleaseInfo{};
|
||||||
res = ReleaseInfo {};
|
|
||||||
res->firstTrackId = track->getId();
|
res->firstTrackId = track->getId();
|
||||||
res->releaseDirectory = track->getPath().parent_path();
|
res->releaseDirectory = track->getPath().parent_path();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}};
|
} };
|
||||||
|
|
||||||
if (const std::optional<ReleaseInfo> releaseInfo {getReleaseInfo()})
|
if (const std::optional<ReleaseInfo> releaseInfo{ getReleaseInfo() })
|
||||||
{
|
{
|
||||||
cover = getFromDirectory(releaseInfo->releaseDirectory, width);
|
cover = getFromDirectory(releaseInfo->releaseDirectory, width);
|
||||||
if (!cover)
|
if (!cover)
|
||||||
@@ -420,50 +404,46 @@ CoverService::getFromRelease(Database::ReleaseId releaseId, ImageSize width)
|
|||||||
saveToCache(cacheEntryDesc, cover);
|
saveToCache(cacheEntryDesc, cover);
|
||||||
|
|
||||||
return cover;
|
return cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void CoverService::flushCache()
|
||||||
CoverService::flushCache()
|
{
|
||||||
{
|
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;
|
||||||
_cache.clear();
|
_cache.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void CoverService::setJpegQuality(unsigned quality)
|
||||||
CoverService::setJpegQuality(unsigned quality)
|
{
|
||||||
{
|
|
||||||
_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
|
void CoverService::saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr<IEncodedImage> image)
|
||||||
CoverService::saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr<IEncodedImage> image)
|
{
|
||||||
{
|
std::unique_lock lock{ _cacheMutex };
|
||||||
std::unique_lock lock {_cacheMutex};
|
|
||||||
|
|
||||||
while (_cacheSize + image->getDataSize() > _maxCacheSize && !_cache.empty())
|
while (_cacheSize + image->getDataSize() > _maxCacheSize && !_cache.empty())
|
||||||
{
|
{
|
||||||
auto itRandom {Random::pickRandom(_cache)};
|
auto itRandom{ Random::pickRandom(_cache) };
|
||||||
_cacheSize -= itRandom->second->getDataSize();
|
_cacheSize -= itRandom->second->getDataSize();
|
||||||
_cache.erase(itRandom);
|
_cache.erase(itRandom);
|
||||||
}
|
}
|
||||||
|
|
||||||
_cacheSize += image->getDataSize();
|
_cacheSize += image->getDataSize();
|
||||||
_cache[entryDesc] = image;
|
_cache[entryDesc] = image;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<IEncodedImage>
|
std::shared_ptr<IEncodedImage> CoverService::loadFromCache(const CacheEntryDesc& entryDesc)
|
||||||
CoverService::loadFromCache(const CacheEntryDesc& entryDesc)
|
{
|
||||||
{
|
std::shared_lock lock{ _cacheMutex };
|
||||||
std::shared_lock lock {_cacheMutex};
|
|
||||||
|
|
||||||
auto it {_cache.find(entryDesc)};
|
auto it{ _cache.find(entryDesc) };
|
||||||
if (it == std::cend(_cache))
|
if (it == std::cend(_cache))
|
||||||
{
|
{
|
||||||
++_cacheMisses;
|
++_cacheMisses;
|
||||||
@@ -472,7 +452,7 @@ CoverService::loadFromCache(const CacheEntryDesc& entryDesc)
|
|||||||
|
|
||||||
++_cacheHits;
|
++_cacheHits;
|
||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Cover
|
} // namespace Cover
|
||||||
|
|
||||||
|
|||||||
@@ -39,11 +39,12 @@ namespace Database
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
Wt::Dbo::Query<TrackId> createQuery(Session& session, const Track::FindParameters& params)
|
template <typename ResultType>
|
||||||
|
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Track::FindParameters& params)
|
||||||
{
|
{
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|
||||||
auto query{ session.getDboSession().query<TrackId>(params.distinct ? "SELECT DISTINCT t.id FROM track t" : "SELECT t.id FROM track t") };
|
auto query{ session.getDboSession().query<ResultType>(params.distinct ? "SELECT DISTINCT " + std::string{ itemToSelect } + " FROM track t" : "SELECT t.id FROM track t") };
|
||||||
|
|
||||||
assert(params.keywords.empty() || params.name.empty());
|
assert(params.keywords.empty() || params.name.empty());
|
||||||
for (std::string_view keyword : params.keywords)
|
for (std::string_view keyword : params.keywords)
|
||||||
@@ -168,6 +169,21 @@ namespace Database
|
|||||||
|
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename ResultType>
|
||||||
|
Wt::Dbo::Query<ResultType> createQuery(Session& session, const Track::FindParameters& params)
|
||||||
|
{
|
||||||
|
std::string_view itemToSelect;
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<ResultType, TrackId>)
|
||||||
|
itemToSelect = "t.id";
|
||||||
|
else if constexpr (std::is_same_v<ResultType, Wt::Dbo::ptr<Track>>)
|
||||||
|
itemToSelect = "t";
|
||||||
|
else
|
||||||
|
static_assert("Unhandled type");
|
||||||
|
|
||||||
|
return createQuery<ResultType>(session, itemToSelect, params);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Track::Track(const std::filesystem::path& p)
|
Track::Track(const std::filesystem::path& p)
|
||||||
@@ -257,7 +273,7 @@ namespace Database
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
RangeResults<TrackId> Track::findTrackMBIDDuplicates(Session& session, Range range)
|
RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, Range range)
|
||||||
{
|
{
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|
||||||
@@ -267,7 +283,7 @@ namespace Database
|
|||||||
return Utils::execQuery(query, range);
|
return Utils::execQuery(query, range);
|
||||||
}
|
}
|
||||||
|
|
||||||
RangeResults<TrackId> Track::findWithRecordingMBIDAndMissingFeatures(Session& session, Range range)
|
RangeResults<TrackId> Track::findIdsWithRecordingMBIDAndMissingFeatures(Session& session, Range range)
|
||||||
{
|
{
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|
||||||
@@ -295,16 +311,23 @@ namespace Database
|
|||||||
return std::vector<ClusterId>(res.begin(), res.end());
|
return std::vector<ClusterId>(res.begin(), res.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
RangeResults<TrackId> Track::find(Session& session, const FindParameters& parameters)
|
RangeResults<TrackId> Track::findIds(Session& session, const FindParameters& parameters)
|
||||||
{
|
{
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|
||||||
auto query{ createQuery(session, parameters) };
|
auto query{ createQuery<TrackId>(session, parameters) };
|
||||||
|
|
||||||
return Utils::execQuery(query, parameters.range);
|
return Utils::execQuery(query, parameters.range);
|
||||||
}
|
}
|
||||||
|
|
||||||
RangeResults<TrackId> Track::findSimilarTracks(Session& session, const std::vector<TrackId>& tracks, Range range)
|
RangeResults<Track::pointer> Track::find(Session& session, const FindParameters& parameters)
|
||||||
|
{
|
||||||
|
session.checkSharedLocked();
|
||||||
|
|
||||||
|
auto query{ createQuery<Wt::Dbo::ptr<Track>>(session, parameters) };
|
||||||
|
return Utils::execQuery(query, parameters.range);
|
||||||
|
}
|
||||||
|
|
||||||
|
RangeResults<TrackId> Track::findSimilarTrackIds(Session& session, const std::vector<TrackId>& tracks, Range range)
|
||||||
{
|
{
|
||||||
assert(!tracks.empty());
|
assert(!tracks.empty());
|
||||||
session.checkSharedLocked();
|
session.checkSharedLocked();
|
||||||
|
|||||||
@@ -21,11 +21,10 @@
|
|||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <iostream>
|
#include <ostream>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_set>
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -111,12 +110,13 @@ class Track final : public Object<Track, TrackId>
|
|||||||
static bool exists(Session& session, TrackId id);
|
static bool exists(Session& session, TrackId id);
|
||||||
static std::vector<pointer> findByRecordingMBID(Session& session, const UUID& MBID);
|
static std::vector<pointer> findByRecordingMBID(Session& session, const UUID& MBID);
|
||||||
static std::vector<pointer> findByMBID(Session& session, const UUID& MBID);
|
static std::vector<pointer> findByMBID(Session& session, const UUID& MBID);
|
||||||
static RangeResults<TrackId> findSimilarTracks(Session& session, const std::vector<TrackId>& trackIds, Range range);
|
static RangeResults<TrackId> findSimilarTrackIds(Session& session, const std::vector<TrackId>& trackIds, Range range);
|
||||||
|
|
||||||
static RangeResults<TrackId> find(Session& session, const FindParameters& parameters);
|
static RangeResults<TrackId> findIds(Session& session, const FindParameters& parameters);
|
||||||
|
static RangeResults<pointer> find(Session& session, const FindParameters& parameters);
|
||||||
static RangeResults<PathResult> findPaths(Session& session, Range range);
|
static RangeResults<PathResult> findPaths(Session& session, Range range);
|
||||||
static RangeResults<TrackId> findTrackMBIDDuplicates(Session& session, Range range);
|
static RangeResults<TrackId> findIdsTrackMBIDDuplicates(Session& session, Range range);
|
||||||
static RangeResults<TrackId> findWithRecordingMBIDAndMissingFeatures(Session& session, Range range);
|
static RangeResults<TrackId> findIdsWithRecordingMBIDAndMissingFeatures(Session& session, Range range);
|
||||||
|
|
||||||
// Accessors
|
// Accessors
|
||||||
void setScanVersion(std::size_t version) { _scanVersion = version; }
|
void setScanVersion(std::size_t version) { _scanVersion = version; }
|
||||||
|
|||||||
@@ -24,17 +24,17 @@ using namespace Database;
|
|||||||
TEST_F(DatabaseFixture, Artist)
|
TEST_F(DatabaseFixture, Artist)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
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));
|
||||||
EXPECT_EQ(Artist::getCount(session), 0);
|
EXPECT_EQ(Artist::getCount(session), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
ScopedArtist artist {session, "MyArtist"};
|
ScopedArtist artist{ session, "MyArtist" };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
EXPECT_TRUE(artist.get());
|
EXPECT_TRUE(artist.get());
|
||||||
EXPECT_FALSE(!artist.get());
|
EXPECT_FALSE(!artist.get());
|
||||||
@@ -45,13 +45,13 @@ TEST_F(DatabaseFixture, Artist)
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
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);
|
||||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||||
|
|
||||||
artists = Artist::findAllOrphans(session, Range {});
|
artists = Artist::findAllOrphans(session, Range{});
|
||||||
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());
|
||||||
}
|
}
|
||||||
@@ -59,69 +59,69 @@ TEST_F(DatabaseFixture, Artist)
|
|||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_singleTrack)
|
TEST_F(DatabaseFixture, Artist_singleTrack)
|
||||||
{
|
{
|
||||||
ScopedTrack track {session, "MyTrack"};
|
ScopedTrack track{ session, "MyTrack" };
|
||||||
ScopedArtist artist {session, "MyArtist"};
|
ScopedArtist artist{ session, "MyArtist" };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createUniqueTransaction()};
|
auto transaction{ session.createUniqueTransaction() };
|
||||||
|
|
||||||
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.createSharedTransaction() };
|
||||||
EXPECT_TRUE(Artist::findAllOrphans(session, Range {}).results.empty());
|
EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
auto artists {track->getArtists({TrackArtistLinkType::Artist})};
|
auto artists{ track->getArtists({TrackArtistLinkType::Artist}) };
|
||||||
ASSERT_EQ(artists.size(), 1);
|
ASSERT_EQ(artists.size(), 1);
|
||||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||||
|
|
||||||
ASSERT_EQ(track->getArtistLinks().size(), 1);
|
ASSERT_EQ(track->getArtistLinks().size(), 1);
|
||||||
auto artistLink {track->getArtistLinks().front()};
|
auto artistLink{ track->getArtistLinks().front() };
|
||||||
EXPECT_EQ(artistLink->getTrack()->getId(), track.getId());
|
EXPECT_EQ(artistLink->getTrack()->getId(), track.getId());
|
||||||
EXPECT_EQ(artistLink->getArtist()->getId(), artist.getId());
|
EXPECT_EQ(artistLink->getArtist()->getId(), artist.getId());
|
||||||
|
|
||||||
ASSERT_EQ(track->getArtists({TrackArtistLinkType::Artist}).size(), 1);
|
ASSERT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 1);
|
||||||
EXPECT_TRUE(track->getArtists({TrackArtistLinkType::ReleaseArtist}).empty());
|
EXPECT_TRUE(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).empty());
|
||||||
EXPECT_EQ(track->getArtists({}).size(), 1);
|
EXPECT_EQ(track->getArtists({}).size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
auto artists {track->getArtistIds({TrackArtistLinkType::Artist})};
|
auto artists{ track->getArtistIds({TrackArtistLinkType::Artist}) };
|
||||||
ASSERT_EQ(artists.size(), 1);
|
ASSERT_EQ(artists.size(), 1);
|
||||||
EXPECT_EQ(artists.front(), artist.getId());
|
EXPECT_EQ(artists.front(), artist.getId());
|
||||||
|
|
||||||
ASSERT_EQ(track->getArtistIds({TrackArtistLinkType::Artist}).size(), 1);
|
ASSERT_EQ(track->getArtistIds({ TrackArtistLinkType::Artist }).size(), 1);
|
||||||
EXPECT_TRUE(track->getArtistIds({TrackArtistLinkType::ReleaseArtist}).empty());
|
EXPECT_TRUE(track->getArtistIds({ TrackArtistLinkType::ReleaseArtist }).empty());
|
||||||
EXPECT_EQ(track->getArtistIds({}).size(), 1);
|
EXPECT_EQ(track->getArtistIds({}).size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
auto tracks {Track::find(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.createSharedTransaction() };
|
||||||
auto tracks {Track::find(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.createSharedTransaction() };
|
||||||
auto tracks {Track::find(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.createSharedTransaction() };
|
||||||
auto artists {Artist::find(session, Artist::FindParameters{}.setTrack(track->getId()))};
|
auto artists{ Artist::find(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());
|
||||||
}
|
}
|
||||||
@@ -129,10 +129,10 @@ TEST_F(DatabaseFixture, Artist_singleTrack)
|
|||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
|
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.createUniqueTransaction() };
|
||||||
|
|
||||||
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);
|
||||||
@@ -140,48 +140,48 @@ TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
EXPECT_TRUE(Artist::findAllOrphans(session, Range {}).results.empty());
|
EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters {}).results.size(), 1);
|
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}).results.size(), 1);
|
||||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters {}.setLinkType(TrackArtistLinkType::Artist)).results.size(), 1);
|
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Artist)).results.size(), 1);
|
||||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters {}.setLinkType(TrackArtistLinkType::ReleaseArtist)).results.size(), 1);
|
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::ReleaseArtist)).results.size(), 1);
|
||||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters {}.setLinkType(TrackArtistLinkType::Writer)).results.size(), 1);
|
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Writer)).results.size(), 1);
|
||||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters {}.setLinkType(TrackArtistLinkType::Composer)).results.size(), 0);
|
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Composer)).results.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
auto artists {track->getArtists({TrackArtistLinkType::Artist})};
|
auto artists{ track->getArtists({TrackArtistLinkType::Artist}) };
|
||||||
ASSERT_EQ(artists.size(), 1);
|
ASSERT_EQ(artists.size(), 1);
|
||||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||||
|
|
||||||
artists = track->getArtists({TrackArtistLinkType::ReleaseArtist});
|
artists = track->getArtists({ TrackArtistLinkType::ReleaseArtist });
|
||||||
ASSERT_EQ(artists.size(), 1);
|
ASSERT_EQ(artists.size(), 1);
|
||||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||||
|
|
||||||
EXPECT_EQ(track->getArtistLinks().size(), 3);
|
EXPECT_EQ(track->getArtistLinks().size(), 3);
|
||||||
|
|
||||||
auto tracks {Track::find(session, Track::FindParameters {}.setArtist(artist.getId()))};
|
auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist.getId())) };
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
|
|
||||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::ReleaseArtist}));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist }));
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Artist}));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist }));
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Writer}));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Writer }));
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
|
|
||||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Composer}));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer }));
|
||||||
EXPECT_EQ(tracks.results.size(), 0);
|
EXPECT_EQ(tracks.results.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
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));
|
||||||
@@ -192,84 +192,84 @@ TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
|
|||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists)
|
TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists)
|
||||||
{
|
{
|
||||||
ScopedTrack track {session, "track"};
|
ScopedTrack track{ session, "track" };
|
||||||
ScopedArtist artist1 {session, "artist1"};
|
ScopedArtist artist1{ session, "artist1" };
|
||||||
ScopedArtist artist2 {session, "artist2"};
|
ScopedArtist artist2{ session, "artist2" };
|
||||||
ASSERT_NE(artist1.getId(), artist2.getId());
|
ASSERT_NE(artist1.getId(), artist2.getId());
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createUniqueTransaction()};
|
auto transaction{ session.createUniqueTransaction() };
|
||||||
|
|
||||||
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.createSharedTransaction() };
|
||||||
EXPECT_TRUE(Artist::findAllOrphans(session, Range {}).results.empty());
|
EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
auto artists {track->getArtists({TrackArtistLinkType::Artist})};
|
auto artists{ track->getArtists({TrackArtistLinkType::Artist}) };
|
||||||
ASSERT_EQ(artists.size(), 2);
|
ASSERT_EQ(artists.size(), 2);
|
||||||
EXPECT_TRUE((artists[0]->getId() == artist1.getId() && artists[1]->getId() == artist2.getId())
|
EXPECT_TRUE((artists[0]->getId() == artist1.getId() && artists[1]->getId() == artist2.getId())
|
||||||
|| (artists[0]->getId() == artist2.getId() && artists[1]->getId() == artist1.getId()));
|
|| (artists[0]->getId() == artist2.getId() && artists[1]->getId() == artist1.getId()));
|
||||||
|
|
||||||
EXPECT_EQ(track->getArtists({}).size(), 2);
|
EXPECT_EQ(track->getArtists({}).size(), 2);
|
||||||
EXPECT_EQ(track->getArtists({TrackArtistLinkType::Artist}).size(), 2);
|
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 2);
|
||||||
EXPECT_TRUE(track->getArtists({TrackArtistLinkType::ReleaseArtist}).empty());
|
EXPECT_TRUE(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).empty());
|
||||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters {}).results.size(), 2);
|
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}).results.size(), 2);
|
||||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::Random)).results.size(), 2);
|
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Random)).results.size(), 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
auto tracks {Track::find(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);
|
||||||
EXPECT_EQ(tracks.results.front(), track->getId());
|
EXPECT_EQ(tracks.results.front(), track->getId());
|
||||||
|
|
||||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist2->getId()));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId()));
|
||||||
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());
|
||||||
|
|
||||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist1->getId(), {TrackArtistLinkType::ReleaseArtist}));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::ReleaseArtist }));
|
||||||
EXPECT_EQ(tracks.results.size(), 0);
|
EXPECT_EQ(tracks.results.size(), 0);
|
||||||
|
|
||||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist1->getId(), {TrackArtistLinkType::Artist}));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::Artist }));
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
|
|
||||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist2->getId(), {TrackArtistLinkType::ReleaseArtist}));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::ReleaseArtist }));
|
||||||
EXPECT_EQ(tracks.results.size(), 0);
|
EXPECT_EQ(tracks.results.size(), 0);
|
||||||
|
|
||||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist2->getId(), {TrackArtistLinkType::Artist}));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::Artist }));
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_findByName)
|
TEST_F(DatabaseFixture, Artist_findByName)
|
||||||
{
|
{
|
||||||
ScopedArtist artist {session, "AAA"};
|
ScopedArtist artist{ session, "AAA" };
|
||||||
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.createUniqueTransaction() };
|
||||||
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.createSharedTransaction() };
|
||||||
|
|
||||||
EXPECT_TRUE(Artist::find(session, Artist::FindParameters {}.setKeywords({"N"})).results.empty());
|
EXPECT_TRUE(Artist::find(session, Artist::FindParameters{}.setKeywords({ "N" })).results.empty());
|
||||||
|
|
||||||
const auto artistsByAAA {Artist::find(session, Artist::FindParameters {}.setKeywords({"A"}))};
|
const auto artistsByAAA{ Artist::find(session, Artist::FindParameters {}.setKeywords({"A"})) };
|
||||||
ASSERT_EQ(artistsByAAA.results.size(), 1);
|
ASSERT_EQ(artistsByAAA.results.size(), 1);
|
||||||
EXPECT_EQ(artistsByAAA.results.front(), artist.getId());
|
EXPECT_EQ(artistsByAAA.results.front(), artist.getId());
|
||||||
|
|
||||||
const auto artistsByZZZ {Artist::Artist::find(session, Artist::FindParameters {}.setKeywords({"Z"}))};
|
const auto artistsByZZZ{ Artist::Artist::find(session, Artist::FindParameters {}.setKeywords({"Z"})) };
|
||||||
ASSERT_EQ(artistsByZZZ.results.size(), 1);
|
ASSERT_EQ(artistsByZZZ.results.size(), 1);
|
||||||
EXPECT_EQ(artistsByZZZ.results.front(), artist.getId());
|
EXPECT_EQ(artistsByZZZ.results.front(), artist.getId());
|
||||||
|
|
||||||
@@ -280,30 +280,30 @@ TEST_F(DatabaseFixture, Artist_findByName)
|
|||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_findByNameEscaped)
|
TEST_F(DatabaseFixture, Artist_findByNameEscaped)
|
||||||
{
|
{
|
||||||
ScopedArtist artist1 {session, "MyArtist%"};
|
ScopedArtist artist1{ session, "MyArtist%" };
|
||||||
ScopedArtist artist2 {session, "%MyArtist"};
|
ScopedArtist artist2{ session, "%MyArtist" };
|
||||||
ScopedArtist artist3 {session, "%_MyArtist"};
|
ScopedArtist artist3{ session, "%_MyArtist" };
|
||||||
|
|
||||||
ScopedArtist artist4 {session, "MyArtist%foo"};
|
ScopedArtist artist4{ session, "MyArtist%foo" };
|
||||||
ScopedArtist artist5 {session, "foo%MyArtist"};
|
ScopedArtist artist5{ session, "foo%MyArtist" };
|
||||||
ScopedArtist artist6 {session, "%AMyArtist"};
|
ScopedArtist artist6{ session, "%AMyArtist" };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
{
|
{
|
||||||
const auto artists {Artist::find(session, "MyArtist%")};
|
const auto artists{ Artist::find(session, "MyArtist%") };
|
||||||
ASSERT_TRUE(artists.size() == 1);
|
ASSERT_TRUE(artists.size() == 1);
|
||||||
EXPECT_EQ(artists.front()->getId(), artist1.getId());
|
EXPECT_EQ(artists.front()->getId(), artist1.getId());
|
||||||
EXPECT_TRUE(Artist::find(session, "MyArtistFoo").empty());
|
EXPECT_TRUE(Artist::find(session, "MyArtistFoo").empty());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const auto artists {Artist::find(session, "%MyArtist")};
|
const auto artists{ Artist::find(session, "%MyArtist") };
|
||||||
ASSERT_TRUE(artists.size() == 1);
|
ASSERT_TRUE(artists.size() == 1);
|
||||||
EXPECT_EQ(artists.front()->getId(), artist2.getId());
|
EXPECT_EQ(artists.front()->getId(), artist2.getId());
|
||||||
EXPECT_TRUE(Artist::find(session, "FooMyArtist").empty());
|
EXPECT_TRUE(Artist::find(session, "FooMyArtist").empty());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const auto artists {Artist::find(session, "%_MyArtist")};
|
const auto artists{ Artist::find(session, "%_MyArtist") };
|
||||||
ASSERT_TRUE(artists.size() == 1);
|
ASSERT_TRUE(artists.size() == 1);
|
||||||
ASSERT_EQ(artists.front()->getId(), artist3.getId());
|
ASSERT_EQ(artists.front()->getId(), artist3.getId());
|
||||||
EXPECT_TRUE(Artist::find(session, "%CMyArtist").empty());
|
EXPECT_TRUE(Artist::find(session, "%CMyArtist").empty());
|
||||||
@@ -311,28 +311,28 @@ TEST_F(DatabaseFixture, Artist_findByNameEscaped)
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
{
|
{
|
||||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setKeywords({"MyArtist"}))};
|
const auto artists{ Artist::find(session, Artist::FindParameters {}.setKeywords({"MyArtist"})) };
|
||||||
EXPECT_EQ(artists.results.size(), 6);
|
EXPECT_EQ(artists.results.size(), 6);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setKeywords({"MyArtist%"}).setSortMethod(ArtistSortMethod::ByName))};
|
const auto artists{ Artist::find(session, Artist::FindParameters {}.setKeywords({"MyArtist%"}).setSortMethod(ArtistSortMethod::ByName)) };
|
||||||
ASSERT_EQ(artists.results.size(), 2);
|
ASSERT_EQ(artists.results.size(), 2);
|
||||||
EXPECT_EQ(artists.results[0], artist1.getId());
|
EXPECT_EQ(artists.results[0], artist1.getId());
|
||||||
EXPECT_EQ(artists.results[1], artist4.getId());
|
EXPECT_EQ(artists.results[1], artist4.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setKeywords({"%MyArtist"}).setSortMethod(ArtistSortMethod::ByName))};
|
const auto artists{ Artist::find(session, Artist::FindParameters {}.setKeywords({"%MyArtist"}).setSortMethod(ArtistSortMethod::ByName)) };
|
||||||
ASSERT_EQ(artists.results.size(), 2);
|
ASSERT_EQ(artists.results.size(), 2);
|
||||||
EXPECT_EQ(artists.results[0], artist2.getId());
|
EXPECT_EQ(artists.results[0], artist2.getId());
|
||||||
EXPECT_EQ(artists.results[1], artist5.getId());
|
EXPECT_EQ(artists.results[1], artist5.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setKeywords({"_MyArtist"}).setSortMethod(ArtistSortMethod::ByName))};
|
const auto artists{ Artist::find(session, Artist::FindParameters {}.setKeywords({"_MyArtist"}).setSortMethod(ArtistSortMethod::ByName)) };
|
||||||
ASSERT_EQ(artists.results.size(), 1);
|
ASSERT_EQ(artists.results.size(), 1);
|
||||||
EXPECT_EQ(artists.results[0], artist3.getId());
|
EXPECT_EQ(artists.results[0], artist3.getId());
|
||||||
}
|
}
|
||||||
@@ -341,21 +341,21 @@ TEST_F(DatabaseFixture, Artist_findByNameEscaped)
|
|||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_sortMethod)
|
TEST_F(DatabaseFixture, Artist_sortMethod)
|
||||||
{
|
{
|
||||||
ScopedArtist artistA {session, "artistA"};
|
ScopedArtist artistA{ session, "artistA" };
|
||||||
ScopedArtist artistB {session, "artistB"};
|
ScopedArtist artistB{ session, "artistB" };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createUniqueTransaction()};
|
auto transaction{ session.createUniqueTransaction() };
|
||||||
|
|
||||||
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.createSharedTransaction() };
|
||||||
|
|
||||||
auto allArtistsByName {Artist::find(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::ByName))};
|
auto allArtistsByName{ Artist::find(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::ByName)) };
|
||||||
auto allArtistsBySortName {Artist::find(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::BySortName))};
|
auto allArtistsBySortName{ Artist::find(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::BySortName)) };
|
||||||
|
|
||||||
ASSERT_EQ(allArtistsByName.results.size(), 2);
|
ASSERT_EQ(allArtistsByName.results.size(), 2);
|
||||||
EXPECT_EQ(allArtistsByName.results.front(), artistA.getId());
|
EXPECT_EQ(allArtistsByName.results.front(), artistA.getId());
|
||||||
@@ -369,20 +369,20 @@ TEST_F(DatabaseFixture, Artist_sortMethod)
|
|||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_nonReleaseTracks)
|
TEST_F(DatabaseFixture, Artist_nonReleaseTracks)
|
||||||
{
|
{
|
||||||
ScopedArtist artist {session, "artist"};
|
ScopedArtist artist{ session, "artist" };
|
||||||
ScopedTrack track1 {session, "MyTrack1"};
|
ScopedTrack track1{ session, "MyTrack1" };
|
||||||
ScopedTrack track2 {session, "MyTrack2"};
|
ScopedTrack track2{ session, "MyTrack2" };
|
||||||
ScopedRelease release{session, "MyRelease"};
|
ScopedRelease release{ session, "MyRelease" };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
auto tracks {Track::find(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.createUniqueTransaction() };
|
||||||
|
|
||||||
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);
|
||||||
@@ -391,9 +391,9 @@ TEST_F(DatabaseFixture, Artist_nonReleaseTracks)
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
const auto tracks {Track::find(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);
|
||||||
EXPECT_EQ(tracks.results.front(), track2.getId());
|
EXPECT_EQ(tracks.results.front(), track2.getId());
|
||||||
}
|
}
|
||||||
@@ -401,37 +401,36 @@ TEST_F(DatabaseFixture, Artist_nonReleaseTracks)
|
|||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_findByRelease)
|
TEST_F(DatabaseFixture, Artist_findByRelease)
|
||||||
{
|
{
|
||||||
ScopedArtist artist {session, "artist"};
|
ScopedArtist artist{ session, "artist" };
|
||||||
ScopedTrack track {session, "MyTrack"};
|
ScopedTrack track{ session, "MyTrack" };
|
||||||
ScopedRelease release{session, "MyRelease"};
|
ScopedRelease release{ session, "MyRelease" };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setRelease(release.getId()))};
|
const auto artists{ Artist::find(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.createUniqueTransaction() };
|
||||||
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.createSharedTransaction() };
|
||||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setRelease(release.getId()))};
|
const auto artists{ Artist::find(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.createUniqueTransaction() };
|
||||||
track.get().modify()->setRelease(release.get());
|
track.get().modify()->setRelease(release.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setRelease(release.getId()))};
|
const auto artists{ Artist::find(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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,11 +133,11 @@ TEST_F(DatabaseFixture, Cluster_singleTrack)
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createSharedTransaction() };
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
auto tracks{ Track::find(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);
|
||||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||||
|
|
||||||
tracks = Track::find(session, Track::FindParameters{}.setClusters({ cluster2.getId() }));
|
tracks = Track::findIds(session, Track::FindParameters{}.setClusters({ cluster2.getId() }));
|
||||||
EXPECT_TRUE(tracks.results.empty());
|
EXPECT_TRUE(tracks.results.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters)
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createSharedTransaction() };
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
const auto tracks{ Track::find(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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters)
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createSharedTransaction() };
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
const auto tracks{ Track::find(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());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -193,7 +193,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters)
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createSharedTransaction() };
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
const auto tracks{ Track::find(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());
|
||||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||||
}
|
}
|
||||||
@@ -550,7 +550,7 @@ TEST_F(DatabaseFixture, MultipleTracksSingleClusterSimilarity)
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createSharedTransaction() };
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
const auto similarTracks{ Track::findSimilarTracks(session, {tracks.front().getId()}, Range {}) };
|
const auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}, Range {}) };
|
||||||
EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1);
|
EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1);
|
||||||
for (const TrackId similarTrackId : similarTracks.results)
|
for (const TrackId similarTrackId : similarTracks.results)
|
||||||
{
|
{
|
||||||
@@ -591,14 +591,14 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleClustersSimilarity)
|
|||||||
auto transaction{ session.createSharedTransaction() };
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto similarTracks{ Track::findSimilarTracks(session, {tracks.back().getId()}, Range {0, 4}) };
|
auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.back().getId()}, Range {0, 4}) };
|
||||||
EXPECT_EQ(similarTracks.results.size(), 4);
|
EXPECT_EQ(similarTracks.results.size(), 4);
|
||||||
for (const TrackId similarTrackId : similarTracks.results)
|
for (const TrackId similarTrackId : similarTracks.results)
|
||||||
EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 5), std::next(std::cend(tracks), -1), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks));
|
EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 5), std::next(std::cend(tracks), -1), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto similarTracks{ Track::findSimilarTracks(session, {tracks.front().getId()}, Range {}) };
|
auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}, Range {}) };
|
||||||
EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1);
|
EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1);
|
||||||
for (const TrackId similarTrackId : similarTracks.results)
|
for (const TrackId similarTrackId : similarTracks.results)
|
||||||
EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks));
|
EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks));
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ TEST_F(DatabaseFixture, Release_singleTrack)
|
|||||||
auto transaction{ session.createSharedTransaction() };
|
auto transaction{ session.createSharedTransaction() };
|
||||||
EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty());
|
EXPECT_TRUE(Release::findOrphanIds(session, Range{}).results.empty());
|
||||||
|
|
||||||
const auto tracks{ Track::find(session, Track::FindParameters {}.setRelease(release.getId())) };
|
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) };
|
||||||
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());
|
||||||
}
|
}
|
||||||
@@ -92,18 +92,18 @@ TEST_F(DatabaseFixture, Release_singleTrack)
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createUniqueTransaction() };
|
auto transaction{ session.createUniqueTransaction() };
|
||||||
auto tracks{ Track::find(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.createUniqueTransaction() };
|
||||||
auto tracks{ Track::find(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.createUniqueTransaction() };
|
||||||
auto tracks{ Track::find(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -111,7 +111,7 @@ TEST_F(DatabaseFixture, Release_singleTrack)
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createUniqueTransaction() };
|
auto transaction{ session.createUniqueTransaction() };
|
||||||
|
|
||||||
const auto tracks{ Track::find(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());
|
||||||
|
|
||||||
auto releases{ Release::findOrphanIds(session, Range {}) };
|
auto releases{ Release::findOrphanIds(session, Range {}) };
|
||||||
@@ -285,8 +285,8 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack)
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createSharedTransaction() };
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
EXPECT_TRUE(Track::find(session, Track::FindParameters{}.setRelease(release1.getId())).results.empty());
|
EXPECT_TRUE(Track::findIds(session, Track::FindParameters{}.setRelease(release1.getId())).results.empty());
|
||||||
EXPECT_TRUE(Track::find(session, Track::FindParameters{}.setRelease(release2.getId())).results.empty());
|
EXPECT_TRUE(Track::findIds(session, Track::FindParameters{}.setRelease(release2.getId())).results.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -310,13 +310,13 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack)
|
|||||||
auto transaction{ session.createSharedTransaction() };
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto tracks{ Track::find(session, Track::FindParameters {}.setRelease(release1.getId()).setSortMethod(TrackSortMethod::Release)) };
|
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release1.getId()).setSortMethod(TrackSortMethod::Release)) };
|
||||||
ASSERT_FALSE(tracks.results.empty());
|
ASSERT_FALSE(tracks.results.empty());
|
||||||
EXPECT_EQ(tracks.results.front(), track1A.getId());
|
EXPECT_EQ(tracks.results.front(), track1A.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto tracks{ Track::find(session, Track::FindParameters {}.setRelease(release2.getId()).setSortMethod(TrackSortMethod::Release)) };
|
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release2.getId()).setSortMethod(TrackSortMethod::Release)) };
|
||||||
ASSERT_FALSE(tracks.results.empty());
|
ASSERT_FALSE(tracks.results.empty());
|
||||||
EXPECT_EQ(tracks.results.front(), track2B.getId());
|
EXPECT_EQ(tracks.results.front(), track2B.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ TEST_F(DatabaseFixture, StarredTrack)
|
|||||||
EXPECT_FALSE(starredTrack);
|
EXPECT_FALSE(starredTrack);
|
||||||
EXPECT_EQ(StarredTrack::getCount(session), 0);
|
EXPECT_EQ(StarredTrack::getCount(session), 0);
|
||||||
|
|
||||||
auto tracks {Track::find(session, Track::FindParameters {})};
|
auto tracks {Track::findIds(session, Track::FindParameters {})};
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,13 +53,13 @@ TEST_F(DatabaseFixture, StarredTrack)
|
|||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
auto tracks {Track::find(session, Track::FindParameters {})};
|
auto tracks {Track::findIds(session, Track::FindParameters {})};
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
|
|
||||||
tracks = Track::find(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::find(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -73,11 +73,11 @@ TEST_F(DatabaseFixture, Starredtrack_PendingDestroy)
|
|||||||
{
|
{
|
||||||
auto transaction {session.createUniqueTransaction()};
|
auto transaction {session.createUniqueTransaction()};
|
||||||
|
|
||||||
auto tracks {Track::find(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::find(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ TEST_F(DatabaseFixture, StarredTrack_dateTime)
|
|||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
auto tracks {Track::find(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ TEST_F(DatabaseFixture, StarredTrack_dateTime)
|
|||||||
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::find(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());
|
||||||
@@ -117,7 +117,7 @@ TEST_F(DatabaseFixture, StarredTrack_dateTime)
|
|||||||
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::find(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());
|
||||||
|
|||||||
@@ -26,21 +26,22 @@ using namespace Database;
|
|||||||
TEST_F(DatabaseFixture, Track)
|
TEST_F(DatabaseFixture, Track)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
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::getCount(session), 0);
|
EXPECT_EQ(Track::getCount(session), 0);
|
||||||
EXPECT_FALSE(Track::exists(session, 0));
|
EXPECT_FALSE(Track::exists(session, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
ScopedTrack track {session, "MyTrackFile"};
|
ScopedTrack track{ session, "MyTrackFile" };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
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);
|
||||||
EXPECT_TRUE(Track::exists(session, track.getId()));
|
EXPECT_TRUE(Track::exists(session, track.getId()));
|
||||||
auto myTrack {Track::find(session, track.getId())};
|
auto myTrack{ Track::find(session, track.getId()) };
|
||||||
ASSERT_TRUE(myTrack);
|
ASSERT_TRUE(myTrack);
|
||||||
EXPECT_EQ(myTrack->getId(), track.getId());
|
EXPECT_EQ(myTrack->getId(), track.getId());
|
||||||
}
|
}
|
||||||
@@ -48,11 +49,11 @@ TEST_F(DatabaseFixture, Track)
|
|||||||
|
|
||||||
TEST_F(DatabaseFixture, MultipleTracks)
|
TEST_F(DatabaseFixture, MultipleTracks)
|
||||||
{
|
{
|
||||||
ScopedTrack track1 {session, "MyTrackFile1"};
|
ScopedTrack track1{ session, "MyTrackFile1" };
|
||||||
ScopedTrack track2 {session, "MyTrackFile2"};
|
ScopedTrack track2{ session, "MyTrackFile2" };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
EXPECT_TRUE(track1.getId() != track2.getId());
|
EXPECT_TRUE(track1.getId() != track2.getId());
|
||||||
EXPECT_TRUE(track1.get() != track2.get());
|
EXPECT_TRUE(track1.get() != track2.get());
|
||||||
@@ -62,15 +63,15 @@ TEST_F(DatabaseFixture, MultipleTracks)
|
|||||||
|
|
||||||
TEST_F(DatabaseFixture, MultipleTracksSearchByFilter)
|
TEST_F(DatabaseFixture, MultipleTracksSearchByFilter)
|
||||||
{
|
{
|
||||||
ScopedTrack track1 {session, ""};
|
ScopedTrack track1{ session, "" };
|
||||||
ScopedTrack track2 {session, ""};
|
ScopedTrack track2{ session, "" };
|
||||||
ScopedTrack track3 {session, ""};
|
ScopedTrack track3{ session, "" };
|
||||||
ScopedTrack track4 {session, ""};
|
ScopedTrack track4{ session, "" };
|
||||||
ScopedTrack track5 {session, ""};
|
ScopedTrack track5{ session, "" };
|
||||||
ScopedTrack track6 {session, ""};
|
ScopedTrack track6{ session, "" };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createUniqueTransaction()};
|
auto transaction{ session.createUniqueTransaction() };
|
||||||
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");
|
||||||
@@ -80,25 +81,25 @@ TEST_F(DatabaseFixture, MultipleTracksSearchByFilter)
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto tracks {Track::find(session, Track::FindParameters {}.setKeywords({"Track"}))};
|
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"Track"})) };
|
||||||
EXPECT_EQ(tracks.results.size(), 6);
|
EXPECT_EQ(tracks.results.size(), 6);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const auto tracks {Track::find(session, Track::FindParameters {}.setKeywords({"MyTrack"}))};
|
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"MyTrack"})) };
|
||||||
EXPECT_EQ(tracks.results.size(), 5);
|
EXPECT_EQ(tracks.results.size(), 5);
|
||||||
EXPECT_TRUE(std::none_of(std::cbegin(tracks.results), std::cend(tracks.results), [&](const TrackId trackId) { return trackId == track6.getId(); }));
|
EXPECT_TRUE(std::none_of(std::cbegin(tracks.results), std::cend(tracks.results), [&](const TrackId trackId) { return trackId == track6.getId(); }));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const auto tracks {Track::find(session, Track::FindParameters {}.setKeywords({"MyTrack%"}))};
|
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"MyTrack%"})) };
|
||||||
ASSERT_EQ(tracks.results.size(), 2);
|
ASSERT_EQ(tracks.results.size(), 2);
|
||||||
EXPECT_EQ(tracks.results[0], track2.getId());
|
EXPECT_EQ(tracks.results[0], track2.getId());
|
||||||
EXPECT_EQ(tracks.results[1], track3.getId());
|
EXPECT_EQ(tracks.results[1], track3.getId());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const auto tracks {Track::find(session, Track::FindParameters {}.setKeywords({"%MyTrack"}))};
|
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"%MyTrack"})) };
|
||||||
ASSERT_EQ(tracks.results.size(), 2);
|
ASSERT_EQ(tracks.results.size(), 2);
|
||||||
EXPECT_EQ(tracks.results[0], track4.getId());
|
EXPECT_EQ(tracks.results[0], track4.getId());
|
||||||
EXPECT_EQ(tracks.results[1], track5.getId());
|
EXPECT_EQ(tracks.results[1], track5.getId());
|
||||||
@@ -108,22 +109,22 @@ TEST_F(DatabaseFixture, MultipleTracksSearchByFilter)
|
|||||||
|
|
||||||
TEST_F(DatabaseFixture, Track_date)
|
TEST_F(DatabaseFixture, Track_date)
|
||||||
{
|
{
|
||||||
ScopedTrack track {session, "MyTrack"};
|
ScopedTrack track{ session, "MyTrack" };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
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.createUniqueTransaction() };
|
||||||
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.createSharedTransaction() };
|
||||||
EXPECT_EQ(track->getYear(), 1995);
|
EXPECT_EQ(track->getYear(), 1995);
|
||||||
EXPECT_EQ(track->getOriginalYear(), 1994);
|
EXPECT_EQ(track->getOriginalYear(), 1994);
|
||||||
}
|
}
|
||||||
@@ -131,30 +132,30 @@ TEST_F(DatabaseFixture, Track_date)
|
|||||||
|
|
||||||
TEST_F(DatabaseFixture, Track_writtenAfter)
|
TEST_F(DatabaseFixture, Track_writtenAfter)
|
||||||
{
|
{
|
||||||
ScopedTrack track {session, "MyTrack"};
|
ScopedTrack track{ session, "MyTrack" };
|
||||||
|
|
||||||
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.createUniqueTransaction() };
|
||||||
track.get().modify()->setLastWriteTime(dateTime);
|
track.get().modify()->setLastWriteTime(dateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
const auto tracks {Track::find(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.createSharedTransaction() };
|
||||||
const auto tracks {Track::find(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.createSharedTransaction() };
|
||||||
const auto tracks {Track::find(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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,10 +49,10 @@ TEST_F(DatabaseFixture, SingleTrackListSingleTrack)
|
|||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
auto tracks {Track::find(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);
|
||||||
|
|
||||||
tracks = Track::find(session, Track::FindParameters {}.setTrackList(trackList2.getId()));
|
tracks = Track::findIds(session, Track::FindParameters {}.setTrackList(trackList2.getId()));
|
||||||
EXPECT_EQ(tracks.results.size(), 0);
|
EXPECT_EQ(tracks.results.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,11 +65,11 @@ TEST_F(DatabaseFixture, SingleTrackListSingleTrack)
|
|||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
auto tracks {Track::find(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);
|
||||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||||
|
|
||||||
tracks = Track::find(session, Track::FindParameters {}.setTrackList(trackList2.getId()));
|
tracks = Track::findIds(session, Track::FindParameters {}.setTrackList(trackList2.getId()));
|
||||||
EXPECT_EQ(tracks.results.size(), 0);
|
EXPECT_EQ(tracks.results.size(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ namespace Feedback
|
|||||||
Session& session{ _db.getTLSSession() };
|
Session& session{ _db.getTLSSession() };
|
||||||
auto transaction{ session.createSharedTransaction() };
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
return Track::find(session, params);
|
return Track::findIds(session, params);
|
||||||
}
|
}
|
||||||
} // ns Feedback
|
} // ns Feedback
|
||||||
|
|
||||||
|
|||||||
@@ -32,36 +32,34 @@ namespace Recommendation
|
|||||||
{
|
{
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
std::unique_ptr<IPlaylistGeneratorService>
|
std::unique_ptr<IPlaylistGeneratorService> createPlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService)
|
||||||
createPlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService)
|
|
||||||
{
|
{
|
||||||
return std::make_unique<PlaylistGeneratorService>(db, recommendationService);
|
return std::make_unique<PlaylistGeneratorService>(db, recommendationService);
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaylistGeneratorService::PlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService)
|
PlaylistGeneratorService::PlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService)
|
||||||
: _db {db}
|
: _db{ db }
|
||||||
, _recommendationService {recommendationService}
|
, _recommendationService{ recommendationService }
|
||||||
{
|
{
|
||||||
_constraints.push_back(std::make_unique<PlaylistGeneratorConstraint::ConsecutiveArtists>(_db));
|
_constraints.push_back(std::make_unique<PlaylistGeneratorConstraint::ConsecutiveArtists>(_db));
|
||||||
_constraints.push_back(std::make_unique<PlaylistGeneratorConstraint::ConsecutiveReleases>(_db));
|
_constraints.push_back(std::make_unique<PlaylistGeneratorConstraint::ConsecutiveReleases>(_db));
|
||||||
_constraints.push_back(std::make_unique<PlaylistGeneratorConstraint::DuplicateTracks>());
|
_constraints.push_back(std::make_unique<PlaylistGeneratorConstraint::DuplicateTracks>());
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<TrackId>
|
std::vector<TrackId> PlaylistGeneratorService::extendPlaylist(TrackListId tracklistId, std::size_t maxCount) const
|
||||||
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
|
||||||
|
|
||||||
const std::vector<TrackId> startingTracks {getTracksFromTrackList(tracklistId)};
|
const std::vector<TrackId> startingTracks{ getTracksFromTrackList(tracklistId) };
|
||||||
|
|
||||||
std::vector<TrackId> finalResult = startingTracks;
|
std::vector<TrackId> finalResult = startingTracks;
|
||||||
finalResult.reserve(startingTracks.size() + maxCount);
|
finalResult.reserve(startingTracks.size() + maxCount);
|
||||||
|
|
||||||
std::vector<float> scores;
|
std::vector<float> scores;
|
||||||
for (std::size_t i {}; i < maxCount; ++i)
|
for (std::size_t i{}; i < maxCount; ++i)
|
||||||
{
|
{
|
||||||
if (similarTracks.empty())
|
if (similarTracks.empty())
|
||||||
break;
|
break;
|
||||||
@@ -69,7 +67,7 @@ namespace Recommendation
|
|||||||
scores.resize(similarTracks.size(), {});
|
scores.resize(similarTracks.size(), {});
|
||||||
|
|
||||||
// select the similar track that has the best score
|
// select the similar track that has the best score
|
||||||
for (std::size_t trackIndex {}; trackIndex < similarTracks.size(); ++trackIndex)
|
for (std::size_t trackIndex{}; trackIndex < similarTracks.size(); ++trackIndex)
|
||||||
{
|
{
|
||||||
using namespace Database::Debug;
|
using namespace Database::Debug;
|
||||||
|
|
||||||
@@ -87,7 +85,7 @@ namespace Recommendation
|
|||||||
}
|
}
|
||||||
|
|
||||||
// get the best score
|
// get the best score
|
||||||
const std::size_t bestScoreIndex {static_cast<std::size_t>(std::distance(std::cbegin(scores), std::min_element(std::cbegin(scores), std::cend(scores))))};
|
const std::size_t bestScoreIndex{ static_cast<std::size_t>(std::distance(std::cbegin(scores), std::min_element(std::cbegin(scores), std::cend(scores)))) };
|
||||||
|
|
||||||
finalResult.push_back(similarTracks[bestScoreIndex]);
|
finalResult.push_back(similarTracks[bestScoreIndex]);
|
||||||
similarTracks.erase(std::begin(similarTracks) + bestScoreIndex);
|
similarTracks.erase(std::begin(similarTracks) + bestScoreIndex);
|
||||||
@@ -97,20 +95,19 @@ namespace Recommendation
|
|||||||
return std::vector(std::cbegin(finalResult) + startingTracks.size(), std::cend(finalResult));
|
return std::vector(std::cbegin(finalResult) + startingTracks.size(), std::cend(finalResult));
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackContainer
|
TrackContainer PlaylistGeneratorService::getTracksFromTrackList(Database::TrackListId tracklistId) const
|
||||||
PlaylistGeneratorService::getTracksFromTrackList(Database::TrackListId tracklistId) const
|
|
||||||
{
|
{
|
||||||
TrackContainer tracks;
|
TrackContainer tracks;
|
||||||
|
|
||||||
Session& dbSession {_db.getTLSSession()};
|
Session& dbSession{ _db.getTLSSession() };
|
||||||
auto transaction {dbSession.createSharedTransaction()};
|
auto transaction{ dbSession.createSharedTransaction() };
|
||||||
|
|
||||||
Track::FindParameters params;
|
Track::FindParameters params;
|
||||||
params.setTrackList(tracklistId);
|
params.setTrackList(tracklistId);
|
||||||
params.setSortMethod(TrackSortMethod::TrackList);
|
params.setSortMethod(TrackSortMethod::TrackList);
|
||||||
params.setDistinct(false);
|
params.setDistinct(false);
|
||||||
|
|
||||||
for (const TrackId trackId : Track::find(dbSession, params).results)
|
for (const TrackId trackId : Track::findIds(dbSession, params).results)
|
||||||
tracks.push_back(trackId);
|
tracks.push_back(trackId);
|
||||||
|
|
||||||
return tracks;
|
return tracks;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ namespace Recommendation {
|
|||||||
Session& dbSession{ _db.getTLSSession() };
|
Session& dbSession{ _db.getTLSSession() };
|
||||||
auto transaction{ dbSession.createSharedTransaction() };
|
auto transaction{ dbSession.createSharedTransaction() };
|
||||||
|
|
||||||
const auto similarTrackIds{ Track::findSimilarTracks(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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ namespace Scanner
|
|||||||
Session& session {_db.getTLSSession()};
|
Session& session {_db.getTLSSession()};
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction {session.createSharedTransaction()};
|
||||||
|
|
||||||
const RangeResults<TrackId> tracks = Track::findTrackMBIDDuplicates(session, Range {});
|
const RangeResults<TrackId> tracks = Track::findIdsTrackMBIDDuplicates(session, Range {});
|
||||||
for (const TrackId trackId : tracks.results)
|
for (const TrackId trackId : tracks.results)
|
||||||
{
|
{
|
||||||
const Track::pointer track {Track::find(session, trackId)};
|
const Track::pointer track {Track::find(session, trackId)};
|
||||||
|
|||||||
@@ -44,18 +44,17 @@ namespace
|
|||||||
{
|
{
|
||||||
using namespace Scrobbling::ListenBrainz;
|
using namespace Scrobbling::ListenBrainz;
|
||||||
|
|
||||||
std::optional<Wt::Json::Object>
|
std::optional<Wt::Json::Object> listenToJsonPayload(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint)
|
||||||
listenToJsonPayload(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint)
|
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
const Database::Track::pointer track {Database::Track::find(session, listen.trackId)};
|
const Database::Track::pointer track{ Database::Track::find(session, listen.trackId) };
|
||||||
if (!track)
|
if (!track)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
auto artists {track->getArtists({Database::TrackArtistLinkType::Artist})};
|
auto artists{ track->getArtists({Database::TrackArtistLinkType::Artist}) };
|
||||||
if (artists.empty())
|
if (artists.empty())
|
||||||
artists = track->getArtists({Database::TrackArtistLinkType::ReleaseArtist});
|
artists = track->getArtists({ Database::TrackArtistLinkType::ReleaseArtist });
|
||||||
|
|
||||||
if (artists.empty())
|
if (artists.empty())
|
||||||
{
|
{
|
||||||
@@ -67,72 +66,70 @@ namespace
|
|||||||
additionalInfo["listening_from"] = "LMS";
|
additionalInfo["listening_from"] = "LMS";
|
||||||
if (track->getRelease())
|
if (track->getRelease())
|
||||||
{
|
{
|
||||||
if (auto MBID {track->getRelease()->getMBID()})
|
if (auto MBID{ track->getRelease()->getMBID() })
|
||||||
additionalInfo["release_mbid"] = Wt::Json::Value {std::string {MBID->getAsString()}};
|
additionalInfo["release_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} };
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Wt::Json::Array artistMBIDs;
|
Wt::Json::Array artistMBIDs;
|
||||||
for (const Database::Artist::pointer& artist : artists)
|
for (const Database::Artist::pointer& artist : artists)
|
||||||
{
|
{
|
||||||
if (auto MBID {artist->getMBID()})
|
if (auto MBID{ artist->getMBID() })
|
||||||
artistMBIDs.push_back(Wt::Json::Value {std::string {MBID->getAsString()}});
|
artistMBIDs.push_back(Wt::Json::Value{ std::string {MBID->getAsString()} });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!artistMBIDs.empty())
|
if (!artistMBIDs.empty())
|
||||||
additionalInfo["artist_mbids"] = std::move(artistMBIDs);
|
additionalInfo["artist_mbids"] = std::move(artistMBIDs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (auto MBID {track->getTrackMBID()})
|
if (auto MBID{ track->getTrackMBID() })
|
||||||
additionalInfo["track_mbid"] = Wt::Json::Value {std::string {MBID->getAsString()}};
|
additionalInfo["track_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} };
|
||||||
|
|
||||||
if (auto MBID {track->getRecordingMBID()})
|
if (auto MBID{ track->getRecordingMBID() })
|
||||||
additionalInfo["recording_mbid"] = Wt::Json::Value {std::string {MBID->getAsString()}};
|
additionalInfo["recording_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} };
|
||||||
|
|
||||||
if (const std::optional<std::size_t> trackNumber {track->getTrackNumber()})
|
if (const std::optional<std::size_t> trackNumber{ track->getTrackNumber() })
|
||||||
additionalInfo["tracknumber"] = Wt::Json::Value {static_cast<long long int>(*trackNumber)};
|
additionalInfo["tracknumber"] = Wt::Json::Value{ static_cast<long long int>(*trackNumber) };
|
||||||
|
|
||||||
Wt::Json::Object trackMetadata;
|
Wt::Json::Object trackMetadata;
|
||||||
trackMetadata["additional_info"] = std::move(additionalInfo);
|
trackMetadata["additional_info"] = std::move(additionalInfo);
|
||||||
trackMetadata["artist_name"] = Wt::Json::Value {artists.front()->getName()};
|
trackMetadata["artist_name"] = Wt::Json::Value{ artists.front()->getName() };
|
||||||
trackMetadata["track_name"] = Wt::Json::Value {track->getName()};
|
trackMetadata["track_name"] = Wt::Json::Value{ track->getName() };
|
||||||
if (track->getRelease())
|
if (track->getRelease())
|
||||||
trackMetadata["release_name"] = Wt::Json::Value {track->getRelease()->getName()};
|
trackMetadata["release_name"] = Wt::Json::Value{ track->getRelease()->getName() };
|
||||||
|
|
||||||
Wt::Json::Object payload;
|
Wt::Json::Object payload;
|
||||||
payload["track_metadata"] = std::move(trackMetadata);
|
payload["track_metadata"] = std::move(trackMetadata);
|
||||||
if (timePoint.isValid())
|
if (timePoint.isValid())
|
||||||
payload["listened_at"] = Wt::Json::Value {static_cast<long long int>(timePoint.toTime_t())};
|
payload["listened_at"] = Wt::Json::Value{ static_cast<long long int>(timePoint.toTime_t()) };
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
std::string listenToJsonString(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint, std::string_view listenType)
|
||||||
listenToJsonString(Database::Session& session, const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint, std::string_view listenType)
|
|
||||||
{
|
{
|
||||||
std::string res;
|
std::string res;
|
||||||
|
|
||||||
std::optional<Wt::Json::Object> payload {listenToJsonPayload(session, listen, timePoint)};
|
std::optional<Wt::Json::Object> payload{ listenToJsonPayload(session, listen, timePoint) };
|
||||||
if (!payload)
|
if (!payload)
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
Wt::Json::Object root;
|
Wt::Json::Object root;
|
||||||
root["listen_type"] = Wt::Json::Value {std::string {listenType}};
|
root["listen_type"] = Wt::Json::Value{ std::string {listenType} };
|
||||||
root["payload"] = Wt::Json::Array {std::move(*payload)};
|
root["payload"] = Wt::Json::Array{ std::move(*payload) };
|
||||||
|
|
||||||
res = Wt::Json::serialize(root);
|
res = Wt::Json::serialize(root);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::size_t>
|
std::optional<std::size_t> parseListenCount(std::string_view msgBody)
|
||||||
parseListenCount(std::string_view msgBody)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Wt::Json::Object root;
|
Wt::Json::Object root;
|
||||||
Wt::Json::parse(std::string {msgBody}, root);
|
Wt::Json::parse(std::string{ msgBody }, root);
|
||||||
|
|
||||||
const Wt::Json::Object& payload {static_cast<const Wt::Json::Object&>(root.get("payload"))};
|
const Wt::Json::Object& payload{ static_cast<const Wt::Json::Object&>(root.get("payload")) };
|
||||||
return static_cast<int>(payload.get("count"));
|
return static_cast<int>(payload.get("count"));
|
||||||
}
|
}
|
||||||
catch (const Wt::WException& e)
|
catch (const Wt::WException& e)
|
||||||
@@ -142,17 +139,16 @@ namespace
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Database::TrackId
|
Database::TrackId tryGetMatchingTrack(Database::Session& session, const Listen& listen)
|
||||||
tryGetMatchingTrack(Database::Session& session, const Listen& listen)
|
|
||||||
{
|
{
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
// first try to match using track MBID, and then fallback on possibly ambiguous info
|
// first try to match using track MBID, and then fallback on possibly ambiguous info
|
||||||
if (listen.trackMBID)
|
if (listen.trackMBID)
|
||||||
{
|
{
|
||||||
const auto tracks {Track::findByMBID(session, *listen.trackMBID)};
|
const auto tracks{ Track::findByMBID(session, *listen.trackMBID) };
|
||||||
// if duplicated files, do not record it (let the user correct its database)
|
// if duplicated files, do not record it (let the user correct its database)
|
||||||
if (tracks.size() == 1)
|
if (tracks.size() == 1)
|
||||||
{
|
{
|
||||||
@@ -168,7 +164,7 @@ namespace
|
|||||||
|
|
||||||
if (listen.recordingMBID)
|
if (listen.recordingMBID)
|
||||||
{
|
{
|
||||||
const auto tracks {Track::findByRecordingMBID(session, *listen.recordingMBID)};
|
const auto tracks{ Track::findByRecordingMBID(session, *listen.recordingMBID) };
|
||||||
// if duplicated files, do not record it (let the user correct its database)
|
// if duplicated files, do not record it (let the user correct its database)
|
||||||
if (tracks.size() == 1)
|
if (tracks.size() == 1)
|
||||||
{
|
{
|
||||||
@@ -192,7 +188,7 @@ namespace
|
|||||||
if (listen.trackNumber)
|
if (listen.trackNumber)
|
||||||
params.setTrackNumber(*listen.trackNumber);
|
params.setTrackNumber(*listen.trackNumber);
|
||||||
|
|
||||||
const auto tracks {Track::find(session, params)};
|
const auto tracks{ Track::findIds(session, params) };
|
||||||
// conservative behavior: in case of multiple matches: reject
|
// conservative behavior: in case of multiple matches: reject
|
||||||
if (tracks.results.size() == 1)
|
if (tracks.results.size() == 1)
|
||||||
{
|
{
|
||||||
@@ -213,39 +209,36 @@ namespace
|
|||||||
namespace Scrobbling::ListenBrainz
|
namespace Scrobbling::ListenBrainz
|
||||||
{
|
{
|
||||||
ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client)
|
ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, Database::Db& db, Http::IClient& client)
|
||||||
: _ioContext {ioContext}
|
: _ioContext{ ioContext }
|
||||||
, _db {db}
|
, _db{ db }
|
||||||
, _client {client}
|
, _client{ client }
|
||||||
, _maxSyncListenCount {Service<IConfig>::get()->getULong("listenbrainz-max-sync-listen-count", 1000)}
|
, _maxSyncListenCount{ Service<IConfig>::get()->getULong("listenbrainz-max-sync-listen-count", 1000) }
|
||||||
, _syncListensPeriod {Service<IConfig>::get()->getULong("listenbrainz-sync-listens-period-hours", 1)}
|
, _syncListensPeriod{ Service<IConfig>::get()->getULong("listenbrainz-sync-listens-period-hours", 1) }
|
||||||
{
|
{
|
||||||
LOG(INFO) << "Starting Listens synchronizer, maxSyncListenCount = " << _maxSyncListenCount << ", _syncListensPeriod = " << _syncListensPeriod.count() << " hours";
|
LOG(INFO) << "Starting Listens synchronizer, maxSyncListenCount = " << _maxSyncListenCount << ", _syncListensPeriod = " << _syncListensPeriod.count() << " hours";
|
||||||
|
|
||||||
scheduleSync(std::chrono::seconds {30});
|
scheduleSync(std::chrono::seconds{ 30 });
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::enqueListen(const TimedListen& listen)
|
||||||
ListensSynchronizer::enqueListen(const TimedListen& listen)
|
|
||||||
{
|
{
|
||||||
assert(listen.listenedAt.isValid());
|
assert(listen.listenedAt.isValid());
|
||||||
enqueListen(listen, listen.listenedAt);
|
enqueListen(listen, listen.listenedAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::enqueListenNow(const Scrobbling::Listen& listen)
|
||||||
ListensSynchronizer::enqueListenNow(const Scrobbling::Listen& listen)
|
|
||||||
{
|
{
|
||||||
enqueListen(listen, {});
|
enqueListen(listen, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::enqueListen(const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint)
|
||||||
ListensSynchronizer::enqueListen(const Scrobbling::Listen& listen, const Wt::WDateTime& timePoint)
|
|
||||||
{
|
{
|
||||||
Http::ClientPOSTRequestParameters request;
|
Http::ClientPOSTRequestParameters request;
|
||||||
request.relativeUrl = "/1/submit-listens";
|
request.relativeUrl = "/1/submit-listens";
|
||||||
|
|
||||||
if (timePoint.isValid())
|
if (timePoint.isValid())
|
||||||
{
|
{
|
||||||
const TimedListen timedListen {listen, timePoint};
|
const TimedListen timedListen{ listen, timePoint };
|
||||||
// We want the listen to be sent again later in case of failure, so we just save it as pending send
|
// We want the listen to be sent again later in case of failure, so we just save it as pending send
|
||||||
saveListen(timedListen, Database::SyncState::PendingAdd);
|
saveListen(timedListen, Database::SyncState::PendingAdd);
|
||||||
|
|
||||||
@@ -256,7 +249,7 @@ namespace Scrobbling::ListenBrainz
|
|||||||
{
|
{
|
||||||
if (saveListen(timedListen, Database::SyncState::Synchronized))
|
if (saveListen(timedListen, Database::SyncState::Synchronized))
|
||||||
{
|
{
|
||||||
UserContext& context {getUserContext(listen.userId)};
|
UserContext& context{ getUserContext(listen.userId) };
|
||||||
if (context.listenCount)
|
if (context.listenCount)
|
||||||
(*context.listenCount)++;
|
(*context.listenCount)++;
|
||||||
}
|
}
|
||||||
@@ -271,14 +264,14 @@ namespace Scrobbling::ListenBrainz
|
|||||||
// don't retry on failure
|
// don't retry on failure
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string bodyText {listenToJsonString(_db.getTLSSession(), listen, timePoint, timePoint.isValid() ? "single" : "playing_now")};
|
std::string bodyText{ listenToJsonString(_db.getTLSSession(), listen, timePoint, timePoint.isValid() ? "single" : "playing_now") };
|
||||||
if (bodyText.empty())
|
if (bodyText.empty())
|
||||||
{
|
{
|
||||||
LOG(DEBUG) << "Cannot convert listen to json: skipping";
|
LOG(DEBUG) << "Cannot convert listen to json: skipping";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::optional<UUID> listenBrainzToken {Utils::getListenBrainzToken(_db.getTLSSession(), listen.userId)};
|
const std::optional<UUID> listenBrainzToken{ Utils::getListenBrainzToken(_db.getTLSSession(), listen.userId) };
|
||||||
if (!listenBrainzToken)
|
if (!listenBrainzToken)
|
||||||
{
|
{
|
||||||
LOG(DEBUG) << "No listenbrainz token found: skipping";
|
LOG(DEBUG) << "No listenbrainz token found: skipping";
|
||||||
@@ -286,27 +279,26 @@ namespace Scrobbling::ListenBrainz
|
|||||||
}
|
}
|
||||||
|
|
||||||
request.message.addBodyText(bodyText);
|
request.message.addBodyText(bodyText);
|
||||||
request.message.addHeader("Authorization", "Token " + std::string {listenBrainzToken->getAsString()});
|
request.message.addHeader("Authorization", "Token " + std::string{ listenBrainzToken->getAsString() });
|
||||||
request.message.addHeader("Content-Type", "application/json");
|
request.message.addHeader("Content-Type", "application/json");
|
||||||
_client.sendPOSTRequest(std::move(request));
|
_client.sendPOSTRequest(std::move(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool ListensSynchronizer::saveListen(const TimedListen& listen, Database::SyncState scrobblingState)
|
||||||
ListensSynchronizer::saveListen(const TimedListen& listen, Database::SyncState scrobblingState)
|
|
||||||
{
|
{
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
Session& session {_db.getTLSSession()};
|
Session& session{ _db.getTLSSession() };
|
||||||
auto transaction {session.createUniqueTransaction()}; // TODO: unique only if needed
|
auto transaction{ session.createUniqueTransaction() }; // TODO: unique only if needed
|
||||||
|
|
||||||
Database::Listen::pointer dbListen {Database::Listen::find(session, listen.userId, listen.trackId, Database::ScrobblingBackend::ListenBrainz, listen.listenedAt)};
|
Database::Listen::pointer dbListen{ Database::Listen::find(session, listen.userId, listen.trackId, Database::ScrobblingBackend::ListenBrainz, listen.listenedAt) };
|
||||||
if (!dbListen)
|
if (!dbListen)
|
||||||
{
|
{
|
||||||
const User::pointer user {User::find(session, listen.userId)};
|
const User::pointer user{ User::find(session, listen.userId) };
|
||||||
if (!user)
|
if (!user)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const Track::pointer track {Track::find(session, listen.trackId)};
|
const Track::pointer track{ Track::find(session, listen.trackId) };
|
||||||
if (!track)
|
if (!track)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -325,27 +317,26 @@ namespace Scrobbling::ListenBrainz
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::enquePendingListens()
|
||||||
ListensSynchronizer::enquePendingListens()
|
|
||||||
{
|
{
|
||||||
std::vector<TimedListen> pendingListens;
|
std::vector<TimedListen> pendingListens;
|
||||||
|
|
||||||
{
|
{
|
||||||
Database::Session& session {_db.getTLSSession()};
|
Database::Session& session{ _db.getTLSSession() };
|
||||||
|
|
||||||
auto transaction {session.createUniqueTransaction()};
|
auto transaction{ session.createUniqueTransaction() };
|
||||||
|
|
||||||
Database::Listen::FindParameters params;
|
Database::Listen::FindParameters params;
|
||||||
params.setScrobblingBackend(Database::ScrobblingBackend::ListenBrainz)
|
params.setScrobblingBackend(Database::ScrobblingBackend::ListenBrainz)
|
||||||
.setSyncState(Database::SyncState::PendingAdd)
|
.setSyncState(Database::SyncState::PendingAdd)
|
||||||
.setRange(Database::Range {0, 100}); // don't flood too much?
|
.setRange(Database::Range{ 0, 100 }); // don't flood too much?
|
||||||
|
|
||||||
const Database::RangeResults results {Database::Listen::find(session, params)};
|
const Database::RangeResults results{ Database::Listen::find(session, params) };
|
||||||
pendingListens.reserve(results.results.size());
|
pendingListens.reserve(results.results.size());
|
||||||
|
|
||||||
for (Database::ListenId listenId : results.results)
|
for (Database::ListenId listenId : results.results)
|
||||||
{
|
{
|
||||||
const Database::Listen::pointer listen {Database::Listen::find(session, listenId)};
|
const Database::Listen::pointer listen{ Database::Listen::find(session, listenId) };
|
||||||
|
|
||||||
TimedListen timedListen;
|
TimedListen timedListen;
|
||||||
timedListen.listenedAt = listen->getDateTime();
|
timedListen.listenedAt = listen->getDateTime();
|
||||||
@@ -362,12 +353,11 @@ namespace Scrobbling::ListenBrainz
|
|||||||
enqueListen(pendingListen);
|
enqueListen(pendingListen);
|
||||||
}
|
}
|
||||||
|
|
||||||
ListensSynchronizer::UserContext&
|
ListensSynchronizer::UserContext& ListensSynchronizer::getUserContext(Database::UserId userId)
|
||||||
ListensSynchronizer::getUserContext(Database::UserId userId)
|
|
||||||
{
|
{
|
||||||
assert(_strand.running_in_this_thread());
|
assert(_strand.running_in_this_thread());
|
||||||
|
|
||||||
auto itContext {_userContexts.find(userId)};
|
auto itContext{ _userContexts.find(userId) };
|
||||||
if (itContext == std::cend(_userContexts))
|
if (itContext == std::cend(_userContexts))
|
||||||
{
|
{
|
||||||
std::tie(itContext, std::ignore) = _userContexts.emplace(userId, userId);
|
std::tie(itContext, std::ignore) = _userContexts.emplace(userId, userId);
|
||||||
@@ -376,8 +366,7 @@ namespace Scrobbling::ListenBrainz
|
|||||||
return itContext->second;
|
return itContext->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool ListensSynchronizer::isSyncing() const
|
||||||
ListensSynchronizer::isSyncing() const
|
|
||||||
{
|
{
|
||||||
return std::any_of(std::cbegin(_userContexts), std::cend(_userContexts), [](const auto& contextEntry)
|
return std::any_of(std::cbegin(_userContexts), std::cend(_userContexts), [](const auto& contextEntry)
|
||||||
{
|
{
|
||||||
@@ -385,15 +374,14 @@ namespace Scrobbling::ListenBrainz
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::scheduleSync(std::chrono::seconds fromNow)
|
||||||
ListensSynchronizer::scheduleSync(std::chrono::seconds fromNow)
|
|
||||||
{
|
{
|
||||||
if (_syncListensPeriod.count() == 0 || _maxSyncListenCount == 0)
|
if (_syncListensPeriod.count() == 0 || _maxSyncListenCount == 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)
|
||||||
{
|
{
|
||||||
@@ -402,15 +390,14 @@ namespace Scrobbling::ListenBrainz
|
|||||||
}
|
}
|
||||||
else if (ec)
|
else if (ec)
|
||||||
{
|
{
|
||||||
throw Exception {"GetListens timer failure: " + std::string {ec.message()} };
|
throw Exception{ "GetListens timer failure: " + std::string {ec.message()} };
|
||||||
}
|
}
|
||||||
|
|
||||||
startSync();
|
startSync();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::startSync()
|
||||||
ListensSynchronizer::startSync()
|
|
||||||
{
|
{
|
||||||
LOG(DEBUG) << "Starting sync!";
|
LOG(DEBUG) << "Starting sync!";
|
||||||
|
|
||||||
@@ -420,8 +407,8 @@ namespace Scrobbling::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.createSharedTransaction() };
|
||||||
userIds = Database::User::find(_db.getTLSSession(), Database::User::FindParameters{}.setScrobblingBackend(Database::ScrobblingBackend::ListenBrainz));
|
userIds = Database::User::find(_db.getTLSSession(), Database::User::FindParameters{}.setScrobblingBackend(Database::ScrobblingBackend::ListenBrainz));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -432,8 +419,7 @@ namespace Scrobbling::ListenBrainz
|
|||||||
scheduleSync(_syncListensPeriod);
|
scheduleSync(_syncListensPeriod);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::startSync(UserContext& context)
|
||||||
ListensSynchronizer::startSync(UserContext& context)
|
|
||||||
{
|
{
|
||||||
context.syncing = true;
|
context.syncing = true;
|
||||||
context.listenBrainzUserName = "";
|
context.listenBrainzUserName = "";
|
||||||
@@ -445,8 +431,7 @@ namespace Scrobbling::ListenBrainz
|
|||||||
enqueValidateToken(context);
|
enqueValidateToken(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::onSyncEnded(UserContext& context)
|
||||||
ListensSynchronizer::onSyncEnded(UserContext& context)
|
|
||||||
{
|
{
|
||||||
_strand.dispatch([this, &context]
|
_strand.dispatch([this, &context]
|
||||||
{
|
{
|
||||||
@@ -458,12 +443,11 @@ namespace Scrobbling::ListenBrainz
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::enqueValidateToken(UserContext& context)
|
||||||
ListensSynchronizer::enqueValidateToken(UserContext& context)
|
|
||||||
{
|
{
|
||||||
assert(context.listenBrainzUserName.empty());
|
assert(context.listenBrainzUserName.empty());
|
||||||
|
|
||||||
const std::optional<UUID> listenBrainzToken {Utils::getListenBrainzToken(_db.getTLSSession(), context.userId)};
|
const std::optional<UUID> listenBrainzToken{ Utils::getListenBrainzToken(_db.getTLSSession(), context.userId) };
|
||||||
if (!listenBrainzToken)
|
if (!listenBrainzToken)
|
||||||
{
|
{
|
||||||
onSyncEnded(context);
|
onSyncEnded(context);
|
||||||
@@ -474,7 +458,7 @@ namespace Scrobbling::ListenBrainz
|
|||||||
request.priority = Http::ClientRequestParameters::Priority::Low;
|
request.priority = Http::ClientRequestParameters::Priority::Low;
|
||||||
request.relativeUrl = "/1/validate-token";
|
request.relativeUrl = "/1/validate-token";
|
||||||
request.headers = { {"Authorization", "Token " + std::string {listenBrainzToken->getAsString()}} };
|
request.headers = { {"Authorization", "Token " + std::string {listenBrainzToken->getAsString()}} };
|
||||||
request.onSuccessFunc = [this, &context] (std::string_view msgBody)
|
request.onSuccessFunc = [this, &context](std::string_view msgBody)
|
||||||
{
|
{
|
||||||
context.listenBrainzUserName = Utils::parseValidateToken(msgBody);
|
context.listenBrainzUserName = Utils::parseValidateToken(msgBody);
|
||||||
if (context.listenBrainzUserName.empty())
|
if (context.listenBrainzUserName.empty())
|
||||||
@@ -492,15 +476,14 @@ namespace Scrobbling::ListenBrainz
|
|||||||
_client.sendGETRequest(std::move(request));
|
_client.sendGETRequest(std::move(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::enqueGetListenCount(UserContext& context)
|
||||||
ListensSynchronizer::enqueGetListenCount(UserContext& context)
|
|
||||||
{
|
{
|
||||||
assert(!context.listenBrainzUserName.empty());
|
assert(!context.listenBrainzUserName.empty());
|
||||||
|
|
||||||
Http::ClientGETRequestParameters request;
|
Http::ClientGETRequestParameters request;
|
||||||
request.relativeUrl = "/1/user/" + std::string {context.listenBrainzUserName} + "/listen-count";
|
request.relativeUrl = "/1/user/" + std::string{ context.listenBrainzUserName } + "/listen-count";
|
||||||
request.priority = Http::ClientRequestParameters::Priority::Low;
|
request.priority = Http::ClientRequestParameters::Priority::Low;
|
||||||
request.onSuccessFunc = [=, &context] (std::string_view msgBody)
|
request.onSuccessFunc = [=, &context](std::string_view msgBody)
|
||||||
{
|
{
|
||||||
_strand.dispatch([=, &context]
|
_strand.dispatch([=, &context]
|
||||||
{
|
{
|
||||||
@@ -508,7 +491,7 @@ namespace Scrobbling::ListenBrainz
|
|||||||
if (listenCount)
|
if (listenCount)
|
||||||
LOG(DEBUG) << "Listen count for listenbrainz user '" << context.listenBrainzUserName << "' = " << *listenCount;
|
LOG(DEBUG) << "Listen count for listenbrainz user '" << context.listenBrainzUserName << "' = " << *listenCount;
|
||||||
|
|
||||||
bool needSync {listenCount && (!context.listenCount || *context.listenCount != *listenCount)};
|
bool needSync{ listenCount && (!context.listenCount || *context.listenCount != *listenCount) };
|
||||||
context.listenCount = listenCount;
|
context.listenCount = listenCount;
|
||||||
|
|
||||||
if (!needSync)
|
if (!needSync)
|
||||||
@@ -529,15 +512,14 @@ namespace Scrobbling::ListenBrainz
|
|||||||
_client.sendGETRequest(std::move(request));
|
_client.sendGETRequest(std::move(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::enqueGetListens(UserContext& context)
|
||||||
ListensSynchronizer::enqueGetListens(UserContext& context)
|
|
||||||
{
|
{
|
||||||
assert(!context.listenBrainzUserName.empty());
|
assert(!context.listenBrainzUserName.empty());
|
||||||
|
|
||||||
Http::ClientGETRequestParameters request;
|
Http::ClientGETRequestParameters request;
|
||||||
request.relativeUrl = "/1/user/" + context.listenBrainzUserName + "/listens?max_ts=" + std::to_string(context.maxDateTime.toTime_t());
|
request.relativeUrl = "/1/user/" + context.listenBrainzUserName + "/listens?max_ts=" + std::to_string(context.maxDateTime.toTime_t());
|
||||||
request.priority = Http::ClientRequestParameters::Priority::Low;
|
request.priority = Http::ClientRequestParameters::Priority::Low;
|
||||||
request.onSuccessFunc = [=, &context] (std::string_view msgBody)
|
request.onSuccessFunc = [=, &context](std::string_view msgBody)
|
||||||
{
|
{
|
||||||
processGetListensResponse(msgBody, context);
|
processGetListensResponse(msgBody, context);
|
||||||
if (context.fetchedListenCount >= _maxSyncListenCount || !context.maxDateTime.isValid())
|
if (context.fetchedListenCount >= _maxSyncListenCount || !context.maxDateTime.isValid())
|
||||||
@@ -556,13 +538,12 @@ namespace Scrobbling::ListenBrainz
|
|||||||
_client.sendGETRequest(std::move(request));
|
_client.sendGETRequest(std::move(request));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ListensSynchronizer::processGetListensResponse(std::string_view msgBody, UserContext& context)
|
||||||
ListensSynchronizer::processGetListensResponse(std::string_view msgBody, UserContext& context)
|
|
||||||
{
|
{
|
||||||
Database::Session& session {_db.getTLSSession()};
|
Database::Session& session{ _db.getTLSSession() };
|
||||||
|
|
||||||
context.maxDateTime = {}; // invalidate to break in case no more listens are fetched
|
context.maxDateTime = {}; // invalidate to break in case no more listens are fetched
|
||||||
ListensParser::Result result {ListensParser::parse(msgBody)};
|
ListensParser::Result result{ ListensParser::parse(msgBody) };
|
||||||
context.fetchedListenCount += result.listenCount;
|
context.fetchedListenCount += result.listenCount;
|
||||||
|
|
||||||
for (const Listen& parsedListen : result.listens)
|
for (const Listen& parsedListen : result.listens)
|
||||||
@@ -577,11 +558,11 @@ namespace Scrobbling::ListenBrainz
|
|||||||
if (!context.maxDateTime.isValid() || context.maxDateTime > parsedListen.listenedAt)
|
if (!context.maxDateTime.isValid() || context.maxDateTime > parsedListen.listenedAt)
|
||||||
context.maxDateTime = parsedListen.listenedAt;
|
context.maxDateTime = parsedListen.listenedAt;
|
||||||
|
|
||||||
if (const Database::TrackId trackId {tryGetMatchingTrack(session, parsedListen)}; trackId.isValid())
|
if (const Database::TrackId trackId{ tryGetMatchingTrack(session, parsedListen) }; trackId.isValid())
|
||||||
{
|
{
|
||||||
context.matchedListenCount++;
|
context.matchedListenCount++;
|
||||||
|
|
||||||
const Scrobbling::TimedListen listen {{context.userId, trackId}, parsedListen.listenedAt};
|
const Scrobbling::TimedListen listen{ {context.userId, trackId}, parsedListen.listenedAt };
|
||||||
if (saveListen(listen, Database::SyncState::Synchronized))
|
if (saveListen(listen, Database::SyncState::Synchronized))
|
||||||
context.importedListenCount++;
|
context.importedListenCount++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -204,16 +204,13 @@ namespace API::Subsonic
|
|||||||
if (!user)
|
if (!user)
|
||||||
throw UserNotAuthorizedError{};
|
throw UserNotAuthorizedError{};
|
||||||
|
|
||||||
const auto trackIds{ Track::find(context.dbSession, Track::FindParameters {}.setSortMethod(TrackSortMethod::Random).setRange({0, size})) };
|
const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setSortMethod(TrackSortMethod::Random).setRange({0, size})) };
|
||||||
|
|
||||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||||
|
|
||||||
Response::Node& randomSongsNode{ response.createNode("randomSongs") };
|
Response::Node& randomSongsNode{ response.createNode("randomSongs") };
|
||||||
for (const TrackId trackId : trackIds.results)
|
for (const Track::pointer& track : tracks.results)
|
||||||
{
|
|
||||||
const Track::pointer track{ Track::find(context.dbSession, trackId) };
|
|
||||||
randomSongsNode.addArrayChild("song", createSongNode(context, track, user));
|
randomSongsNode.addArrayChild("song", createSongNode(context, track, user));
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -250,12 +247,9 @@ namespace API::Subsonic
|
|||||||
params.setClusters({ cluster->getId() });
|
params.setClusters({ cluster->getId() });
|
||||||
params.setRange({ offset, size });
|
params.setRange({ offset, size });
|
||||||
|
|
||||||
auto trackIds{ Track::find(context.dbSession, params) };
|
const auto tracks{ Track::find(context.dbSession, params) };
|
||||||
for (const TrackId trackId : trackIds.results)
|
for (const Track::pointer& track : tracks.results)
|
||||||
{
|
|
||||||
const Track::pointer track{ Track::find(context.dbSession, trackId) };
|
|
||||||
songsByGenreNode.addArrayChild("song", createSongNode(context, track, user));
|
songsByGenreNode.addArrayChild("song", createSongNode(context, track, user));
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ namespace API::Subsonic
|
|||||||
params.setRange({ 0, meanTrackCountPerArtist });
|
params.setRange({ 0, meanTrackCountPerArtist });
|
||||||
params.setSortMethod(TrackSortMethod::Random);
|
params.setSortMethod(TrackSortMethod::Random);
|
||||||
|
|
||||||
const auto artistTracks{ Track::find(context.dbSession, params) };
|
const auto artistTracks{ Track::findIds(context.dbSession, params) };
|
||||||
tracks.insert(std::end(tracks),
|
tracks.insert(std::end(tracks),
|
||||||
std::begin(artistTracks.results),
|
std::begin(artistTracks.results),
|
||||||
std::end(artistTracks.results));
|
std::end(artistTracks.results));
|
||||||
@@ -225,7 +225,7 @@ namespace API::Subsonic
|
|||||||
params.setRange({ 0, meanTrackCountPerRelease });
|
params.setRange({ 0, meanTrackCountPerRelease });
|
||||||
params.setSortMethod(TrackSortMethod::Random);
|
params.setSortMethod(TrackSortMethod::Random);
|
||||||
|
|
||||||
const auto releaseTracks{ Track::find(context.dbSession, params) };
|
const auto releaseTracks{ Track::findIds(context.dbSession, params) };
|
||||||
tracks.insert(std::end(tracks),
|
tracks.insert(std::end(tracks),
|
||||||
std::begin(releaseTracks.results),
|
std::begin(releaseTracks.results),
|
||||||
std::end(releaseTracks.results));
|
std::end(releaseTracks.results));
|
||||||
@@ -349,12 +349,9 @@ namespace API::Subsonic
|
|||||||
directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(release->getName()));
|
directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(release->getName()));
|
||||||
|
|
||||||
const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release)) };
|
const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release)) };
|
||||||
for (const TrackId trackId : tracks.results)
|
for (const Track::pointer& track : tracks.results)
|
||||||
{
|
|
||||||
const Track::pointer track{ Track::find(context.dbSession, trackId) };
|
|
||||||
directoryNode.addArrayChild("child", createSongNode(context, track, user));
|
directoryNode.addArrayChild("child", createSongNode(context, track, user));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
throw BadParameterGenericError{ "id" };
|
throw BadParameterGenericError{ "id" };
|
||||||
|
|
||||||
@@ -432,11 +429,8 @@ namespace API::Subsonic
|
|||||||
Response::Node albumNode{ createAlbumNode(context, release, user, true /* id3 */) };
|
Response::Node albumNode{ createAlbumNode(context, release, user, true /* id3 */) };
|
||||||
|
|
||||||
const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setRelease(id).setSortMethod(TrackSortMethod::Release)) };
|
const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setRelease(id).setSortMethod(TrackSortMethod::Release)) };
|
||||||
for (const TrackId trackId : tracks.results)
|
for (const Track::pointer& track : tracks.results)
|
||||||
{
|
|
||||||
const Track::pointer track{ Track::find(context.dbSession, trackId) };
|
|
||||||
albumNode.addArrayChild("song", createSongNode(context, track, user));
|
albumNode.addArrayChild("song", createSongNode(context, track, user));
|
||||||
}
|
|
||||||
|
|
||||||
response.addNode("album", std::move(albumNode));
|
response.addNode("album", std::move(albumNode));
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,7 @@ namespace API::Subsonic
|
|||||||
params.setKeywords(keywords);
|
params.setKeywords(keywords);
|
||||||
params.setRange({ albumOffset, albumCount });
|
params.setRange({ albumOffset, albumCount });
|
||||||
|
|
||||||
RangeResults<Release::pointer> releases{ Release::find(context.dbSession, params) };
|
for (const Release::pointer& release : Release::find(context.dbSession, params).results)
|
||||||
for (const Release::pointer& release : releases.results)
|
|
||||||
searchResult2Node.addArrayChild("album", createAlbumNode(context, release, user, id3));
|
searchResult2Node.addArrayChild("album", createAlbumNode(context, release, user, id3));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,13 +96,9 @@ namespace API::Subsonic
|
|||||||
params.setKeywords(keywords);
|
params.setKeywords(keywords);
|
||||||
params.setRange({ songOffset, songCount });
|
params.setRange({ songOffset, songCount });
|
||||||
|
|
||||||
RangeResults<TrackId> trackIds{ Track::find(context.dbSession, params) };
|
for (const Track::pointer& track : Track::find(context.dbSession, params).results)
|
||||||
for (const TrackId trackId : trackIds.results)
|
|
||||||
{
|
|
||||||
const auto track{ Track::find(context.dbSession, trackId) };
|
|
||||||
searchResult2Node.addArrayChild("song", createSongNode(context, track, user));
|
searchResult2Node.addArrayChild("song", createSongNode(context, track, user));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ namespace API::Subsonic
|
|||||||
trackResponse.setAttribute("playCount", Listen::getCount(context.dbSession, user->getId(), user->getScrobblingBackend(), track->getId()));
|
trackResponse.setAttribute("playCount", Listen::getCount(context.dbSession, user->getId(), user->getScrobblingBackend(), track->getId()));
|
||||||
trackResponse.setAttribute("path", getTrackPath(track));
|
trackResponse.setAttribute("path", getTrackPath(track));
|
||||||
{
|
{
|
||||||
|
// TODO, store this in DB
|
||||||
std::error_code ec;
|
std::error_code ec;
|
||||||
const auto fileSize{ std::filesystem::file_size(track->getPath(), ec) };
|
const auto fileSize{ std::filesystem::file_size(track->getPath(), ec) };
|
||||||
if (!ec)
|
if (!ec)
|
||||||
|
|||||||
+206
-238
@@ -51,14 +51,14 @@
|
|||||||
#include "ModalManager.hpp"
|
#include "ModalManager.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
class CreateTrackListModel : public Wt::WFormModel
|
class CreateTrackListModel : public Wt::WFormModel
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static inline const Field NameField {"name"};
|
static inline const Field NameField{ "name" };
|
||||||
|
|
||||||
CreateTrackListModel()
|
CreateTrackListModel()
|
||||||
{
|
{
|
||||||
@@ -72,7 +72,7 @@ namespace
|
|||||||
class ReplaceTrackListModel : public Wt::WFormModel
|
class ReplaceTrackListModel : public Wt::WFormModel
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static inline const Field NameField {"name"};
|
static inline const Field NameField{ "name" };
|
||||||
using TrackListModel = ValueStringModel<Database::TrackListId>;
|
using TrackListModel = ValueStringModel<Database::TrackListId>;
|
||||||
|
|
||||||
ReplaceTrackListModel()
|
ReplaceTrackListModel()
|
||||||
@@ -83,7 +83,7 @@ namespace
|
|||||||
|
|
||||||
Database::TrackListId getTrackListId() const
|
Database::TrackListId getTrackListId() const
|
||||||
{
|
{
|
||||||
auto row {trackListModel->getRowFromString(valueText(NameField))};
|
auto row{ trackListModel->getRowFromString(valueText(NameField)) };
|
||||||
return trackListModel->getValue(*row);
|
return trackListModel->getValue(*row);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,45 +93,45 @@ namespace
|
|||||||
{
|
{
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
auto model {std::make_shared<TrackListModel>()};
|
auto model{ std::make_shared<TrackListModel>() };
|
||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
TrackList::FindParameters params;
|
TrackList::FindParameters params;
|
||||||
params.setType(TrackListType::Playlist);
|
params.setType(TrackListType::Playlist);
|
||||||
params.setUser(LmsApp->getUserId());
|
params.setUser(LmsApp->getUserId());
|
||||||
params.setSortMethod(TrackListSortMethod::Name);
|
params.setSortMethod(TrackListSortMethod::Name);
|
||||||
|
|
||||||
auto tracklists {TrackList::find(LmsApp->getDbSession(), params)};
|
auto tracklists{ TrackList::find(LmsApp->getDbSession(), params) };
|
||||||
for (const TrackListId trackListId : tracklists.results)
|
for (const TrackListId trackListId : tracklists.results)
|
||||||
{
|
{
|
||||||
const TrackList::pointer trackList {TrackList::find(LmsApp->getDbSession(), trackListId)};
|
const TrackList::pointer trackList{ TrackList::find(LmsApp->getDbSession(), trackListId) };
|
||||||
model->add(Wt::WString::fromUTF8(std::string {trackList->getName()}), trackListId);
|
model->add(Wt::WString::fromUTF8(std::string{ trackList->getName() }), trackListId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<TrackListModel> trackListModel {createTrackListModel()};
|
std::shared_ptr<TrackListModel> trackListModel{ createTrackListModel() };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayQueue::PlayQueue()
|
PlayQueue::PlayQueue()
|
||||||
: Template {Wt::WString::tr("Lms.PlayQueue.template")}
|
: Template{ Wt::WString::tr("Lms.PlayQueue.template") }
|
||||||
, _capacity {Service<IConfig>::get()->getULong("playqueue-max-entry-count", 1000)}
|
, _capacity{ Service<IConfig>::get()->getULong("playqueue-max-entry-count", 1000) }
|
||||||
{
|
{
|
||||||
initTrackLists();
|
initTrackLists();
|
||||||
|
|
||||||
addFunction("id", &Wt::WTemplate::Functions::id);
|
addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
|
||||||
Wt::WPushButton* clearBtn {bindNew<Wt::WPushButton>("clear-btn", Wt::WString::tr("Lms.PlayQueue.template.clear-btn"), Wt::TextFormat::XHTML)};
|
Wt::WPushButton* clearBtn{ bindNew<Wt::WPushButton>("clear-btn", Wt::WString::tr("Lms.PlayQueue.template.clear-btn"), Wt::TextFormat::XHTML) };
|
||||||
clearBtn->clicked().connect([=]
|
clearBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
clearTracks();
|
clearTracks();
|
||||||
});
|
});
|
||||||
|
|
||||||
Wt::WPushButton* saveBtn {bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr("Lms.PlayQueue.template.save-btn"), Wt::TextFormat::XHTML)};
|
Wt::WPushButton* saveBtn{ bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr("Lms.PlayQueue.template.save-btn"), Wt::TextFormat::XHTML) };
|
||||||
saveBtn->clicked().connect([=]
|
saveBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
saveAsTrackList();
|
saveAsTrackList();
|
||||||
@@ -144,14 +144,14 @@ PlayQueue::PlayQueue()
|
|||||||
updateCurrentTrack(true);
|
updateCurrentTrack(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
Wt::WPushButton* shuffleBtn {bindNew<Wt::WPushButton>("shuffle-btn", Wt::WString::tr("Lms.PlayQueue.template.shuffle-btn"), Wt::TextFormat::XHTML)};
|
Wt::WPushButton* shuffleBtn{ bindNew<Wt::WPushButton>("shuffle-btn", Wt::WString::tr("Lms.PlayQueue.template.shuffle-btn"), Wt::TextFormat::XHTML) };
|
||||||
shuffleBtn->clicked().connect([=]
|
shuffleBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
|
|
||||||
Database::TrackList::pointer queue {getQueue()};
|
Database::TrackList::pointer queue{ getQueue() };
|
||||||
auto entries {queue->getEntries()};
|
auto entries{ queue->getEntries() };
|
||||||
Random::shuffleContainer(entries);
|
Random::shuffleContainer(entries);
|
||||||
|
|
||||||
queue.modify()->clear();
|
queue.modify()->clear();
|
||||||
@@ -165,13 +165,13 @@ PlayQueue::PlayQueue()
|
|||||||
_repeatBtn = bindNew<Wt::WCheckBox>("repeat-btn");
|
_repeatBtn = bindNew<Wt::WCheckBox>("repeat-btn");
|
||||||
_repeatBtn->clicked().connect([=]
|
_repeatBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
|
|
||||||
if (!LmsApp->getUser()->isDemo())
|
if (!LmsApp->getUser()->isDemo())
|
||||||
LmsApp->getUser().modify()->setRepeatAll(isRepeatAllSet());
|
LmsApp->getUser().modify()->setRepeatAll(isRepeatAllSet());
|
||||||
});
|
});
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
if (LmsApp->getUser()->isRepeatAllSet())
|
if (LmsApp->getUser()->isRepeatAllSet())
|
||||||
_repeatBtn->setCheckState(Wt::CheckState::Checked);
|
_repeatBtn->setCheckState(Wt::CheckState::Checked);
|
||||||
}
|
}
|
||||||
@@ -180,7 +180,7 @@ PlayQueue::PlayQueue()
|
|||||||
_radioBtn->clicked().connect([=]
|
_radioBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
|
|
||||||
if (!LmsApp->getUser()->isDemo())
|
if (!LmsApp->getUser()->isDemo())
|
||||||
LmsApp->getUser().modify()->setRadio(isRadioModeSet());
|
LmsApp->getUser().modify()->setRadio(isRadioModeSet());
|
||||||
@@ -189,9 +189,9 @@ PlayQueue::PlayQueue()
|
|||||||
enqueueRadioTracksIfNeeded();
|
enqueueRadioTracksIfNeeded();
|
||||||
});
|
});
|
||||||
|
|
||||||
bool isRadioModeSet {};
|
bool isRadioModeSet{};
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
isRadioModeSet = LmsApp->getUser()->isRadioSet();
|
isRadioModeSet = LmsApp->getUser()->isRadioSet();
|
||||||
}
|
}
|
||||||
if (isRadioModeSet)
|
if (isRadioModeSet)
|
||||||
@@ -210,10 +210,10 @@ PlayQueue::PlayQueue()
|
|||||||
|
|
||||||
_mediaPlayerSettingsLoaded = true;
|
_mediaPlayerSettingsLoaded = true;
|
||||||
|
|
||||||
std::size_t trackPos {};
|
std::size_t trackPos{};
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
trackPos = LmsApp->getUser()->getCurPlayingTrackPos();
|
trackPos = LmsApp->getUser()->getCurPlayingTrackPos();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,77 +222,70 @@ PlayQueue::PlayQueue()
|
|||||||
|
|
||||||
LmsApp->preQuit().connect([=]
|
LmsApp->preQuit().connect([=]
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
|
|
||||||
if (LmsApp->getUser()->isDemo())
|
if (LmsApp->getUser()->isDemo())
|
||||||
{
|
{
|
||||||
LMS_LOG(UI, DEBUG) << "Removing queue (tracklist id " << _queueId.toString() << ")";
|
LMS_LOG(UI, DEBUG) << "Removing queue (tracklist id " << _queueId.toString() << ")";
|
||||||
if (Database::TrackList::pointer queue {getQueue()})
|
if (Database::TrackList::pointer queue{ getQueue() })
|
||||||
queue.remove();
|
queue.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
updateInfo();
|
updateInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool PlayQueue::isRepeatAllSet() const
|
||||||
PlayQueue::isRepeatAllSet() const
|
|
||||||
{
|
|
||||||
return _repeatBtn->checkState() == Wt::CheckState::Checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
PlayQueue::isRadioModeSet() const
|
|
||||||
{
|
|
||||||
return _radioBtn->checkState() == Wt::CheckState::Checked;
|
|
||||||
}
|
|
||||||
|
|
||||||
Database::TrackList::pointer
|
|
||||||
PlayQueue::getQueue() const
|
|
||||||
{
|
|
||||||
return Database::TrackList::find(LmsApp->getDbSession(), _queueId);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
PlayQueue::isFull() const
|
|
||||||
{
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
return getQueue()->getCount() == getCapacity();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
PlayQueue::clearTracks()
|
|
||||||
{
|
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
return _repeatBtn->checkState() == Wt::CheckState::Checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayQueue::isRadioModeSet() const
|
||||||
|
{
|
||||||
|
return _radioBtn->checkState() == Wt::CheckState::Checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
Database::TrackList::pointer PlayQueue::getQueue() const
|
||||||
|
{
|
||||||
|
return Database::TrackList::find(LmsApp->getDbSession(), _queueId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PlayQueue::isFull() const
|
||||||
|
{
|
||||||
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
return getQueue()->getCount() == getCapacity();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PlayQueue::clearTracks()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
getQueue().modify()->clear();
|
getQueue().modify()->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
_entriesContainer->reset();
|
_entriesContainer->reset();
|
||||||
_trackPos.reset();
|
_trackPos.reset();
|
||||||
updateInfo();
|
updateInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::stop()
|
||||||
PlayQueue::stop()
|
{
|
||||||
{
|
|
||||||
updateCurrentTrack(false);
|
updateCurrentTrack(false);
|
||||||
_trackPos.reset();
|
_trackPos.reset();
|
||||||
_isTrackSelected = false;
|
_isTrackSelected = false;
|
||||||
trackUnselected.emit();
|
trackUnselected.emit();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::loadTrack(std::size_t pos, bool play)
|
||||||
PlayQueue::loadTrack(std::size_t pos, bool play)
|
{
|
||||||
{
|
|
||||||
updateCurrentTrack(false);
|
updateCurrentTrack(false);
|
||||||
|
|
||||||
Database::TrackId trackId {};
|
Database::TrackId trackId{};
|
||||||
std::optional<float> replayGain {};
|
std::optional<float> replayGain{};
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
const Database::TrackList::pointer queue {getQueue()};
|
const Database::TrackList::pointer queue{ getQueue() };
|
||||||
|
|
||||||
// If out of range, stop playing
|
// If out of range, stop playing
|
||||||
if (pos >= queue->getCount())
|
if (pos >= queue->getCount())
|
||||||
@@ -307,7 +300,7 @@ PlayQueue::loadTrack(std::size_t pos, bool play)
|
|||||||
}
|
}
|
||||||
|
|
||||||
_trackPos = pos;
|
_trackPos = pos;
|
||||||
const Database::Track::pointer track {queue->getEntry(*_trackPos)->getTrack()};
|
const Database::Track::pointer track{ queue->getEntry(*_trackPos)->getTrack() };
|
||||||
|
|
||||||
trackId = track->getId();
|
trackId = track->getId();
|
||||||
|
|
||||||
@@ -321,11 +314,10 @@ PlayQueue::loadTrack(std::size_t pos, bool play)
|
|||||||
updateCurrentTrack(true);
|
updateCurrentTrack(true);
|
||||||
_isTrackSelected = true;
|
_isTrackSelected = true;
|
||||||
trackSelected.emit(trackId, play, replayGain ? *replayGain : 0);
|
trackSelected.emit(trackId, play, replayGain ? *replayGain : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::playPrevious()
|
||||||
PlayQueue::playPrevious()
|
{
|
||||||
{
|
|
||||||
if (!_trackPos)
|
if (!_trackPos)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -333,11 +325,10 @@ PlayQueue::playPrevious()
|
|||||||
stop();
|
stop();
|
||||||
else
|
else
|
||||||
loadTrack(*_trackPos - 1, true);
|
loadTrack(*_trackPos - 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::playNext()
|
||||||
PlayQueue::playNext()
|
{
|
||||||
{
|
|
||||||
if (!_trackPos)
|
if (!_trackPos)
|
||||||
{
|
{
|
||||||
loadTrack(0, true);
|
loadTrack(0, true);
|
||||||
@@ -345,86 +336,80 @@ PlayQueue::playNext()
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadTrack(*_trackPos + 1, true);
|
loadTrack(*_trackPos + 1, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::onPlaybackEnded()
|
||||||
PlayQueue::onPlaybackEnded()
|
{
|
||||||
{
|
|
||||||
playNext();
|
playNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t
|
std::size_t PlayQueue::getCount()
|
||||||
PlayQueue::getCount()
|
{
|
||||||
{
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
return getQueue()->getCount();
|
return getQueue()->getCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::initTrackLists()
|
||||||
PlayQueue::initTrackLists()
|
{
|
||||||
{
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
|
||||||
|
|
||||||
Database::TrackList::pointer queue;
|
Database::TrackList::pointer queue;
|
||||||
Database::TrackList::pointer radioStartingTracks;
|
Database::TrackList::pointer radioStartingTracks;
|
||||||
|
|
||||||
if (!LmsApp->getUser()->isDemo())
|
if (!LmsApp->getUser()->isDemo())
|
||||||
{
|
{
|
||||||
static const std::string queueName {"__queued_tracks__"};
|
static const std::string queueName{ "__queued_tracks__" };
|
||||||
queue = Database::TrackList::find(LmsApp->getDbSession(), queueName, Database::TrackListType::Internal, LmsApp->getUserId());
|
queue = Database::TrackList::find(LmsApp->getDbSession(), queueName, Database::TrackListType::Internal, LmsApp->getUserId());
|
||||||
if (!queue)
|
if (!queue)
|
||||||
queue = LmsApp->getDbSession().create<Database::TrackList>(queueName, Database::TrackListType::Internal, false, LmsApp->getUser());
|
queue = LmsApp->getDbSession().create<Database::TrackList>(queueName, Database::TrackListType::Internal, false, LmsApp->getUser());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
static const std::string queueName {"__temp_queue__"};
|
static const std::string queueName{ "__temp_queue__" };
|
||||||
queue = LmsApp->getDbSession().create<Database::TrackList>(queueName, Database::TrackListType::Internal, false, LmsApp->getUser());
|
queue = LmsApp->getDbSession().create<Database::TrackList>(queueName, Database::TrackListType::Internal, false, LmsApp->getUser());
|
||||||
}
|
}
|
||||||
|
|
||||||
_queueId = queue->getId();
|
_queueId = queue->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::updateInfo()
|
||||||
PlayQueue::updateInfo()
|
{
|
||||||
{
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
|
|
||||||
const Database::TrackList::pointer queue {getQueue()};
|
const Database::TrackList::pointer queue{ getQueue() };
|
||||||
const std::size_t trackCount {queue->getCount()};
|
const std::size_t trackCount{ queue->getCount() };
|
||||||
_nbTracks->setText(Wt::WString::trn("Lms.track-count", trackCount).arg(trackCount));
|
_nbTracks->setText(Wt::WString::trn("Lms.track-count", trackCount).arg(trackCount));
|
||||||
_duration->setText(Utils::durationToString(queue->getDuration()));
|
_duration->setText(Utils::durationToString(queue->getDuration()));
|
||||||
trackCountChanged.emit(trackCount);
|
trackCountChanged.emit(trackCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::updateCurrentTrack(bool selected)
|
||||||
PlayQueue::updateCurrentTrack(bool selected)
|
{
|
||||||
{
|
|
||||||
if (!_trackPos || *_trackPos >= static_cast<std::size_t>(_entriesContainer->getCount()))
|
if (!_trackPos || *_trackPos >= static_cast<std::size_t>(_entriesContainer->getCount()))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Template* entry {static_cast<Template*>(_entriesContainer->getWidget(*_trackPos))};
|
Template* entry{ static_cast<Template*>(_entriesContainer->getWidget(*_trackPos)) };
|
||||||
if (!entry)
|
if (!entry)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
entry->toggleStyleClass("Lms-entry-playing", selected);
|
entry->toggleStyleClass("Lms-entry-playing", selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::enqueueTracks(const std::vector<Database::TrackId>& trackIds)
|
||||||
PlayQueue::enqueueTracks(const std::vector<Database::TrackId>& trackIds)
|
|
||||||
{
|
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
{
|
||||||
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
|
|
||||||
Database::TrackList::pointer queue {getQueue()};
|
Database::TrackList::pointer queue{ getQueue() };
|
||||||
const std::size_t queueSize {queue->getCount()};
|
const std::size_t queueSize{ queue->getCount() };
|
||||||
|
|
||||||
std::size_t nbTracksToEnqueue {queueSize + trackIds.size() > getCapacity() ? getCapacity() - queueSize : trackIds.size()};
|
std::size_t nbTracksToEnqueue{ queueSize + trackIds.size() > getCapacity() ? getCapacity() - queueSize : trackIds.size() };
|
||||||
for (const Database::TrackId trackId : trackIds)
|
for (const Database::TrackId trackId : trackIds)
|
||||||
{
|
{
|
||||||
if (nbTracksToEnqueue == 0)
|
if (nbTracksToEnqueue == 0)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
Database::Track::pointer track {Database::Track::find(LmsApp->getDbSession(), trackId)};
|
Database::Track::pointer track{ Database::Track::find(LmsApp->getDbSession(), trackId) };
|
||||||
if (!track)
|
if (!track)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -436,17 +421,16 @@ PlayQueue::enqueueTracks(const std::vector<Database::TrackId>& trackIds)
|
|||||||
updateInfo();
|
updateInfo();
|
||||||
addSome();
|
addSome();
|
||||||
_entriesContainer->setHasMore();
|
_entriesContainer->setHasMore();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Database::TrackId>
|
std::vector<Database::TrackId> PlayQueue::getAndClearNextTracks()
|
||||||
PlayQueue::getAndClearNextTracks()
|
{
|
||||||
{
|
|
||||||
std::vector<Database::TrackId> tracks;
|
std::vector<Database::TrackId> tracks;
|
||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
|
|
||||||
Database::TrackList::pointer queue {getQueue()};
|
Database::TrackList::pointer queue{ getQueue() };
|
||||||
std::vector<Database::TrackListEntry::pointer> entries {queue->getEntries(Database::Range {_trackPos ? *_trackPos + 1 : 0, getCapacity()})};
|
std::vector<Database::TrackListEntry::pointer> entries{ queue->getEntries(Database::Range {_trackPos ? *_trackPos + 1 : 0, getCapacity()}) };
|
||||||
tracks.reserve(entries.size());
|
tracks.reserve(entries.size());
|
||||||
for (Database::TrackListEntry::pointer entry : entries)
|
for (Database::TrackListEntry::pointer entry : entries)
|
||||||
{
|
{
|
||||||
@@ -466,72 +450,65 @@ PlayQueue::getAndClearNextTracks()
|
|||||||
}
|
}
|
||||||
|
|
||||||
return tracks;
|
return tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::play(const std::vector<Database::TrackId>& trackIds)
|
||||||
PlayQueue::play(const std::vector<Database::TrackId>& trackIds)
|
{
|
||||||
{
|
|
||||||
playAtIndex(trackIds, 0);
|
playAtIndex(trackIds, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::playNext(const std::vector<Database::TrackId>& trackIds)
|
||||||
PlayQueue::playNext(const std::vector<Database::TrackId>& trackIds)
|
{
|
||||||
{
|
std::vector<Database::TrackId> nextTracks{ getAndClearNextTracks() };
|
||||||
std::vector<Database::TrackId> nextTracks {getAndClearNextTracks()};
|
|
||||||
nextTracks.insert(std::cbegin(nextTracks), std::cbegin(trackIds), std::cend(trackIds));
|
nextTracks.insert(std::cbegin(nextTracks), std::cbegin(trackIds), std::cend(trackIds));
|
||||||
playOrAddLast(nextTracks);
|
playOrAddLast(nextTracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::playShuffled(const std::vector<Database::TrackId>& trackIds)
|
||||||
PlayQueue::playShuffled(const std::vector<Database::TrackId>& trackIds)
|
{
|
||||||
{
|
|
||||||
clearTracks();
|
clearTracks();
|
||||||
std::vector<Database::TrackId> shuffledTrackIds {trackIds};
|
std::vector<Database::TrackId> shuffledTrackIds{ trackIds };
|
||||||
Random::shuffleContainer(shuffledTrackIds);
|
Random::shuffleContainer(shuffledTrackIds);
|
||||||
enqueueTracks(shuffledTrackIds);
|
enqueueTracks(shuffledTrackIds);
|
||||||
loadTrack(0, true);
|
loadTrack(0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::playOrAddLast(const std::vector<Database::TrackId>& trackIds)
|
||||||
PlayQueue::playOrAddLast(const std::vector<Database::TrackId>& trackIds)
|
{
|
||||||
{
|
|
||||||
enqueueTracks(trackIds);
|
enqueueTracks(trackIds);
|
||||||
if (!_isTrackSelected)
|
if (!_isTrackSelected)
|
||||||
loadTrack(0, true);
|
loadTrack(0, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::playAtIndex(const std::vector<Database::TrackId>& trackIds, std::size_t index)
|
||||||
PlayQueue::playAtIndex(const std::vector<Database::TrackId>& trackIds, std::size_t index)
|
{
|
||||||
{
|
|
||||||
clearTracks();
|
clearTracks();
|
||||||
enqueueTracks(trackIds);
|
enqueueTracks(trackIds);
|
||||||
loadTrack(index, true);
|
loadTrack(index, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::addSome()
|
||||||
PlayQueue::addSome()
|
{
|
||||||
{
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
|
|
||||||
const Database::TrackList::pointer queue {getQueue()};
|
const Database::TrackList::pointer queue{ getQueue() };
|
||||||
const auto tracklistEntries {queue->getEntries(Database::Range {_entriesContainer->getCount(), _batchSize})};
|
const auto tracklistEntries{ queue->getEntries(Database::Range {_entriesContainer->getCount(), _batchSize}) };
|
||||||
for (const Database::TrackListEntry::pointer& tracklistEntry : tracklistEntries)
|
for (const Database::TrackListEntry::pointer& tracklistEntry : tracklistEntries)
|
||||||
addEntry(tracklistEntry);
|
addEntry(tracklistEntry);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
|
||||||
PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
|
{
|
||||||
{
|
const Database::TrackListEntryId tracklistEntryId{ tracklistEntry->getId() };
|
||||||
const Database::TrackListEntryId tracklistEntryId {tracklistEntry->getId()};
|
const auto track{ tracklistEntry->getTrack() };
|
||||||
const auto track {tracklistEntry->getTrack()};
|
const Database::TrackId trackId{ track->getId() };
|
||||||
const Database::TrackId trackId {track->getId()};
|
|
||||||
|
|
||||||
Template* entry {_entriesContainer->addNew<Template>(Wt::WString::tr("Lms.PlayQueue.template.entry"))};
|
Template* entry{ _entriesContainer->addNew<Template>(Wt::WString::tr("Lms.PlayQueue.template.entry")) };
|
||||||
entry->addFunction("id", &Wt::WTemplate::Functions::id);
|
entry->addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
|
|
||||||
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
const auto artists {track->getArtistIds({Database::TrackArtistLinkType::Artist})};
|
const auto artists{ track->getArtistIds({Database::TrackArtistLinkType::Artist}) };
|
||||||
if (!artists.empty())
|
if (!artists.empty())
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-artists", true);
|
entry->setCondition("if-has-artists", true);
|
||||||
@@ -539,49 +516,49 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
|
|||||||
entry->bindWidget("artists-md", Utils::createArtistAnchorList(artists));
|
entry->bindWidget("artists-md", Utils::createArtistAnchorList(artists));
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto release {track->getRelease()};
|
const auto release{ track->getRelease() };
|
||||||
if (release)
|
if (release)
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-release", true);
|
entry->setCondition("if-has-release", true);
|
||||||
entry->bindWidget("release", Utils::createReleaseAnchor(release));
|
entry->bindWidget("release", Utils::createReleaseAnchor(release));
|
||||||
{
|
{
|
||||||
Wt::WAnchor* anchor {entry->bindWidget("cover", Utils::createReleaseAnchor(release, false))};
|
Wt::WAnchor* anchor{ entry->bindWidget("cover", Utils::createReleaseAnchor(release, false)) };
|
||||||
auto cover {Utils::createCover(release->getId(), CoverResource::Size::Small)};
|
auto cover{ Utils::createCover(release->getId(), CoverResource::Size::Small) };
|
||||||
cover->addStyleClass("Lms-cover-track Lms-cover-anchor"); // HACK
|
cover->addStyleClass("Lms-cover-track Lms-cover-anchor"); // HACK
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(cover));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto cover {entry->bindWidget<Wt::WImage>("cover", Utils::createCover(track->getId(), CoverResource::Size::Small))};
|
auto cover{ entry->bindWidget<Wt::WImage>("cover", Utils::createCover(track->getId(), CoverResource::Size::Small)) };
|
||||||
cover->addStyleClass("Lms-cover-track");
|
cover->addStyleClass("Lms-cover-track");
|
||||||
}
|
}
|
||||||
|
|
||||||
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
Wt::WPushButton* playBtn {entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML)};
|
Wt::WPushButton* playBtn{ entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) };
|
||||||
playBtn->clicked().connect([=]
|
playBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
const std::optional<std::size_t> pos {_entriesContainer->getIndexOf(*entry)};
|
const std::optional<std::size_t> pos{ _entriesContainer->getIndexOf(*entry) };
|
||||||
if (pos)
|
if (pos)
|
||||||
loadTrack(*pos, true);
|
loadTrack(*pos, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
Wt::WPushButton* delBtn {entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.template.delete-btn"), Wt::TextFormat::XHTML)};
|
Wt::WPushButton* delBtn{ entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.template.delete-btn"), Wt::TextFormat::XHTML) };
|
||||||
delBtn->setToolTip(Wt::WString::tr("Lms.delete"));
|
delBtn->setToolTip(Wt::WString::tr("Lms.delete"));
|
||||||
delBtn->clicked().connect([=]
|
delBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
// Remove the entry n both the widget tree and the playqueue
|
// Remove the entry n both the widget tree and the playqueue
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
|
|
||||||
Database::TrackListEntry::pointer entryToRemove {Database::TrackListEntry::getById(LmsApp->getDbSession(), tracklistEntryId)};
|
Database::TrackListEntry::pointer entryToRemove{ Database::TrackListEntry::getById(LmsApp->getDbSession(), tracklistEntryId) };
|
||||||
entryToRemove.remove();
|
entryToRemove.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_trackPos)
|
if (_trackPos)
|
||||||
{
|
{
|
||||||
const std::optional<std::size_t> pos {_entriesContainer->getIndexOf(*entry)};
|
const std::optional<std::size_t> pos{ _entriesContainer->getIndexOf(*entry) };
|
||||||
if (pos && *_trackPos >= *pos)
|
if (pos && *_trackPos >= *pos)
|
||||||
(*_trackPos)--;
|
(*_trackPos)--;
|
||||||
else if (*_trackPos >= _entriesContainer->getCount())
|
else if (*_trackPos >= _entriesContainer->getCount())
|
||||||
@@ -597,17 +574,17 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
|
|||||||
entry->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play"))
|
entry->bindNew<Wt::WPushButton>("play", Wt::WString::tr("Lms.Explore.play"))
|
||||||
->clicked().connect([=]
|
->clicked().connect([=]
|
||||||
{
|
{
|
||||||
const std::optional<std::size_t> pos {_entriesContainer->getIndexOf(*entry)};
|
const std::optional<std::size_t> pos{ _entriesContainer->getIndexOf(*entry) };
|
||||||
if (pos)
|
if (pos)
|
||||||
loadTrack(*pos, true);
|
loadTrack(*pos, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
auto isStarred {[=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), trackId); }};
|
auto isStarred{ [=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), trackId); } };
|
||||||
|
|
||||||
Wt::WPushButton* starBtn {entry->bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star"))};
|
Wt::WPushButton* starBtn{ entry->bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) };
|
||||||
starBtn->clicked().connect([=]
|
starBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
|
|
||||||
if (isStarred())
|
if (isStarred())
|
||||||
{
|
{
|
||||||
@@ -622,20 +599,19 @@ PlayQueue::addEntry(const Database::TrackListEntry::pointer& tracklistEntry)
|
|||||||
});
|
});
|
||||||
|
|
||||||
entry->bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
entry->bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
||||||
->setLink(Wt::WLink {std::make_unique<DownloadTrackResource>(trackId)});
|
->setLink(Wt::WLink{ std::make_unique<DownloadTrackResource>(trackId) });
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::enqueueRadioTracksIfNeeded()
|
||||||
PlayQueue::enqueueRadioTracksIfNeeded()
|
{
|
||||||
{
|
|
||||||
if (!isRadioModeSet())
|
if (!isRadioModeSet())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
bool addTracks {};
|
bool addTracks{};
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
const Database::TrackList::pointer queue {getQueue()};
|
const Database::TrackList::pointer queue{ getQueue() };
|
||||||
|
|
||||||
// If out of range, stop playing
|
// If out of range, stop playing
|
||||||
if (_trackPos >= queue->getCount() - 1)
|
if (_trackPos >= queue->getCount() - 1)
|
||||||
@@ -644,19 +620,17 @@ PlayQueue::enqueueRadioTracksIfNeeded()
|
|||||||
|
|
||||||
if (addTracks)
|
if (addTracks)
|
||||||
enqueueRadioTracks();
|
enqueueRadioTracks();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::enqueueRadioTracks()
|
||||||
PlayQueue::enqueueRadioTracks()
|
{
|
||||||
{
|
|
||||||
std::vector<Database::TrackId> trackIds = Service<Recommendation::IPlaylistGeneratorService>::get()->extendPlaylist(_queueId, 15);
|
std::vector<Database::TrackId> trackIds = Service<Recommendation::IPlaylistGeneratorService>::get()->extendPlaylist(_queueId, 15);
|
||||||
enqueueTracks(trackIds);
|
enqueueTracks(trackIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<float>
|
std::optional<float> PlayQueue::getReplayGain(std::size_t pos, const Database::Track::pointer& track) const
|
||||||
PlayQueue::getReplayGain(std::size_t pos, const Database::Track::pointer& track) const
|
{
|
||||||
{
|
const auto& settings{ LmsApp->getMediaPlayer().getSettings() };
|
||||||
const auto& settings {LmsApp->getMediaPlayer().getSettings()};
|
|
||||||
if (!settings)
|
if (!settings)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
@@ -679,11 +653,11 @@ PlayQueue::getReplayGain(std::size_t pos, const Database::Track::pointer& track)
|
|||||||
|
|
||||||
case MediaPlayer::Settings::ReplayGain::Mode::Auto:
|
case MediaPlayer::Settings::ReplayGain::Mode::Auto:
|
||||||
{
|
{
|
||||||
const Database::TrackList::pointer queue {getQueue()};
|
const Database::TrackList::pointer queue{ getQueue() };
|
||||||
const Database::TrackListEntry::pointer prevEntry {pos > 0 ? queue->getEntry(pos - 1) : Database::TrackListEntry::pointer {}};
|
const Database::TrackListEntry::pointer prevEntry{ pos > 0 ? queue->getEntry(pos - 1) : Database::TrackListEntry::pointer {} };
|
||||||
const Database::TrackListEntry::pointer nextEntry {queue->getEntry(pos + 1)};
|
const Database::TrackListEntry::pointer nextEntry{ queue->getEntry(pos + 1) };
|
||||||
const Database::Track::pointer prevTrack {prevEntry ? prevEntry->getTrack() : Database::Track::pointer {}};
|
const Database::Track::pointer prevTrack{ prevEntry ? prevEntry->getTrack() : Database::Track::pointer {} };
|
||||||
const Database::Track::pointer nextTrack {nextEntry ? nextEntry->getTrack() : Database::Track::pointer {}};
|
const Database::Track::pointer nextTrack{ nextEntry ? nextEntry->getTrack() : Database::Track::pointer {} };
|
||||||
|
|
||||||
if ((prevTrack && prevTrack->getRelease() && prevTrack->getRelease() == track->getRelease())
|
if ((prevTrack && prevTrack->getRelease() && prevTrack->getRelease() == track->getRelease())
|
||||||
||
|
||
|
||||||
@@ -705,23 +679,22 @@ PlayQueue::getReplayGain(std::size_t pos, const Database::Track::pointer& track)
|
|||||||
return *gain + settings->replayGain.preAmpGain;
|
return *gain + settings->replayGain.preAmpGain;
|
||||||
|
|
||||||
return settings->replayGain.preAmpGainIfNoInfo;
|
return settings->replayGain.preAmpGainIfNoInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::saveAsTrackList()
|
||||||
PlayQueue::saveAsTrackList()
|
{
|
||||||
{
|
auto modal{ std::make_unique<Template>(Wt::WString::tr("Lms.PlayQueue.template.save-as-tracklist")) };
|
||||||
auto modal {std::make_unique<Template>(Wt::WString::tr("Lms.PlayQueue.template.save-as-tracklist"))};
|
|
||||||
modal->addFunction("id", &Wt::WTemplate::Functions::id);
|
modal->addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
Wt::WWidget* modalPtr {modal.get()};
|
Wt::WWidget* modalPtr{ modal.get() };
|
||||||
|
|
||||||
auto* cancelBtn {modal->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel"))};
|
auto* cancelBtn{ modal->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel")) };
|
||||||
cancelBtn->clicked().connect([=]
|
cancelBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
LmsApp->getModalManager().dispose(modalPtr);
|
LmsApp->getModalManager().dispose(modalPtr);
|
||||||
});
|
});
|
||||||
|
|
||||||
Wt::WStackedWidget* contentStack {modal->bindNew<Wt::WStackedWidget>("contents")};
|
Wt::WStackedWidget* contentStack{ modal->bindNew<Wt::WStackedWidget>("contents") };
|
||||||
|
|
||||||
// Create/Replace selector
|
// Create/Replace selector
|
||||||
enum Index : int
|
enum Index : int
|
||||||
@@ -736,7 +709,7 @@ PlayQueue::saveAsTrackList()
|
|||||||
contentStack->setCurrentIndex(Index::ReplaceTrackList);
|
contentStack->setCurrentIndex(Index::ReplaceTrackList);
|
||||||
});
|
});
|
||||||
|
|
||||||
Wt::WRadioButton* createTrackListBtn {modal->bindNew<Wt::WRadioButton>("create-tracklist-btn")};
|
Wt::WRadioButton* createTrackListBtn{ modal->bindNew<Wt::WRadioButton>("create-tracklist-btn") };
|
||||||
createTrackListBtn->setChecked();
|
createTrackListBtn->setChecked();
|
||||||
createTrackListBtn->clicked().connect([=]
|
createTrackListBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
@@ -746,25 +719,25 @@ PlayQueue::saveAsTrackList()
|
|||||||
|
|
||||||
|
|
||||||
// Create TrackList
|
// Create TrackList
|
||||||
Wt::WTemplateFormView* createTrackList {contentStack->addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.PlayQueue.template.save-as-tracklist.create-tracklist"))};
|
Wt::WTemplateFormView* createTrackList{ contentStack->addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.PlayQueue.template.save-as-tracklist.create-tracklist")) };
|
||||||
auto createTrackListModel {std::make_shared<CreateTrackListModel>()};
|
auto createTrackListModel{ std::make_shared<CreateTrackListModel>() };
|
||||||
createTrackList->setFormWidget(CreateTrackListModel::NameField, std::make_unique<Wt::WLineEdit>());
|
createTrackList->setFormWidget(CreateTrackListModel::NameField, std::make_unique<Wt::WLineEdit>());
|
||||||
createTrackList->updateView(createTrackListModel.get());
|
createTrackList->updateView(createTrackListModel.get());
|
||||||
|
|
||||||
// Replace TrackList
|
// Replace TrackList
|
||||||
Wt::WTemplateFormView* replaceTrackList {contentStack->addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.PlayQueue.template.save-as-tracklist.replace-tracklist"))};
|
Wt::WTemplateFormView* replaceTrackList{ contentStack->addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.PlayQueue.template.save-as-tracklist.replace-tracklist")) };
|
||||||
auto replaceTrackListModel {std::make_shared<ReplaceTrackListModel>()};
|
auto replaceTrackListModel{ std::make_shared<ReplaceTrackListModel>() };
|
||||||
{
|
{
|
||||||
auto name {std::make_unique<Wt::WComboBox>()};
|
auto name{ std::make_unique<Wt::WComboBox>() };
|
||||||
name->setModel(replaceTrackListModel->trackListModel);
|
name->setModel(replaceTrackListModel->trackListModel);
|
||||||
replaceTrackList->setFormWidget(ReplaceTrackListModel::NameField, std::move(name));
|
replaceTrackList->setFormWidget(ReplaceTrackListModel::NameField, std::move(name));
|
||||||
}
|
}
|
||||||
replaceTrackList->updateView(replaceTrackListModel.get());
|
replaceTrackList->updateView(replaceTrackListModel.get());
|
||||||
|
|
||||||
auto* saveBtn {modal->bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr("Lms.save"))};
|
auto* saveBtn{ modal->bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr("Lms.save")) };
|
||||||
saveBtn->clicked().connect([=]
|
saveBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
bool success {};
|
bool success{};
|
||||||
switch (contentStack->currentIndex())
|
switch (contentStack->currentIndex())
|
||||||
{
|
{
|
||||||
case Index::CreateTrackList:
|
case Index::CreateTrackList:
|
||||||
@@ -793,34 +766,32 @@ PlayQueue::saveAsTrackList()
|
|||||||
});
|
});
|
||||||
|
|
||||||
LmsApp->getModalManager().show(std::move(modal));
|
LmsApp->getModalManager().show(std::move(modal));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::exportToNewTrackList(const Wt::WString& name)
|
||||||
PlayQueue::exportToNewTrackList(const Wt::WString& name)
|
{
|
||||||
{
|
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
Database::TrackListId trackListId;
|
Database::TrackListId trackListId;
|
||||||
|
|
||||||
{
|
{
|
||||||
Session& session {LmsApp->getDbSession()};
|
Session& session{ LmsApp->getDbSession() };
|
||||||
auto transaction {session.createUniqueTransaction()};
|
auto transaction{ session.createUniqueTransaction() };
|
||||||
TrackList::pointer trackList {session.create<TrackList>(name.toUTF8(), TrackListType::Playlist, false, LmsApp->getUser())};
|
TrackList::pointer trackList{ session.create<TrackList>(name.toUTF8(), TrackListType::Playlist, false, LmsApp->getUser()) };
|
||||||
trackListId = trackList->getId();
|
trackListId = trackList->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
exportToTrackList(trackListId);
|
exportToTrackList(trackListId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueue::exportToTrackList(Database::TrackListId trackListId)
|
||||||
PlayQueue::exportToTrackList(Database::TrackListId trackListId)
|
{
|
||||||
{
|
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
Session& session {LmsApp->getDbSession()};
|
Session& session{ LmsApp->getDbSession() };
|
||||||
auto transaction {session.createUniqueTransaction()};
|
auto transaction{ session.createUniqueTransaction() };
|
||||||
|
|
||||||
TrackList::pointer trackList {TrackList::find(LmsApp->getDbSession(), trackListId)};
|
TrackList::pointer trackList{ TrackList::find(LmsApp->getDbSession(), trackListId) };
|
||||||
trackList.modify()->clear();
|
trackList.modify()->clear();
|
||||||
|
|
||||||
Track::FindParameters params;
|
Track::FindParameters params;
|
||||||
@@ -828,11 +799,8 @@ PlayQueue::exportToTrackList(Database::TrackListId trackListId)
|
|||||||
params.setDistinct(false);
|
params.setDistinct(false);
|
||||||
params.setSortMethod(TrackSortMethod::TrackList);
|
params.setSortMethod(TrackSortMethod::TrackList);
|
||||||
|
|
||||||
const auto tracks {Track::find(session, params)};
|
const auto tracks{ Track::find(session, params) };
|
||||||
for (const TrackId trackId : tracks.results)
|
for (const Track::pointer& track : tracks.results)
|
||||||
session.create<TrackListEntry>(Track::find(session, trackId), trackList);
|
session.create<TrackListEntry>(track, trackList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -369,12 +369,12 @@ Artist::addSomeNonReleaseTracks()
|
|||||||
params.setNonRelease(true);
|
params.setNonRelease(true);
|
||||||
|
|
||||||
const auto tracks {Track::find(LmsApp->getDbSession(), params)};
|
const auto tracks {Track::find(LmsApp->getDbSession(), params)};
|
||||||
for (const TrackId trackId : tracks.results)
|
for (const Track::pointer& track : tracks.results)
|
||||||
{
|
{
|
||||||
|
// TODO handle this with range
|
||||||
if (_trackContainer->getCount() == _tracksMaxCount)
|
if (_trackContainer->getCount() == _tracksMaxCount)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)};
|
|
||||||
_trackContainer->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
_trackContainer->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
||||||
|
|
||||||
areTracksAdded = true;
|
areTracksAdded = true;
|
||||||
|
|||||||
@@ -29,15 +29,15 @@
|
|||||||
namespace UserInterface
|
namespace UserInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
static
|
namespace
|
||||||
std::vector<Database::TrackId>
|
{
|
||||||
getArtistsTracks(Database::Session& session, const std::vector<Database::ArtistId>& artistsId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
std::vector<Database::TrackId> getArtistsTracks(Database::Session& session, const std::vector<Database::ArtistId>& artistsId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
||||||
{
|
{
|
||||||
assert(maxTrackCount);
|
assert(maxTrackCount);
|
||||||
|
|
||||||
std::vector<Database::TrackId> res;
|
std::vector<Database::TrackId> res;
|
||||||
|
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
for (const Database::ArtistId artistId : artistsId)
|
for (const Database::ArtistId artistId : artistsId)
|
||||||
{
|
{
|
||||||
@@ -45,9 +45,9 @@ namespace UserInterface
|
|||||||
params.setArtist(artistId);
|
params.setArtist(artistId);
|
||||||
params.setSortMethod(Database::TrackSortMethod::DateDescAndRelease);
|
params.setSortMethod(Database::TrackSortMethod::DateDescAndRelease);
|
||||||
params.setClusters(clusters);
|
params.setClusters(clusters);
|
||||||
params.setRange({0, maxTrackCount - res.size()});
|
params.setRange({ 0, maxTrackCount - res.size() });
|
||||||
|
|
||||||
const auto tracks {Database::Track::find(session, params)};
|
const auto tracks{ Database::Track::findIds(session, params) };
|
||||||
|
|
||||||
res.reserve(res.size() + tracks.results.size());
|
res.reserve(res.size() + tracks.results.size());
|
||||||
res.insert(std::end(res), std::cbegin(tracks.results), std::cend(tracks.results));
|
res.insert(std::end(res), std::cbegin(tracks.results), std::cend(tracks.results));
|
||||||
@@ -59,16 +59,14 @@ namespace UserInterface
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
std::vector<Database::TrackId> getReleasesTracks(Database::Session& session, const std::vector<Database::ReleaseId>& releasesId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
||||||
std::vector<Database::TrackId>
|
|
||||||
getReleasesTracks(Database::Session& session, const std::vector<Database::ReleaseId>& releasesId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
|
||||||
{
|
{
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
assert(maxTrackCount);
|
assert(maxTrackCount);
|
||||||
|
|
||||||
std::vector<TrackId> res;
|
std::vector<TrackId> res;
|
||||||
|
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
for (const ReleaseId releaseId : releasesId)
|
for (const ReleaseId releaseId : releasesId)
|
||||||
{
|
{
|
||||||
@@ -76,9 +74,9 @@ namespace UserInterface
|
|||||||
params.setRelease(releaseId);
|
params.setRelease(releaseId);
|
||||||
params.setSortMethod(Database::TrackSortMethod::Release);
|
params.setSortMethod(Database::TrackSortMethod::Release);
|
||||||
params.setClusters(clusters);
|
params.setClusters(clusters);
|
||||||
params.setRange({0, maxTrackCount - res.size()});
|
params.setRange({ 0, maxTrackCount - res.size() });
|
||||||
|
|
||||||
const auto tracks {Database::Track::find(session, params)};
|
const auto tracks{ Database::Track::findIds(session, params) };
|
||||||
|
|
||||||
res.reserve(res.size() + tracks.results.size());
|
res.reserve(res.size() + tracks.results.size());
|
||||||
res.insert(std::end(res), std::cbegin(tracks.results), std::cend(tracks.results));
|
res.insert(std::end(res), std::cbegin(tracks.results), std::cend(tracks.results));
|
||||||
@@ -90,47 +88,43 @@ namespace UserInterface
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
std::vector<Database::TrackId> getTrackListTracks(Database::Session& session, Database::TrackListId trackListId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
||||||
std::vector<Database::TrackId>
|
|
||||||
getTrackListTracks(Database::Session& session, Database::TrackListId trackListId, const std::vector<Database::ClusterId>& clusters, std::size_t maxTrackCount)
|
|
||||||
{
|
{
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
assert(maxTrackCount);
|
assert(maxTrackCount);
|
||||||
|
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction{ session.createSharedTransaction() };
|
||||||
|
|
||||||
Database::Track::FindParameters params;
|
Database::Track::FindParameters params;
|
||||||
params.setTrackList(trackListId);
|
params.setTrackList(trackListId);
|
||||||
params.setClusters(clusters);
|
params.setClusters(clusters);
|
||||||
params.setRange({0, maxTrackCount});
|
params.setRange({ 0, maxTrackCount });
|
||||||
params.setSortMethod(TrackSortMethod::TrackList);
|
params.setSortMethod(TrackSortMethod::TrackList);
|
||||||
params.setDistinct(false);
|
params.setDistinct(false);
|
||||||
|
|
||||||
return Database::Track::find(session, params).results;
|
return Database::Track::findIds(session, params).results;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayQueueController::PlayQueueController(Filters& filters, PlayQueue& playQueue)
|
PlayQueueController::PlayQueueController(Filters& filters, PlayQueue& playQueue)
|
||||||
: _filters {filters}
|
: _filters{ filters }
|
||||||
, _playQueue {playQueue}
|
, _playQueue{ playQueue }
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueueController::processCommand(Command command, const std::vector<Database::ArtistId>& artistIds)
|
||||||
PlayQueueController::processCommand(Command command, const std::vector<Database::ArtistId>& artistIds)
|
|
||||||
{
|
{
|
||||||
const std::vector<Database::TrackId> tracks {getArtistsTracks(LmsApp->getDbSession(), artistIds, _filters.getClusterIds(), _maxTrackCountToEnqueue)};
|
const std::vector<Database::TrackId> tracks{ getArtistsTracks(LmsApp->getDbSession(), artistIds, _filters.getClusterIds(), _maxTrackCountToEnqueue) };
|
||||||
processCommand(command, tracks);
|
processCommand(command, tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueueController::processCommand(Command command, const std::vector<Database::ReleaseId>& releaseIds)
|
||||||
PlayQueueController::processCommand(Command command, const std::vector<Database::ReleaseId>& releaseIds)
|
|
||||||
{
|
{
|
||||||
const std::vector<Database::TrackId> tracks {getReleasesTracks(LmsApp->getDbSession(), releaseIds, _filters.getClusterIds(), _maxTrackCountToEnqueue)};
|
const std::vector<Database::TrackId> tracks{ getReleasesTracks(LmsApp->getDbSession(), releaseIds, _filters.getClusterIds(), _maxTrackCountToEnqueue) };
|
||||||
processCommand(command, tracks);
|
processCommand(command, tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueueController::processCommand(Command command, const std::vector<Database::TrackId>& trackIds)
|
||||||
PlayQueueController::processCommand(Command command, const std::vector<Database::TrackId>& trackIds)
|
|
||||||
{
|
{
|
||||||
// consider things are already filtered here, and _maxTrackCount honored playqueue side...
|
// consider things are already filtered here, and _maxTrackCount honored playqueue side...
|
||||||
switch (command)
|
switch (command)
|
||||||
@@ -150,32 +144,30 @@ namespace UserInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueueController::processCommand(Command command, Database::TrackListId trackListId)
|
||||||
PlayQueueController::processCommand(Command command, Database::TrackListId trackListId)
|
|
||||||
{
|
{
|
||||||
const std::vector<Database::TrackId> tracks {getTrackListTracks(LmsApp->getDbSession(), trackListId, _filters.getClusterIds(), _maxTrackCountToEnqueue)};
|
const std::vector<Database::TrackId> tracks{ getTrackListTracks(LmsApp->getDbSession(), trackListId, _filters.getClusterIds(), _maxTrackCountToEnqueue) };
|
||||||
processCommand(command, tracks);
|
processCommand(command, tracks);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void PlayQueueController::playTrackInRelease(Database::TrackId trackId)
|
||||||
PlayQueueController::playTrackInRelease(Database::TrackId trackId)
|
|
||||||
{
|
{
|
||||||
Database::ReleaseId releaseId;
|
Database::ReleaseId releaseId;
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
const Database::Track::pointer track {Database::Track::find(LmsApp->getDbSession(), trackId)};
|
const Database::Track::pointer track{ Database::Track::find(LmsApp->getDbSession(), trackId) };
|
||||||
if (!track || !track->getRelease())
|
if (!track || !track->getRelease())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
releaseId = track->getRelease()->getId();
|
releaseId = track->getRelease()->getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<Database::TrackId> tracks {getReleasesTracks(LmsApp->getDbSession(), {releaseId}, _filters.getClusterIds(), _maxTrackCountToEnqueue)};
|
const std::vector<Database::TrackId> tracks{ getReleasesTracks(LmsApp->getDbSession(), {releaseId}, _filters.getClusterIds(), _maxTrackCountToEnqueue) };
|
||||||
auto itTrack {std::find(std::cbegin(tracks), std::cend(tracks), trackId)};
|
auto itTrack{ std::find(std::cbegin(tracks), std::cend(tracks), trackId) };
|
||||||
if (itTrack == std::cend(tracks))
|
if (itTrack == std::cend(tracks))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const std::size_t index {static_cast<std::size_t>(std::distance(std::cbegin(tracks), itTrack))};
|
const std::size_t index{ static_cast<std::size_t>(std::distance(std::cbegin(tracks), itTrack)) };
|
||||||
_playQueue.playAtIndex(tracks, index);
|
_playQueue.playAtIndex(tracks, index);
|
||||||
}
|
}
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|||||||
+116
-129
@@ -50,22 +50,23 @@
|
|||||||
|
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface
|
||||||
|
|
||||||
void
|
|
||||||
showReleaseInfoModal(Database::ReleaseId releaseId)
|
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
namespace
|
||||||
|
{
|
||||||
|
void showReleaseInfoModal(Database::ReleaseId releaseId)
|
||||||
|
{
|
||||||
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
const Database::Release::pointer release {Database::Release::find(LmsApp->getDbSession(), releaseId)};
|
const Database::Release::pointer release{ Database::Release::find(LmsApp->getDbSession(), releaseId) };
|
||||||
if (!release)
|
if (!release)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto releaseInfo {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.Release.template.release-info"))};
|
auto releaseInfo{ std::make_unique<Template>(Wt::WString::tr("Lms.Explore.Release.template.release-info")) };
|
||||||
Wt::WWidget* releaseInfoPtr {releaseInfo.get()};
|
Wt::WWidget* releaseInfoPtr{ releaseInfo.get() };
|
||||||
releaseInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
releaseInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
|
||||||
if (auto primaryReleaseType {release->getPrimaryType()})
|
if (auto primaryReleaseType{ release->getPrimaryType() })
|
||||||
{
|
{
|
||||||
releaseInfo->setCondition("if-has-release-type", true);
|
releaseInfo->setCondition("if-has-release-type", true);
|
||||||
releaseInfo->bindString("release-type", ReleaseHelpers::buildReleaseTypeString(*primaryReleaseType, release->getSecondaryTypes()));
|
releaseInfo->bindString("release-type", ReleaseHelpers::buildReleaseTypeString(*primaryReleaseType, release->getSecondaryTypes()));
|
||||||
@@ -78,11 +79,11 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
|
|||||||
Artist::FindParameters params;
|
Artist::FindParameters params;
|
||||||
params.setRelease(releaseId);
|
params.setRelease(releaseId);
|
||||||
params.setLinkType(linkType);
|
params.setLinkType(linkType);
|
||||||
const auto artistIds {Artist::find(LmsApp->getDbSession(), params)};
|
const auto artistIds{ Artist::find(LmsApp->getDbSession(), params) };
|
||||||
if (artistIds.results.empty())
|
if (artistIds.results.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Wt::WString typeStr {Wt::WString::trn(type, artistIds.results.size())};
|
Wt::WString typeStr{ Wt::WString::trn(type, artistIds.results.size()) };
|
||||||
for (ArtistId artistId : artistIds.results)
|
for (ArtistId artistId : artistIds.results)
|
||||||
artistMap[typeStr].insert(artistId);
|
artistMap[typeStr].insert(artistId);
|
||||||
};
|
};
|
||||||
@@ -92,17 +93,17 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
|
|||||||
TrackArtistLink::FindParameters params;
|
TrackArtistLink::FindParameters params;
|
||||||
params.setRelease(releaseId);
|
params.setRelease(releaseId);
|
||||||
params.setLinkType(TrackArtistLinkType::Performer);
|
params.setLinkType(TrackArtistLinkType::Performer);
|
||||||
const auto links {TrackArtistLink::find(LmsApp->getDbSession(), params)};
|
const auto links{ TrackArtistLink::find(LmsApp->getDbSession(), params) };
|
||||||
if (links.results.empty())
|
if (links.results.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
for (const TrackArtistLinkId linkId : links.results)
|
for (const TrackArtistLinkId linkId : links.results)
|
||||||
{
|
{
|
||||||
const TrackArtistLink::pointer link {TrackArtistLink::find(LmsApp->getDbSession(), linkId)};
|
const TrackArtistLink::pointer link{ TrackArtistLink::find(LmsApp->getDbSession(), linkId) };
|
||||||
if (!link)
|
if (!link)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
artistMap[std::string {link->getSubType()}].insert(link->getArtist()->getId());
|
artistMap[std::string{ link->getSubType() }].insert(link->getArtist()->getId());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,9 +115,9 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
|
|||||||
addArtists(TrackArtistLinkType::Producer, "Lms.Explore.Artists.linktype-producer");
|
addArtists(TrackArtistLinkType::Producer, "Lms.Explore.Artists.linktype-producer");
|
||||||
addPerformerArtists();
|
addPerformerArtists();
|
||||||
|
|
||||||
if (auto itRolelessPerformers {artistMap.find("")}; itRolelessPerformers != std::cend(artistMap))
|
if (auto itRolelessPerformers{ artistMap.find("") }; itRolelessPerformers != std::cend(artistMap))
|
||||||
{
|
{
|
||||||
Wt::WString performersStr {Wt::WString::trn("Lms.Explore.Artists.linktype-performer", itRolelessPerformers->second.size())};
|
Wt::WString performersStr{ Wt::WString::trn("Lms.Explore.Artists.linktype-performer", itRolelessPerformers->second.size()) };
|
||||||
artistMap[performersStr] = std::move(itRolelessPerformers->second);
|
artistMap[performersStr] = std::move(itRolelessPerformers->second);
|
||||||
artistMap.erase(itRolelessPerformers);
|
artistMap.erase(itRolelessPerformers);
|
||||||
}
|
}
|
||||||
@@ -124,12 +125,12 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
|
|||||||
if (!artistMap.empty())
|
if (!artistMap.empty())
|
||||||
{
|
{
|
||||||
releaseInfo->setCondition("if-has-artist", true);
|
releaseInfo->setCondition("if-has-artist", true);
|
||||||
Wt::WContainerWidget* artistTable {releaseInfo->bindNew<Wt::WContainerWidget>("artist-table")};
|
Wt::WContainerWidget* artistTable{ releaseInfo->bindNew<Wt::WContainerWidget>("artist-table") };
|
||||||
|
|
||||||
for (const auto& [role, artistIds] : artistMap)
|
for (const auto& [role, artistIds] : artistMap)
|
||||||
{
|
{
|
||||||
std::unique_ptr<Wt::WContainerWidget> artistContainer {Utils::createArtistAnchorList(std::vector (std::cbegin(artistIds), std::cend(artistIds)))};
|
std::unique_ptr<Wt::WContainerWidget> artistContainer{ Utils::createArtistAnchorList(std::vector(std::cbegin(artistIds), std::cend(artistIds))) };
|
||||||
auto artistsEntry {std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists"))};
|
auto artistsEntry{ std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists")) };
|
||||||
artistsEntry->bindString("type", role);
|
artistsEntry->bindString("type", role);
|
||||||
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
||||||
artistTable->addWidget(std::move(artistsEntry));
|
artistTable->addWidget(std::move(artistsEntry));
|
||||||
@@ -137,15 +138,11 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: save in DB and mean all this
|
// TODO: save in DB and mean all this
|
||||||
for (TrackId trackId : Track::find(LmsApp->getDbSession(), Track::FindParameters {}.setRelease(releaseId).setRange(Range {0, 1})).results)
|
for (const Track::pointer& track : Track::find(LmsApp->getDbSession(), Track::FindParameters{}.setRelease(releaseId).setRange(Range{ 0, 1 })).results)
|
||||||
{
|
{
|
||||||
const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)};
|
if (const auto audioFile{ Av::parseAudioFile(track->getPath()) })
|
||||||
if (!track)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (const auto audioFile {Av::parseAudioFile(track->getPath())})
|
|
||||||
{
|
{
|
||||||
const std::optional<Av::StreamInfo> audioStream {audioFile->getBestStreamInfo()};
|
const std::optional<Av::StreamInfo> audioStream{ audioFile->getBestStreamInfo() };
|
||||||
if (audioStream)
|
if (audioStream)
|
||||||
{
|
{
|
||||||
releaseInfo->setCondition("if-has-codec", true);
|
releaseInfo->setCondition("if-has-codec", true);
|
||||||
@@ -160,20 +157,39 @@ showReleaseInfoModal(Database::ReleaseId releaseId)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WPushButton* okBtn {releaseInfo->bindNew<Wt::WPushButton>("ok-btn", Wt::WString::tr("Lms.ok"))};
|
Wt::WPushButton* okBtn{ releaseInfo->bindNew<Wt::WPushButton>("ok-btn", Wt::WString::tr("Lms.ok")) };
|
||||||
okBtn->clicked().connect([=]
|
okBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
LmsApp->getModalManager().dispose(releaseInfoPtr);
|
LmsApp->getModalManager().dispose(releaseInfoPtr);
|
||||||
});
|
});
|
||||||
|
|
||||||
LmsApp->getModalManager().show(std::move(releaseInfo));
|
LmsApp->getModalManager().show(std::move(releaseInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
Release::Release(Filters& filters, PlayQueueController& playQueueController)
|
std::optional<ReleaseId> extractReleaseIdFromInternalPath()
|
||||||
: Template {Wt::WString::tr("Lms.Explore.Release.template")}
|
{
|
||||||
, _filters {filters}
|
if (wApp->internalPathMatches("/release/mbid/"))
|
||||||
, _playQueueController {playQueueController}
|
{
|
||||||
{
|
const auto mbid{ UUID::fromString(wApp->internalPathNextPart("/release/mbid/")) };
|
||||||
|
if (mbid)
|
||||||
|
{
|
||||||
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
if (const Database::Release::pointer release{ Database::Release::find(LmsApp->getDbSession(), *mbid) })
|
||||||
|
return release->getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringUtils::readAs<ReleaseId::ValueType>(wApp->internalPathNextPart("/release/"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Release::Release(Filters& filters, PlayQueueController& playQueueController)
|
||||||
|
: Template{ Wt::WString::tr("Lms.Explore.Release.template") }
|
||||||
|
, _filters{ filters }
|
||||||
|
, _playQueueController{ playQueueController }
|
||||||
|
{
|
||||||
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
addFunction("id", &Wt::WTemplate::Functions::id);
|
addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
|
|
||||||
@@ -189,36 +205,14 @@ Release::Release(Filters& filters, PlayQueueController& playQueueController)
|
|||||||
});
|
});
|
||||||
|
|
||||||
refreshView();
|
refreshView();
|
||||||
}
|
|
||||||
|
|
||||||
static
|
|
||||||
std::optional<ReleaseId>
|
|
||||||
extractReleaseIdFromInternalPath()
|
|
||||||
{
|
|
||||||
if (wApp->internalPathMatches("/release/mbid/"))
|
|
||||||
{
|
|
||||||
const auto mbid {UUID::fromString(wApp->internalPathNextPart("/release/mbid/"))};
|
|
||||||
if (mbid)
|
|
||||||
{
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
if (const Database::Release::pointer release {Database::Release::find(LmsApp->getDbSession(), *mbid)})
|
|
||||||
return release->getId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::nullopt;
|
void Release::refreshView()
|
||||||
}
|
{
|
||||||
|
|
||||||
return StringUtils::readAs<ReleaseId::ValueType>(wApp->internalPathNextPart("/release/"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
Release::refreshView()
|
|
||||||
{
|
|
||||||
if (!wApp->internalPathMatches("/release/"))
|
if (!wApp->internalPathMatches("/release/"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const auto releaseId {extractReleaseIdFromInternalPath()};
|
const auto releaseId{ extractReleaseIdFromInternalPath() };
|
||||||
|
|
||||||
// consider everything is up to date is the same release is being rendered
|
// consider everything is up to date is the same release is being rendered
|
||||||
if (!_needForceRefresh && releaseId && *releaseId == _releaseId)
|
if (!_needForceRefresh && releaseId && *releaseId == _releaseId)
|
||||||
@@ -229,15 +223,15 @@ Release::refreshView()
|
|||||||
_needForceRefresh = false;
|
_needForceRefresh = false;
|
||||||
|
|
||||||
if (!releaseId)
|
if (!releaseId)
|
||||||
throw ReleaseNotFoundException {};
|
throw ReleaseNotFoundException{};
|
||||||
|
|
||||||
auto similarReleasesIds {Service<Recommendation::IRecommendationService>::get()->getSimilarReleases(*releaseId, 6)};
|
auto similarReleasesIds{ Service<Recommendation::IRecommendationService>::get()->getSimilarReleases(*releaseId, 6) };
|
||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
const Database::Release::pointer release {Database::Release::find(LmsApp->getDbSession(), *releaseId)};
|
const Database::Release::pointer release{ Database::Release::find(LmsApp->getDbSession(), *releaseId) };
|
||||||
if (!release)
|
if (!release)
|
||||||
throw ReleaseNotFoundException {};
|
throw ReleaseNotFoundException{};
|
||||||
|
|
||||||
LmsApp->setTitle(release->getName());
|
LmsApp->setTitle(release->getName());
|
||||||
_releaseId = *releaseId;
|
_releaseId = *releaseId;
|
||||||
@@ -248,7 +242,7 @@ Release::refreshView()
|
|||||||
|
|
||||||
bindString("name", Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
|
bindString("name", Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
Wt::WString year {ReleaseHelpers::buildReleaseYearString(release->getReleaseDate(), release->getOriginalReleaseDate())};
|
Wt::WString year{ ReleaseHelpers::buildReleaseYearString(release->getReleaseDate(), release->getOriginalReleaseDate()) };
|
||||||
if (!year.empty())
|
if (!year.empty())
|
||||||
{
|
{
|
||||||
setCondition("if-has-year", true);
|
setCondition("if-has-year", true);
|
||||||
@@ -261,17 +255,17 @@ Release::refreshView()
|
|||||||
|
|
||||||
bindWidget<Wt::WImage>("cover", Utils::createCover(release->getId(), CoverResource::Size::Large));
|
bindWidget<Wt::WImage>("cover", Utils::createCover(release->getId(), CoverResource::Size::Large));
|
||||||
|
|
||||||
Wt::WContainerWidget* clusterContainers {bindNew<Wt::WContainerWidget>("clusters")};
|
Wt::WContainerWidget* clusterContainers{ bindNew<Wt::WContainerWidget>("clusters") };
|
||||||
{
|
{
|
||||||
const auto clusterTypes {ScanSettings::get(LmsApp->getDbSession())->getClusterTypes()};
|
const auto clusterTypes{ ScanSettings::get(LmsApp->getDbSession())->getClusterTypes() };
|
||||||
const auto clusterGroups {release->getClusterGroups(clusterTypes, 3)};
|
const auto clusterGroups{ release->getClusterGroups(clusterTypes, 3) };
|
||||||
|
|
||||||
for (const auto& clusters : clusterGroups)
|
for (const auto& clusters : clusterGroups)
|
||||||
{
|
{
|
||||||
for (const Database::Cluster::pointer& cluster : clusters)
|
for (const Database::Cluster::pointer& cluster : clusters)
|
||||||
{
|
{
|
||||||
const ClusterId clusterId {cluster->getId()};
|
const ClusterId clusterId{ cluster->getId() };
|
||||||
Wt::WInteractWidget* entry {clusterContainers->addWidget(Utils::createCluster(clusterId))};
|
Wt::WInteractWidget* entry{ clusterContainers->addWidget(Utils::createCluster(clusterId)) };
|
||||||
entry->clicked().connect([=]
|
entry->clicked().connect([=]
|
||||||
{
|
{
|
||||||
_filters.add(clusterId);
|
_filters.add(clusterId);
|
||||||
@@ -283,29 +277,29 @@ Release::refreshView()
|
|||||||
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
|
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
|
||||||
->clicked().connect([=]
|
->clicked().connect([=]
|
||||||
{
|
{
|
||||||
_playQueueController.processCommand(PlayQueueController::Command::Play, {_releaseId});
|
_playQueueController.processCommand(PlayQueueController::Command::Play, { _releaseId });
|
||||||
});
|
});
|
||||||
|
|
||||||
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
|
bindNew<Wt::WPushButton>("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain)
|
||||||
->clicked().connect([=]
|
->clicked().connect([=]
|
||||||
{
|
{
|
||||||
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, {_releaseId});
|
_playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { _releaseId });
|
||||||
});
|
});
|
||||||
|
|
||||||
bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain)
|
bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain)
|
||||||
->clicked().connect([=]
|
->clicked().connect([=]
|
||||||
{
|
{
|
||||||
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, {_releaseId});
|
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, { _releaseId });
|
||||||
});
|
});
|
||||||
|
|
||||||
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
|
bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain)
|
||||||
->clicked().connect([=]
|
->clicked().connect([=]
|
||||||
{
|
{
|
||||||
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, {_releaseId});
|
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { _releaseId });
|
||||||
});
|
});
|
||||||
|
|
||||||
bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
||||||
->setLink(Wt::WLink {std::make_unique<DownloadReleaseResource>(_releaseId)});
|
->setLink(Wt::WLink{ std::make_unique<DownloadReleaseResource>(_releaseId) });
|
||||||
|
|
||||||
bindNew<Wt::WPushButton>("release-info", Wt::WString::tr("Lms.Explore.release-info"))
|
bindNew<Wt::WPushButton>("release-info", Wt::WString::tr("Lms.Explore.release-info"))
|
||||||
->clicked().connect([=]
|
->clicked().connect([=]
|
||||||
@@ -314,9 +308,9 @@ Release::refreshView()
|
|||||||
});
|
});
|
||||||
|
|
||||||
{
|
{
|
||||||
auto isStarred {[=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), _releaseId); }};
|
auto isStarred{ [=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), _releaseId); } };
|
||||||
|
|
||||||
Wt::WPushButton* starBtn {bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star"))};
|
Wt::WPushButton* starBtn{ bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) };
|
||||||
starBtn->clicked().connect([=]
|
starBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
if (isStarred())
|
if (isStarred())
|
||||||
@@ -332,12 +326,12 @@ Release::refreshView()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WContainerWidget* rootContainer {bindNew<Wt::WContainerWidget>("container")};
|
Wt::WContainerWidget* rootContainer{ bindNew<Wt::WContainerWidget>("container") };
|
||||||
|
|
||||||
const bool variousArtists {release->hasVariousArtists()};
|
const bool variousArtists{ release->hasVariousArtists() };
|
||||||
const auto totalDisc {release->getTotalDisc()};
|
const auto totalDisc{ release->getTotalDisc() };
|
||||||
const std::size_t discCount {release->getDiscCount()};
|
const std::size_t discCount{ release->getDiscCount() };
|
||||||
const bool isReleaseMultiDisc {(discCount > 1) || (totalDisc && *totalDisc > 1)};
|
const bool isReleaseMultiDisc{ (discCount > 1) || (totalDisc && *totalDisc > 1) };
|
||||||
|
|
||||||
// Expect to be called in asc order
|
// Expect to be called in asc order
|
||||||
std::map<std::size_t, Wt::WContainerWidget*> trackContainers;
|
std::map<std::size_t, Wt::WContainerWidget*> trackContainers;
|
||||||
@@ -349,14 +343,14 @@ Release::refreshView()
|
|||||||
return it->second;
|
return it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WTemplate* disc {rootContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry-disc"))};
|
Wt::WTemplate* disc{ rootContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry-disc")) };
|
||||||
|
|
||||||
if (discSubtitle.empty())
|
if (discSubtitle.empty())
|
||||||
disc->bindNew<Wt::WText>("disc-title", Wt::WString::tr("Lms.Explore.Release.disc").arg(discNumber));
|
disc->bindNew<Wt::WText>("disc-title", Wt::WString::tr("Lms.Explore.Release.disc").arg(discNumber));
|
||||||
else
|
else
|
||||||
disc->bindString("disc-title", Wt::WString::fromUTF8(discSubtitle), Wt::TextFormat::Plain);
|
disc->bindString("disc-title", Wt::WString::fromUTF8(discSubtitle), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
Wt::WContainerWidget* tracksContainer {disc->bindNew<Wt::WContainerWidget>("tracks")};
|
Wt::WContainerWidget* tracksContainer{ disc->bindNew<Wt::WContainerWidget>("tracks") };
|
||||||
trackContainers[discNumber] = tracksContainer;
|
trackContainers[discNumber] = tracksContainer;
|
||||||
|
|
||||||
return tracksContainer;
|
return tracksContainer;
|
||||||
@@ -368,7 +362,7 @@ Release::refreshView()
|
|||||||
if (noDiscTracksContainer)
|
if (noDiscTracksContainer)
|
||||||
return noDiscTracksContainer;
|
return noDiscTracksContainer;
|
||||||
|
|
||||||
Wt::WTemplate* disc {rootContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry-nodisc"))};
|
Wt::WTemplate* disc{ rootContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry-nodisc")) };
|
||||||
noDiscTracksContainer = disc->bindNew<Wt::WContainerWidget>("tracks");
|
noDiscTracksContainer = disc->bindNew<Wt::WContainerWidget>("tracks");
|
||||||
|
|
||||||
return noDiscTracksContainer;
|
return noDiscTracksContainer;
|
||||||
@@ -379,12 +373,10 @@ Release::refreshView()
|
|||||||
params.setSortMethod(Database::TrackSortMethod::Release);
|
params.setSortMethod(Database::TrackSortMethod::Release);
|
||||||
params.setClusters(_filters.getClusterIds());
|
params.setClusters(_filters.getClusterIds());
|
||||||
|
|
||||||
const auto tracks {Database::Track::find(LmsApp->getDbSession(), params)};
|
for (const Database::Track::pointer& track : Database::Track::find(LmsApp->getDbSession(), params).results)
|
||||||
for (const Database::TrackId trackId : tracks.results)
|
|
||||||
{
|
{
|
||||||
const Database::Track::pointer track {Database::Track::find(LmsApp->getDbSession(), trackId)};
|
const Database::TrackId trackId{ track->getId() };
|
||||||
|
const auto discNumber{ track->getDiscNumber() };
|
||||||
const auto discNumber {track->getDiscNumber()};
|
|
||||||
|
|
||||||
Wt::WContainerWidget* container;
|
Wt::WContainerWidget* container;
|
||||||
if (isReleaseMultiDisc && discNumber)
|
if (isReleaseMultiDisc && discNumber)
|
||||||
@@ -392,26 +384,26 @@ Release::refreshView()
|
|||||||
else
|
else
|
||||||
container = getOrAddNoDiscContainer();
|
container = getOrAddNoDiscContainer();
|
||||||
|
|
||||||
Template* entry {container->addNew<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry"))};
|
Template* entry{ container->addNew<Template>(Wt::WString::tr("Lms.Explore.Release.template.entry")) };
|
||||||
|
|
||||||
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
const auto artists {track->getArtistIds({TrackArtistLinkType::Artist})};
|
const auto artists{ track->getArtistIds({TrackArtistLinkType::Artist}) };
|
||||||
if (variousArtists && !artists.empty())
|
if (variousArtists && !artists.empty())
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-artists", true);
|
entry->setCondition("if-has-artists", true);
|
||||||
entry->bindWidget("artists", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
entry->bindWidget("artists", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
||||||
entry->bindWidget("artists-md", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(),artists));
|
entry->bindWidget("artists-md", Utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto trackNumber {track->getTrackNumber()};
|
auto trackNumber{ track->getTrackNumber() };
|
||||||
if (trackNumber)
|
if (trackNumber)
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-track-number", true);
|
entry->setCondition("if-has-track-number", true);
|
||||||
entry->bindInt("track-number", *trackNumber);
|
entry->bindInt("track-number", *trackNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WPushButton* playBtn {entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML)};
|
Wt::WPushButton* playBtn{ entry->bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) };
|
||||||
playBtn->clicked().connect([=]
|
playBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
_playQueueController.playTrackInRelease(trackId);
|
_playQueueController.playTrackInRelease(trackId);
|
||||||
@@ -427,20 +419,20 @@ Release::refreshView()
|
|||||||
entry->bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"))
|
entry->bindNew<Wt::WPushButton>("play-next", Wt::WString::tr("Lms.Explore.play-next"))
|
||||||
->clicked().connect([=]
|
->clicked().connect([=]
|
||||||
{
|
{
|
||||||
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, {trackId});
|
_playQueueController.processCommand(PlayQueueController::Command::PlayNext, { trackId });
|
||||||
});
|
});
|
||||||
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
|
entry->bindNew<Wt::WPushButton>("play-last", Wt::WString::tr("Lms.Explore.play-last"))
|
||||||
->clicked().connect([=]
|
->clicked().connect([=]
|
||||||
{
|
{
|
||||||
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, {trackId});
|
_playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { trackId });
|
||||||
});
|
});
|
||||||
|
|
||||||
auto isStarred {[=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), trackId); }};
|
auto isStarred{ [=] { return Service<Feedback::IFeedbackService>::get()->isStarred(LmsApp->getUserId(), trackId); } };
|
||||||
|
|
||||||
Wt::WPushButton* starBtn {entry->bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star"))};
|
Wt::WPushButton* starBtn{ entry->bindNew<Wt::WPushButton>("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) };
|
||||||
starBtn->clicked().connect([=]
|
starBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
|
|
||||||
if (isStarred())
|
if (isStarred())
|
||||||
{
|
{
|
||||||
@@ -455,7 +447,7 @@ Release::refreshView()
|
|||||||
});
|
});
|
||||||
|
|
||||||
entry->bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
entry->bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
||||||
->setLink(Wt::WLink {std::make_unique<DownloadTrackResource>(trackId)});
|
->setLink(Wt::WLink{ std::make_unique<DownloadTrackResource>(trackId) });
|
||||||
|
|
||||||
entry->bindNew<Wt::WPushButton>("track-info", Wt::WString::tr("Lms.Explore.track-info"))
|
entry->bindNew<Wt::WPushButton>("track-info", Wt::WString::tr("Lms.Explore.track-info"))
|
||||||
->clicked().connect([=] { TrackListHelpers::showTrackInfoModal(trackId, _filters); });
|
->clicked().connect([=] { TrackListHelpers::showTrackInfoModal(trackId, _filters); });
|
||||||
@@ -463,88 +455,83 @@ Release::refreshView()
|
|||||||
|
|
||||||
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
entry->bindString("duration", Utils::durationToString(track->getDuration()), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
LmsApp->getMediaPlayer().trackLoaded.connect(entry, [=] (TrackId loadedTrackId)
|
LmsApp->getMediaPlayer().trackLoaded.connect(entry, [=](TrackId loadedTrackId)
|
||||||
{
|
{
|
||||||
entry->toggleStyleClass("Lms-entry-playing", loadedTrackId == trackId);
|
entry->toggleStyleClass("Lms-entry-playing", loadedTrackId == trackId);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (auto trackIdLoaded {LmsApp->getMediaPlayer().getTrackLoaded()})
|
if (auto trackIdLoaded{ LmsApp->getMediaPlayer().getTrackLoaded() })
|
||||||
{
|
{
|
||||||
entry->toggleStyleClass("Lms-entry-playing", *trackIdLoaded == trackId);
|
entry->toggleStyleClass("Lms-entry-playing", *trackIdLoaded == trackId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
entry->removeStyleClass("Lms-entry-playing");
|
entry->removeStyleClass("Lms-entry-playing");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void Release::refreshReleaseArtists(const Database::Release::pointer& release)
|
||||||
Release::refreshReleaseArtists(const Database::Release::pointer& release)
|
{
|
||||||
{
|
|
||||||
auto container{ Utils::createArtistsAnchorsForRelease(release) };
|
auto container{ Utils::createArtistsAnchorsForRelease(release) };
|
||||||
if (container)
|
if (container)
|
||||||
{
|
{
|
||||||
setCondition("if-has-release-artists", true);
|
setCondition("if-has-release-artists", true);
|
||||||
bindWidget("artists", std::move(container));
|
bindWidget("artists", std::move(container));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void Release::refreshCopyright(const Database::Release::pointer& release)
|
||||||
Release::refreshCopyright(const Database::Release::pointer& release)
|
{
|
||||||
{
|
std::optional<std::string> copyright{ release->getCopyright() };
|
||||||
std::optional<std::string> copyright {release->getCopyright()};
|
std::optional<std::string> copyrightURL{ release->getCopyrightURL() };
|
||||||
std::optional<std::string> copyrightURL {release->getCopyrightURL()};
|
|
||||||
|
|
||||||
if (!copyright && !copyrightURL)
|
if (!copyright && !copyrightURL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
setCondition("if-has-copyright", true);
|
setCondition("if-has-copyright", true);
|
||||||
|
|
||||||
std::string copyrightText {copyright ? *copyright : ""};
|
std::string copyrightText{ copyright ? *copyright : "" };
|
||||||
if (copyrightText.empty() && copyrightURL)
|
if (copyrightText.empty() && copyrightURL)
|
||||||
copyrightText = *copyrightURL;
|
copyrightText = *copyrightURL;
|
||||||
|
|
||||||
if (copyrightURL)
|
if (copyrightURL)
|
||||||
{
|
{
|
||||||
Wt::WLink link {*copyrightURL};
|
Wt::WLink link{ *copyrightURL };
|
||||||
link.setTarget(Wt::LinkTarget::NewWindow);
|
link.setTarget(Wt::LinkTarget::NewWindow);
|
||||||
|
|
||||||
Wt::WAnchor* anchor {bindNew<Wt::WAnchor>("copyright", link)};
|
Wt::WAnchor* anchor{ bindNew<Wt::WAnchor>("copyright", link) };
|
||||||
anchor->setTextFormat(Wt::TextFormat::Plain);
|
anchor->setTextFormat(Wt::TextFormat::Plain);
|
||||||
anchor->setText(Wt::WString::fromUTF8(copyrightText));
|
anchor->setText(Wt::WString::fromUTF8(copyrightText));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
bindString("copyright", Wt::WString::fromUTF8(*copyright), Wt::TextFormat::Plain);
|
bindString("copyright", Wt::WString::fromUTF8(*copyright), Wt::TextFormat::Plain);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void Release::refreshLinks(const Database::Release::pointer& release)
|
||||||
Release::refreshLinks(const Database::Release::pointer& release)
|
{
|
||||||
{
|
const auto mbid{ release->getMBID() };
|
||||||
const auto mbid {release->getMBID()};
|
|
||||||
if (mbid)
|
if (mbid)
|
||||||
{
|
{
|
||||||
setCondition("if-has-mbid", true);
|
setCondition("if-has-mbid", true);
|
||||||
bindString("mbid-link", std::string {"https://musicbrainz.org/release/"} + std::string {mbid->getAsString()});
|
bindString("mbid-link", std::string{ "https://musicbrainz.org/release/" } + std::string{ mbid->getAsString() });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void
|
void Release::refreshSimilarReleases(const std::vector<ReleaseId>& similarReleasesId)
|
||||||
Release::refreshSimilarReleases(const std::vector<ReleaseId>& similarReleasesId)
|
{
|
||||||
{
|
|
||||||
if (similarReleasesId.empty())
|
if (similarReleasesId.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
setCondition("if-has-similar-releases", true);
|
setCondition("if-has-similar-releases", true);
|
||||||
auto* similarReleasesContainer {bindNew<Wt::WContainerWidget>("similar-releases")};
|
auto* similarReleasesContainer{ bindNew<Wt::WContainerWidget>("similar-releases") };
|
||||||
|
|
||||||
for (const ReleaseId id : similarReleasesId)
|
for (const ReleaseId id : similarReleasesId)
|
||||||
{
|
{
|
||||||
const Database::Release::pointer similarRelease {Database::Release::find(LmsApp->getDbSession(), id)};
|
const Database::Release::pointer similarRelease{ Database::Release::find(LmsApp->getDbSession(), id) };
|
||||||
if (!similarRelease)
|
if (!similarRelease)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
similarReleasesContainer->addWidget(ReleaseListHelpers::createEntry(similarRelease));
|
similarReleasesContainer->addWidget(ReleaseListHelpers::createEntry(similarRelease));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -35,11 +35,10 @@ namespace UserInterface
|
|||||||
{
|
{
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
RangeResults<TrackId>
|
RangeResults<TrackId> TrackCollector::get(Range range)
|
||||||
TrackCollector::get(Range range)
|
|
||||||
{
|
{
|
||||||
Feedback::IFeedbackService& feedbackService{*Service<Feedback::IFeedbackService>::get()};
|
Feedback::IFeedbackService& feedbackService{ *Service<Feedback::IFeedbackService>::get() };
|
||||||
Scrobbling::IScrobblingService& scrobblingService {*Service<Scrobbling::IScrobblingService>::get()};
|
Scrobbling::IScrobblingService& scrobblingService{ *Service<Scrobbling::IScrobblingService>::get() };
|
||||||
|
|
||||||
RangeResults<TrackId> tracks;
|
RangeResults<TrackId> tracks;
|
||||||
range = getActualRange(range);
|
range = getActualRange(range);
|
||||||
@@ -72,8 +71,8 @@ namespace UserInterface
|
|||||||
params.setRange(range);
|
params.setRange(range);
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
tracks = Track::find(LmsApp->getDbSession(), params);
|
tracks = Track::findIds(LmsApp->getDbSession(), params);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -86,8 +85,8 @@ namespace UserInterface
|
|||||||
params.setRange(range);
|
params.setRange(range);
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
tracks = Track::find(LmsApp->getDbSession(), params);
|
tracks = Track::findIds(LmsApp->getDbSession(), params);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -99,8 +98,8 @@ namespace UserInterface
|
|||||||
params.setRange(range);
|
params.setRange(range);
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
tracks = Track::find(LmsApp->getDbSession(), params);
|
tracks = Track::findIds(LmsApp->getDbSession(), params);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -112,8 +111,7 @@ namespace UserInterface
|
|||||||
return tracks;
|
return tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
RangeResults<TrackId>
|
RangeResults<TrackId> TrackCollector::getRandomTracks(Range range)
|
||||||
TrackCollector::getRandomTracks(Range range)
|
|
||||||
{
|
{
|
||||||
assert(getMode() == Mode::Random);
|
assert(getMode() == Mode::Random);
|
||||||
|
|
||||||
@@ -122,11 +120,11 @@ namespace UserInterface
|
|||||||
Track::FindParameters params;
|
Track::FindParameters params;
|
||||||
params.setClusters(getFilters().getClusterIds());
|
params.setClusters(getFilters().getClusterIds());
|
||||||
params.setSortMethod(TrackSortMethod::Random);
|
params.setSortMethod(TrackSortMethod::Random);
|
||||||
params.setRange({0, getMaxCount()});
|
params.setRange({ 0, getMaxCount() });
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
_randomTracks = Track::find(LmsApp->getDbSession(), params);
|
_randomTracks = Track::findIds(LmsApp->getDbSession(), params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,4 +132,3 @@ namespace UserInterface
|
|||||||
}
|
}
|
||||||
|
|
||||||
} // ns UserInterface
|
} // ns UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -39,24 +39,22 @@
|
|||||||
#include "ModalManager.hpp"
|
#include "ModalManager.hpp"
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
using namespace Database;
|
namespace UserInterface
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
{
|
||||||
static
|
using namespace Database;
|
||||||
std::optional<TrackListId>
|
|
||||||
extractTrackListIdFromInternalPath()
|
namespace
|
||||||
|
{
|
||||||
|
std::optional<TrackListId> extractTrackListIdFromInternalPath()
|
||||||
{
|
{
|
||||||
return StringUtils::readAs<TrackListId::ValueType>(wApp->internalPathNextPart("/tracklist/"));
|
return StringUtils::readAs<TrackListId::ValueType>(wApp->internalPathNextPart("/tracklist/"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace UserInterface
|
|
||||||
{
|
|
||||||
TrackList::TrackList(Filters& filters, PlayQueueController& playQueueController)
|
TrackList::TrackList(Filters& filters, PlayQueueController& playQueueController)
|
||||||
: Template {Wt::WString::tr("Lms.Explore.TrackList.template")}
|
: Template{ Wt::WString::tr("Lms.Explore.TrackList.template") }
|
||||||
, _filters {filters}
|
, _filters{ filters }
|
||||||
, _playQueueController {playQueueController}
|
, _playQueueController{ playQueueController }
|
||||||
{
|
{
|
||||||
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
addFunction("id", &Wt::WTemplate::Functions::id);
|
addFunction("id", &Wt::WTemplate::Functions::id);
|
||||||
@@ -74,43 +72,42 @@ namespace UserInterface
|
|||||||
refreshView();
|
refreshView();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void TrackList::refreshView()
|
||||||
TrackList::refreshView()
|
|
||||||
{
|
{
|
||||||
if (!wApp->internalPathMatches("/tracklist/"))
|
if (!wApp->internalPathMatches("/tracklist/"))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const std::optional<TrackListId> trackListId {extractTrackListIdFromInternalPath()};
|
const std::optional<TrackListId> trackListId{ extractTrackListIdFromInternalPath() };
|
||||||
if (!trackListId)
|
if (!trackListId)
|
||||||
throw TrackListNotFoundException {};
|
throw TrackListNotFoundException{};
|
||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
const Database::TrackList::pointer trackList {Database::TrackList::find(LmsApp->getDbSession(), *trackListId)};
|
const Database::TrackList::pointer trackList{ Database::TrackList::find(LmsApp->getDbSession(), *trackListId) };
|
||||||
if (!trackList)
|
if (!trackList)
|
||||||
throw TrackListNotFoundException {};
|
throw TrackListNotFoundException{};
|
||||||
|
|
||||||
LmsApp->setTitle(std::string {trackList->getName()});
|
LmsApp->setTitle(std::string{ trackList->getName() });
|
||||||
_trackListId = *trackListId;
|
_trackListId = *trackListId;
|
||||||
|
|
||||||
clear();
|
clear();
|
||||||
|
|
||||||
bindString("name", std::string {trackList->getName()}, Wt::TextFormat::Plain);
|
bindString("name", std::string{ trackList->getName() }, Wt::TextFormat::Plain);
|
||||||
bindString("duration", Utils::durationToString(trackList->getDuration()));
|
bindString("duration", Utils::durationToString(trackList->getDuration()));
|
||||||
const auto trackCount {trackList->getCount()};
|
const auto trackCount{ trackList->getCount() };
|
||||||
bindString("track-count", Wt::WString::trn("Lms.track-count", trackCount).arg(trackCount));
|
bindString("track-count", Wt::WString::trn("Lms.track-count", trackCount).arg(trackCount));
|
||||||
|
|
||||||
Wt::WContainerWidget* clusterContainers {bindNew<Wt::WContainerWidget>("clusters")};
|
Wt::WContainerWidget* clusterContainers{ bindNew<Wt::WContainerWidget>("clusters") };
|
||||||
{
|
{
|
||||||
const auto clusterTypes {ScanSettings::get(LmsApp->getDbSession())->getClusterTypes()};
|
const auto clusterTypes{ ScanSettings::get(LmsApp->getDbSession())->getClusterTypes() };
|
||||||
const auto clusterGroups {trackList->getClusterGroups(clusterTypes, 3)};
|
const auto clusterGroups{ trackList->getClusterGroups(clusterTypes, 3) };
|
||||||
|
|
||||||
for (const auto& clusters : clusterGroups)
|
for (const auto& clusters : clusterGroups)
|
||||||
{
|
{
|
||||||
for (const Database::Cluster::pointer& cluster : clusters)
|
for (const Database::Cluster::pointer& cluster : clusters)
|
||||||
{
|
{
|
||||||
const ClusterId clusterId {cluster->getId()};
|
const ClusterId clusterId{ cluster->getId() };
|
||||||
Wt::WInteractWidget* entry {clusterContainers->addWidget(Utils::createCluster(clusterId))};
|
Wt::WInteractWidget* entry{ clusterContainers->addWidget(Utils::createCluster(clusterId)) };
|
||||||
entry->clicked().connect([=]
|
entry->clicked().connect([=]
|
||||||
{
|
{
|
||||||
_filters.add(clusterId);
|
_filters.add(clusterId);
|
||||||
@@ -138,22 +135,22 @@ namespace UserInterface
|
|||||||
});
|
});
|
||||||
|
|
||||||
bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
bindNew<Wt::WPushButton>("download", Wt::WString::tr("Lms.Explore.download"))
|
||||||
->setLink(Wt::WLink {std::make_unique<DownloadTrackListResource>(*trackListId)});
|
->setLink(Wt::WLink{ std::make_unique<DownloadTrackListResource>(*trackListId) });
|
||||||
|
|
||||||
bindNew<Wt::WPushButton>("delete", Wt::WString::tr("Lms.delete"))
|
bindNew<Wt::WPushButton>("delete", Wt::WString::tr("Lms.delete"))
|
||||||
->clicked().connect([=]
|
->clicked().connect([=]
|
||||||
{
|
{
|
||||||
auto modal {std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.TrackList.template.delete-tracklist"))};
|
auto modal{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.TrackList.template.delete-tracklist")) };
|
||||||
modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
modal->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
Wt::WWidget* modalPtr {modal.get()};
|
Wt::WWidget* modalPtr{ modal.get() };
|
||||||
|
|
||||||
auto* delBtn {modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.delete"))};
|
auto* delBtn{ modal->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.delete")) };
|
||||||
delBtn->clicked().connect([=]
|
delBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createUniqueTransaction()};
|
auto transaction{ LmsApp->getDbSession().createUniqueTransaction() };
|
||||||
|
|
||||||
Database::TrackList::pointer trackList {Database::TrackList::find(LmsApp->getDbSession(), *trackListId)};
|
Database::TrackList::pointer trackList{ Database::TrackList::find(LmsApp->getDbSession(), *trackListId) };
|
||||||
if (trackList)
|
if (trackList)
|
||||||
trackList.remove();
|
trackList.remove();
|
||||||
}
|
}
|
||||||
@@ -165,7 +162,7 @@ namespace UserInterface
|
|||||||
LmsApp->getModalManager().dispose(modalPtr);
|
LmsApp->getModalManager().dispose(modalPtr);
|
||||||
});
|
});
|
||||||
|
|
||||||
auto* cancelBtn {modal->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel"))};
|
auto* cancelBtn{ modal->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel")) };
|
||||||
cancelBtn->clicked().connect([=]
|
cancelBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
LmsApp->getModalManager().dispose(modalPtr);
|
LmsApp->getModalManager().dispose(modalPtr);
|
||||||
@@ -181,24 +178,19 @@ namespace UserInterface
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void TrackList::addSome()
|
||||||
TrackList::addSome()
|
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
Database::Track::FindParameters params;
|
Database::Track::FindParameters params;
|
||||||
params.setClusters(_filters.getClusterIds());
|
params.setClusters(_filters.getClusterIds());
|
||||||
params.setTrackList(_trackListId);
|
params.setTrackList(_trackListId);
|
||||||
params.setSortMethod(Database::TrackSortMethod::TrackList);
|
params.setSortMethod(Database::TrackSortMethod::TrackList);
|
||||||
params.setRange({static_cast<std::size_t>(_container->getCount()), _batchSize});
|
params.setRange({ static_cast<std::size_t>(_container->getCount()), _batchSize });
|
||||||
params.setDistinct(false);
|
params.setDistinct(false);
|
||||||
|
|
||||||
const auto trackIds {Database::Track::find(LmsApp->getDbSession(), params)};
|
for (const Track::pointer& track : Database::Track::find(LmsApp->getDbSession(), params).results)
|
||||||
for (const TrackId trackId : trackIds.results)
|
|
||||||
{
|
|
||||||
if (const Track::pointer track {Track::find(LmsApp->getDbSession(), trackId)})
|
|
||||||
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
_container->add(TrackListHelpers::createEntry(track, _playQueueController, _filters));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,6 @@
|
|||||||
|
|
||||||
#include "DownloadResource.hpp"
|
#include "DownloadResource.hpp"
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <iostream>
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
#include <Wt/Http/Response.h>
|
#include <Wt/Http/Response.h>
|
||||||
#include <Wt/WDateTime.h>
|
#include <Wt/WDateTime.h>
|
||||||
|
|
||||||
@@ -38,22 +34,22 @@
|
|||||||
|
|
||||||
#define LOG(level) LMS_LOG(UI, level) << "Download resource: "
|
#define LOG(level) LMS_LOG(UI, level) << "Download resource: "
|
||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface
|
||||||
|
|
||||||
DownloadResource::~DownloadResource()
|
|
||||||
{
|
{
|
||||||
|
|
||||||
|
DownloadResource::~DownloadResource()
|
||||||
|
{
|
||||||
beingDeleted();
|
beingDeleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void DownloadResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
|
||||||
DownloadResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
|
{
|
||||||
{
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
std::shared_ptr<Zip::IZipper> zipper;
|
std::shared_ptr<Zip::IZipper> zipper;
|
||||||
|
|
||||||
// First, see if this request is for a continuation
|
// First, see if this request is for a continuation
|
||||||
if (Wt::Http::ResponseContinuation *continuation {request.continuation()})
|
if (Wt::Http::ResponseContinuation * continuation{ request.continuation() })
|
||||||
zipper = Wt::cpp17::any_cast<std::shared_ptr<Zip::IZipper>>(continuation->data());
|
zipper = Wt::cpp17::any_cast<std::shared_ptr<Zip::IZipper>>(continuation->data());
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -64,7 +60,7 @@ DownloadResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Resp
|
|||||||
zipper->writeSome(response.out());
|
zipper->writeSome(response.out());
|
||||||
if (!zipper->isComplete())
|
if (!zipper->isComplete())
|
||||||
{
|
{
|
||||||
auto* continuation {response.createContinuation()};
|
auto* continuation{ response.createContinuation() };
|
||||||
continuation->setData(zipper);
|
continuation->setData(zipper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,19 +68,17 @@ DownloadResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Resp
|
|||||||
{
|
{
|
||||||
LOG(ERROR) << "Zipper exception: " << exception.what();
|
LOG(ERROR) << "Zipper exception: " << exception.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
namespace
|
||||||
std::string
|
{
|
||||||
getArtistPathName(Database::Artist::pointer artist)
|
std::string getArtistPathName(Database::Artist::pointer artist)
|
||||||
{
|
{
|
||||||
return StringUtils::replaceInString(artist->getName(), "/", "_");
|
return StringUtils::replaceInString(artist->getName(), "/", "_");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
std::string getReleaseArtistPathName(Database::Release::pointer release)
|
||||||
std::string
|
{
|
||||||
getReleaseArtistPathName(Database::Release::pointer release)
|
|
||||||
{
|
|
||||||
std::string releaseArtistName;
|
std::string releaseArtistName;
|
||||||
|
|
||||||
std::vector<Database::ObjectPtr<Database::Artist>> artists;
|
std::vector<Database::ObjectPtr<Database::Artist>> artists;
|
||||||
@@ -101,32 +95,29 @@ getReleaseArtistPathName(Database::Release::pointer release)
|
|||||||
releaseArtistName = StringUtils::replaceInString(releaseArtistName, "/", "_");
|
releaseArtistName = StringUtils::replaceInString(releaseArtistName, "/", "_");
|
||||||
|
|
||||||
return releaseArtistName;
|
return releaseArtistName;
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
std::string getReleasePathName(Database::Release::pointer release)
|
||||||
std::string
|
{
|
||||||
getReleasePathName(Database::Release::pointer release)
|
|
||||||
{
|
|
||||||
std::string releaseName;
|
std::string releaseName;
|
||||||
|
|
||||||
if (const Wt::WDate releaseDate {release->getReleaseDate()}; releaseDate.isValid())
|
if (const Wt::WDate releaseDate{ release->getReleaseDate() }; releaseDate.isValid())
|
||||||
releaseName += std::to_string(releaseDate.year()) + " - ";
|
releaseName += std::to_string(releaseDate.year()) + " - ";
|
||||||
releaseName += StringUtils::replaceInString(release->getName(), "/", "_");
|
releaseName += StringUtils::replaceInString(release->getName(), "/", "_");
|
||||||
|
|
||||||
return releaseName;
|
return releaseName;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace details
|
namespace details
|
||||||
{
|
{
|
||||||
static
|
std::string getTrackPathName(Database::Track::pointer track)
|
||||||
std::string
|
|
||||||
getTrackPathName(Database::Track::pointer track)
|
|
||||||
{
|
{
|
||||||
std::ostringstream fileName;
|
std::ostringstream fileName;
|
||||||
|
|
||||||
if (auto discNumber {track->getDiscNumber()})
|
if (auto discNumber{ track->getDiscNumber() })
|
||||||
fileName << *discNumber << ".";
|
fileName << *discNumber << ".";
|
||||||
if (auto trackNumber {track->getTrackNumber()})
|
if (auto trackNumber{ track->getTrackNumber() })
|
||||||
fileName << std::setw(2) << std::setfill('0') << *trackNumber << " - ";
|
fileName << std::setw(2) << std::setfill('0') << *trackNumber << " - ";
|
||||||
|
|
||||||
fileName << StringUtils::replaceInString(track->getName(), "/", "_") << track->getPath().filename().extension().string();
|
fileName << StringUtils::replaceInString(track->getName(), "/", "_") << track->getPath().filename().extension().string();
|
||||||
@@ -134,16 +125,12 @@ namespace details
|
|||||||
return fileName.str();
|
return fileName.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
std::string getTrackListPathName(Database::TrackList::pointer trackList)
|
||||||
std::string
|
|
||||||
getTrackListPathName(Database::TrackList::pointer trackList)
|
|
||||||
{
|
{
|
||||||
return StringUtils::replaceInString(trackList->getName(), "/", "_");
|
return StringUtils::replaceInString(trackList->getName(), "/", "_");
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
std::unique_ptr<Zip::IZipper> createZipper(const std::vector<Database::Track::pointer>& tracks)
|
||||||
std::unique_ptr<Zip::IZipper>
|
|
||||||
createZipper(const std::vector<Database::Track::pointer>& tracks)
|
|
||||||
{
|
{
|
||||||
if (tracks.empty())
|
if (tracks.empty())
|
||||||
return {};
|
return {};
|
||||||
@@ -153,7 +140,7 @@ namespace details
|
|||||||
{
|
{
|
||||||
std::string releaseName;
|
std::string releaseName;
|
||||||
std::string releaseArtistName;
|
std::string releaseArtistName;
|
||||||
if (auto release {track->getRelease()})
|
if (auto release{ track->getRelease() })
|
||||||
{
|
{
|
||||||
releaseName = getReleasePathName(release);
|
releaseName = getReleasePathName(release);
|
||||||
releaseArtistName = getReleaseArtistPathName(release);
|
releaseArtistName = getReleaseArtistPathName(release);
|
||||||
@@ -166,115 +153,95 @@ namespace details
|
|||||||
fileName += releaseName + "/";
|
fileName += releaseName + "/";
|
||||||
fileName += getTrackPathName(track);
|
fileName += getTrackPathName(track);
|
||||||
|
|
||||||
files.emplace_back(Zip::Entry {fileName, track->getPath()});
|
files.emplace_back(Zip::Entry{ fileName, track->getPath() });
|
||||||
}
|
}
|
||||||
|
|
||||||
return Zip::createArchiveZipper(files);
|
return Zip::createArchiveZipper(files);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadArtistResource::DownloadArtistResource(Database::ArtistId artistId)
|
DownloadArtistResource::DownloadArtistResource(Database::ArtistId artistId)
|
||||||
: _artistId {artistId}
|
: _artistId{ artistId }
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
Database::Artist::pointer artist {Database::Artist::find(LmsApp->getDbSession(), artistId)};
|
Database::Artist::pointer artist{ Database::Artist::find(LmsApp->getDbSession(), artistId) };
|
||||||
if (artist)
|
if (artist)
|
||||||
suggestFileName(getArtistPathName(artist) + ".zip");
|
suggestFileName(getArtistPathName(artist) + ".zip");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Zip::IZipper>
|
std::unique_ptr<Zip::IZipper> DownloadArtistResource::createZipper()
|
||||||
DownloadArtistResource::createZipper()
|
{
|
||||||
{
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
|
|
||||||
const auto trackResults {Database::Track::find(LmsApp->getDbSession(), Database::Track::FindParameters {}.setArtist(_artistId).setSortMethod(Database::TrackSortMethod::DateDescAndRelease))};
|
const auto trackResults{ Database::Track::find(LmsApp->getDbSession(), Database::Track::FindParameters {}.setArtist(_artistId).setSortMethod(Database::TrackSortMethod::DateDescAndRelease)) };
|
||||||
std::vector<Database::Track::pointer> tracks;
|
return details::createZipper(trackResults.results);
|
||||||
tracks.reserve(trackResults.results.size());
|
}
|
||||||
|
|
||||||
for (const Database::TrackId trackId : trackResults.results)
|
DownloadReleaseResource::DownloadReleaseResource(Database::ReleaseId releaseId)
|
||||||
tracks.push_back(Database::Track::find(LmsApp->getDbSession(), trackId));
|
: _releaseId{ releaseId }
|
||||||
|
{
|
||||||
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
return details::createZipper(tracks);
|
Database::Release::pointer release{ Database::Release::find(LmsApp->getDbSession(), releaseId) };
|
||||||
}
|
|
||||||
|
|
||||||
DownloadReleaseResource::DownloadReleaseResource(Database::ReleaseId releaseId)
|
|
||||||
: _releaseId {releaseId}
|
|
||||||
{
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
|
|
||||||
Database::Release::pointer release {Database::Release::find(LmsApp->getDbSession(), releaseId)};
|
|
||||||
if (release)
|
if (release)
|
||||||
suggestFileName(getReleasePathName(release) + ".zip");
|
suggestFileName(getReleasePathName(release) + ".zip");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::unique_ptr<Zip::IZipper>
|
std::unique_ptr<Zip::IZipper> DownloadReleaseResource::createZipper()
|
||||||
DownloadReleaseResource::createZipper()
|
{
|
||||||
{
|
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
auto trackResults {Track::find(LmsApp->getDbSession(), Track::FindParameters {}.setRelease(_releaseId).setSortMethod(TrackSortMethod::Release))};
|
auto tracks{ Track::find(LmsApp->getDbSession(), Track::FindParameters {}.setRelease(_releaseId).setSortMethod(TrackSortMethod::Release)) };
|
||||||
|
return details::createZipper(tracks.results);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Track::pointer> tracks;
|
DownloadTrackResource::DownloadTrackResource(Database::TrackId trackId)
|
||||||
tracks.reserve(trackResults.results.size());
|
: _trackId{ trackId }
|
||||||
std::transform(std::cbegin(trackResults.results), std::cend(trackResults.results), std::back_inserter(tracks), [](TrackId trackId){ return Track::find(LmsApp->getDbSession(), trackId); });
|
{
|
||||||
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
return details::createZipper(tracks);
|
Database::Track::pointer track{ Database::Track::find(LmsApp->getDbSession(), trackId) };
|
||||||
}
|
|
||||||
|
|
||||||
DownloadTrackResource::DownloadTrackResource(Database::TrackId trackId)
|
|
||||||
: _trackId {trackId}
|
|
||||||
{
|
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
|
|
||||||
Database::Track::pointer track {Database::Track::find(LmsApp->getDbSession(), trackId)};
|
|
||||||
if (track)
|
if (track)
|
||||||
suggestFileName(details::getTrackPathName(track) + ".zip");
|
suggestFileName(details::getTrackPathName(track) + ".zip");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Zip::IZipper>
|
std::unique_ptr<Zip::IZipper> DownloadTrackResource::createZipper()
|
||||||
DownloadTrackResource::createZipper()
|
{
|
||||||
{
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
|
||||||
|
|
||||||
const Database::Track::pointer track {Database::Track::find(LmsApp->getDbSession(), _trackId)};
|
const Database::Track::pointer track{ Database::Track::find(LmsApp->getDbSession(), _trackId) };
|
||||||
if (!track)
|
if (!track)
|
||||||
{
|
{
|
||||||
LOG(DEBUG) << "Cannot find track";
|
LOG(DEBUG) << "Cannot find track";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return details::createZipper({track});
|
return details::createZipper({ track });
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadTrackListResource::DownloadTrackListResource(Database::TrackListId trackListId)
|
DownloadTrackListResource::DownloadTrackListResource(Database::TrackListId trackListId)
|
||||||
: _trackListId {trackListId}
|
: _trackListId{ trackListId }
|
||||||
{
|
{
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
const Database::TrackList::pointer trackList {Database::TrackList::find(LmsApp->getDbSession(), trackListId)};
|
const Database::TrackList::pointer trackList{ Database::TrackList::find(LmsApp->getDbSession(), trackListId) };
|
||||||
if (trackList)
|
if (trackList)
|
||||||
suggestFileName(details::getTrackListPathName(trackList) + ".zip");
|
suggestFileName(details::getTrackListPathName(trackList) + ".zip");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Zip::IZipper>
|
std::unique_ptr<Zip::IZipper> DownloadTrackListResource::createZipper()
|
||||||
DownloadTrackListResource::createZipper()
|
{
|
||||||
{
|
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
auto transaction {LmsApp->getDbSession().createSharedTransaction()};
|
auto transaction{ LmsApp->getDbSession().createSharedTransaction() };
|
||||||
|
|
||||||
Track::FindParameters params;
|
Track::FindParameters params;
|
||||||
params.setTrackList(_trackListId);
|
params.setTrackList(_trackListId);
|
||||||
const auto trackResults {Track::find(LmsApp->getDbSession(), params)};
|
const auto tracks{ Track::find(LmsApp->getDbSession(), params) };
|
||||||
|
return details::createZipper(tracks.results);
|
||||||
std::vector<Track::pointer> tracks;
|
}
|
||||||
tracks.reserve(trackResults.results.size());
|
|
||||||
std::transform(std::cbegin(trackResults.results), std::cend(trackResults.results), std::back_inserter(tracks), [](TrackId trackId){ return Track::find(LmsApp->getDbSession(), trackId); });
|
|
||||||
|
|
||||||
return details::createZipper(tracks);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ dumpTrackCovers(Database::Session& session, Image::ImageSize width)
|
|||||||
RangeResults<Database::TrackId> trackIds;
|
RangeResults<Database::TrackId> trackIds;
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction {session.createSharedTransaction()};
|
||||||
trackIds = Database::Track::find(session, Database::Track::FindParameters {});
|
trackIds = Database::Track::findIds(session, Database::Track::FindParameters {});
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const Database::TrackId trackId : trackIds.results)
|
for (const Database::TrackId trackId : trackIds.results)
|
||||||
|
|||||||
@@ -38,14 +38,12 @@
|
|||||||
|
|
||||||
using namespace Database;
|
using namespace Database;
|
||||||
|
|
||||||
static
|
static void dumpTracksRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount)
|
||||||
void
|
|
||||||
dumpTracksRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount)
|
|
||||||
{
|
{
|
||||||
const RangeResults<TrackId> trackIds{ [&]
|
const RangeResults<TrackId> trackIds{ [&]
|
||||||
{
|
{
|
||||||
auto transaction {session.createSharedTransaction()};
|
auto transaction {session.createSharedTransaction()};
|
||||||
return Track::find(session, Track::FindParameters {});
|
return Track::findIds(session, Track::FindParameters{});
|
||||||
}() };
|
}() };
|
||||||
|
|
||||||
std::cout << "*** Tracks (" << trackIds.results.size() << ") ***" << std::endl;
|
std::cout << "*** Tracks (" << trackIds.results.size() << ") ***" << std::endl;
|
||||||
@@ -74,9 +72,7 @@ dumpTracksRecommendation(Session session, Recommendation::IRecommendationService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void dumpReleasesRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount)
|
||||||
void
|
|
||||||
dumpReleasesRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount)
|
|
||||||
{
|
{
|
||||||
const RangeResults<ReleaseId> releaseIds{ std::invoke([&]
|
const RangeResults<ReleaseId> releaseIds{ std::invoke([&]
|
||||||
{
|
{
|
||||||
@@ -101,9 +97,7 @@ dumpReleasesRecommendation(Session session, Recommendation::IRecommendationServi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static
|
static void dumpArtistsRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount)
|
||||||
void
|
|
||||||
dumpArtistsRecommendation(Session session, Recommendation::IRecommendationService& recommendationService, unsigned maxSimilarityCount)
|
|
||||||
{
|
{
|
||||||
const RangeResults<ArtistId> artistIds = std::invoke([&]()
|
const RangeResults<ArtistId> artistIds = std::invoke([&]()
|
||||||
{
|
{
|
||||||
@@ -130,7 +124,6 @@ dumpArtistsRecommendation(Session session, Recommendation::IRecommendationServic
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int main(int argc, char* argv[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
Reference in New Issue
Block a user