SubsonicAPI: optimized search for tracks
This commit is contained in:
@@ -34,445 +34,425 @@
|
||||
#include "utils/String.hpp"
|
||||
#include "utils/Utils.hpp"
|
||||
|
||||
namespace
|
||||
namespace Cover
|
||||
{
|
||||
struct TrackInfo
|
||||
{
|
||||
bool hasCover {};
|
||||
bool isMultiDisc {};
|
||||
std::filesystem::path trackPath;
|
||||
std::optional<Database::ReleaseId> releaseId;
|
||||
};
|
||||
|
||||
std::optional<TrackInfo>
|
||||
getTrackInfo(Database::Session& dbSession, Database::TrackId trackId)
|
||||
{
|
||||
std::optional<TrackInfo> res;
|
||||
namespace
|
||||
{
|
||||
struct TrackInfo
|
||||
{
|
||||
bool hasCover{};
|
||||
bool isMultiDisc{};
|
||||
std::filesystem::path trackPath;
|
||||
std::optional<Database::ReleaseId> releaseId;
|
||||
};
|
||||
|
||||
auto transaction {dbSession.createSharedTransaction()};
|
||||
std::optional<TrackInfo> getTrackInfo(Database::Session& dbSession, Database::TrackId trackId)
|
||||
{
|
||||
std::optional<TrackInfo> res;
|
||||
|
||||
const Database::Track::pointer track {Database::Track::find(dbSession, trackId)};
|
||||
if (!track)
|
||||
return res;
|
||||
auto transaction{ dbSession.createSharedTransaction() };
|
||||
|
||||
res = TrackInfo {};
|
||||
const Database::Track::pointer track{ Database::Track::find(dbSession, trackId) };
|
||||
if (!track)
|
||||
return res;
|
||||
|
||||
res->hasCover = track->hasCover();
|
||||
res->trackPath = track->getPath();
|
||||
res = TrackInfo{};
|
||||
|
||||
if (const Database::Release::pointer& release {track->getRelease()})
|
||||
{
|
||||
res->releaseId = release->getId();
|
||||
if (release->getTotalDisc() > 1)
|
||||
res->isMultiDisc = true;
|
||||
}
|
||||
res->hasCover = track->hasCover();
|
||||
res->trackPath = track->getPath();
|
||||
|
||||
return res;
|
||||
}
|
||||
if (const Database::Release::pointer & release{ track->getRelease() })
|
||||
{
|
||||
res->releaseId = release->getId();
|
||||
if (release->getTotalDisc() > 1)
|
||||
res->isMultiDisc = true;
|
||||
}
|
||||
|
||||
std::vector<std::string> constructPreferredFileNames()
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
return res;
|
||||
}
|
||||
|
||||
Service<IConfig>::get()->visitStrings("cover-preferred-file-names",
|
||||
[&res](std::string_view fileName)
|
||||
{
|
||||
res.emplace_back(fileName);
|
||||
}, {"cover", "front"});
|
||||
std::vector<std::string> constructPreferredFileNames()
|
||||
{
|
||||
std::vector<std::string> res;
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
Service<IConfig>::get()->visitStrings("cover-preferred-file-names",
|
||||
[&res](std::string_view fileName)
|
||||
{
|
||||
res.emplace_back(fileName);
|
||||
}, { "cover", "front" });
|
||||
|
||||
namespace Cover {
|
||||
return res;
|
||||
}
|
||||
|
||||
using namespace Image;
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
std::unique_ptr<ICoverService> createCoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath)
|
||||
{
|
||||
return std::make_unique<CoverService>(db, execPath, defaultCoverPath);
|
||||
}
|
||||
|
||||
std::unique_ptr<ICoverService>
|
||||
createCoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath)
|
||||
{
|
||||
return std::make_unique<CoverService>(db, execPath, defaultCoverPath);
|
||||
}
|
||||
using namespace Image;
|
||||
|
||||
CoverService::CoverService(Database::Db& db,
|
||||
const std::filesystem::path& execPath,
|
||||
const std::filesystem::path& defaultCoverPath)
|
||||
: _db {db}
|
||||
, _defaultCoverPath {defaultCoverPath}
|
||||
, _maxCacheSize {Service<IConfig>::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000}
|
||||
, _maxFileSize {Service<IConfig>::get()->getULong("cover-max-file-size", 10) * 1000 * 1000}
|
||||
, _preferredFileNames {constructPreferredFileNames()}
|
||||
CoverService::CoverService(Database::Db& db,
|
||||
const std::filesystem::path& execPath,
|
||||
const std::filesystem::path& defaultCoverPath)
|
||||
: _db{ db }
|
||||
, _defaultCoverPath{ defaultCoverPath }
|
||||
, _maxCacheSize{ Service<IConfig>::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 }
|
||||
, _maxFileSize{ Service<IConfig>::get()->getULong("cover-max-file-size", 10) * 1000 * 1000 }
|
||||
, _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) << "Max cache size = " << _maxCacheSize;
|
||||
LMS_LOG(COVER, INFO) << "Max file size = " << _maxFileSize;
|
||||
LMS_LOG(COVER, INFO) << "Preferred file names: " << StringUtils::joinStrings(_preferredFileNames, ",");
|
||||
LMS_LOG(COVER, INFO) << "Default cover path = '" << _defaultCoverPath.string() << "'";
|
||||
LMS_LOG(COVER, INFO) << "Max cache size = " << _maxCacheSize;
|
||||
LMS_LOG(COVER, INFO) << "Max file size = " << _maxFileSize;
|
||||
LMS_LOG(COVER, INFO) << "Preferred file names: " << StringUtils::joinStrings(_preferredFileNames, ",");
|
||||
|
||||
#if LMS_SUPPORT_IMAGE_GM
|
||||
GraphicsMagick::init(execPath);
|
||||
GraphicsMagick::init(execPath);
|
||||
#else
|
||||
(void)execPath;
|
||||
(void)execPath;
|
||||
#endif
|
||||
|
||||
try
|
||||
{
|
||||
getDefault(512);
|
||||
}
|
||||
catch (const Image::ImageException& e)
|
||||
{
|
||||
throw LmsException("Cannot read default cover file '" + _defaultCoverPath.string() + "': " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage>
|
||||
CoverService::getFromAvMediaFile(const Av::IAudioFile& input, ImageSize width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
|
||||
input.visitAttachedPictures([&](const Av::Picture& picture)
|
||||
{
|
||||
if (image)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
std::unique_ptr<IRawImage> rawImage {decodeImage(picture.data, picture.dataSize)};
|
||||
rawImage->resize(width);
|
||||
image = rawImage->encodeToJPEG(_jpegQuality);
|
||||
}
|
||||
catch (const Image::ImageException& e)
|
||||
{
|
||||
LMS_LOG(COVER, ERROR) << "Cannot read embedded cover: " << e.what();
|
||||
}
|
||||
});
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage>
|
||||
CoverService::getFromCoverFile(const std::filesystem::path& p, ImageSize width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
|
||||
try
|
||||
{
|
||||
std::unique_ptr<IRawImage> rawImage {decodeImage(p)};
|
||||
rawImage->resize(width);
|
||||
image = rawImage->encodeToJPEG(_jpegQuality);
|
||||
}
|
||||
catch (const ImageException& e)
|
||||
{
|
||||
LMS_LOG(COVER, ERROR) << "Cannot read cover in file '" << p.string() << "': " << e.what();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage>
|
||||
CoverService::getDefault(ImageSize width)
|
||||
{
|
||||
{
|
||||
std::shared_lock lock {_cacheMutex};
|
||||
|
||||
if (auto it {_defaultCoverCache.find(width)}; it != std::cend(_defaultCoverCache))
|
||||
return it->second;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock lock {_cacheMutex};
|
||||
|
||||
if (auto it {_defaultCoverCache.find(width)}; it != std::cend(_defaultCoverCache))
|
||||
return it->second;
|
||||
|
||||
std::shared_ptr<IEncodedImage> image {getFromCoverFile(_defaultCoverPath, width)};
|
||||
_defaultCoverCache[width] = image;
|
||||
LMS_LOG(COVER, DEBUG) << "Default cache entries = " << _defaultCoverCache.size();
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage>
|
||||
CoverService::getFromDirectory(const std::filesystem::path& directory, ImageSize width) const
|
||||
{
|
||||
const std::multimap<std::string, std::filesystem::path> coverPaths {getCoverPaths(directory)};
|
||||
|
||||
auto tryLoadImageFromFilename = [&](std::string_view fileName)
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
|
||||
auto range {coverPaths.equal_range(std::string {fileName})};
|
||||
for (auto it {range.first}; it != range.second; ++it)
|
||||
{
|
||||
image = getFromCoverFile(it->second, width);
|
||||
if (image)
|
||||
break;
|
||||
}
|
||||
return image;
|
||||
};
|
||||
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
|
||||
for (std::string_view filename : _preferredFileNames)
|
||||
{
|
||||
image = tryLoadImageFromFilename(filename);
|
||||
if (image)
|
||||
return image;
|
||||
}
|
||||
|
||||
// Just pick one
|
||||
for (const auto& [filename, coverPath] : coverPaths)
|
||||
{
|
||||
image = getFromCoverFile(coverPath, width);
|
||||
if (image)
|
||||
return image;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage>
|
||||
CoverService::getFromSameNamedFile(const std::filesystem::path& filePath, ImageSize width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> res;
|
||||
|
||||
std::filesystem::path coverPath {filePath};
|
||||
for (const std::filesystem::path& extension : _fileExtensions)
|
||||
{
|
||||
coverPath.replace_extension(extension);
|
||||
|
||||
if (!checkCoverFile(coverPath))
|
||||
continue;
|
||||
|
||||
res = getFromCoverFile(coverPath, width);
|
||||
if (res)
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool
|
||||
CoverService::checkCoverFile(const std::filesystem::path& filePath) const
|
||||
{
|
||||
std::error_code ec;
|
||||
|
||||
if (!isFileSupported(filePath, _fileExtensions))
|
||||
return false;
|
||||
|
||||
if (!std::filesystem::exists(filePath, ec))
|
||||
return false;
|
||||
|
||||
if (!std::filesystem::is_regular_file(filePath, ec))
|
||||
return false;
|
||||
|
||||
if (std::filesystem::file_size(filePath, ec) > _maxFileSize && !ec)
|
||||
{
|
||||
LMS_LOG(COVER, INFO) << "Cover file '" << filePath.string() << " is too big (" << std::filesystem::file_size(filePath, ec) << "), limit is " << _maxFileSize;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::multimap<std::string, std::filesystem::path>
|
||||
CoverService::getCoverPaths(const std::filesystem::path& directoryPath) const
|
||||
{
|
||||
std::multimap<std::string, std::filesystem::path> res;
|
||||
std::error_code ec;
|
||||
|
||||
std::filesystem::directory_iterator itPath(directoryPath, ec);
|
||||
std::filesystem::directory_iterator itEnd;
|
||||
while (!ec && itPath != itEnd)
|
||||
{
|
||||
const std::filesystem::path& path {*itPath};
|
||||
|
||||
if (checkCoverFile(path))
|
||||
res.emplace(std::filesystem::path{ path }.filename().replace_extension("").string(), path);
|
||||
|
||||
itPath.increment(ec);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage>
|
||||
CoverService::getFromTrack(const std::filesystem::path& p, ImageSize width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
|
||||
try
|
||||
{
|
||||
image = getFromAvMediaFile(*Av::parseAudioFile(p), width);
|
||||
}
|
||||
catch (Av::Exception& e)
|
||||
{
|
||||
LMS_LOG(COVER, ERROR) << "Cannot get covers from track " << p.string() << ": " << e.what();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage>
|
||||
CoverService::getFromTrack(Database::TrackId trackId, ImageSize width)
|
||||
{
|
||||
return getFromTrack(_db.getTLSSession(), trackId, width, true /* allow release fallback*/);
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage>
|
||||
CoverService::getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width, bool allowReleaseFallback)
|
||||
{
|
||||
using namespace Database;
|
||||
|
||||
const CacheEntryDesc cacheEntryDesc {trackId, width};
|
||||
|
||||
std::shared_ptr<IEncodedImage> cover {loadFromCache(cacheEntryDesc)};
|
||||
if (cover)
|
||||
return cover;
|
||||
|
||||
if (const std::optional<TrackInfo> trackInfo {getTrackInfo(dbSession, trackId)})
|
||||
{
|
||||
if (trackInfo->hasCover)
|
||||
cover = getFromTrack(trackInfo->trackPath, width);
|
||||
|
||||
if (!cover)
|
||||
cover = getFromSameNamedFile(trackInfo->trackPath, width);
|
||||
|
||||
if (!cover && trackInfo->releaseId && allowReleaseFallback)
|
||||
cover = getFromRelease(*trackInfo->releaseId, width);
|
||||
|
||||
if (!cover && trackInfo->isMultiDisc)
|
||||
{
|
||||
if (trackInfo->trackPath.parent_path().has_parent_path())
|
||||
cover = getFromDirectory(trackInfo->trackPath.parent_path().parent_path(), width);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cover)
|
||||
cover = getDefault(width);
|
||||
|
||||
if (cover)
|
||||
saveToCache(cacheEntryDesc, cover);
|
||||
|
||||
return cover;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage>
|
||||
CoverService::getFromRelease(Database::ReleaseId releaseId, ImageSize width)
|
||||
{
|
||||
using namespace Database;
|
||||
const CacheEntryDesc cacheEntryDesc {releaseId, width};
|
||||
|
||||
std::shared_ptr<IEncodedImage> cover {loadFromCache(cacheEntryDesc)};
|
||||
if (cover)
|
||||
return cover;
|
||||
|
||||
struct ReleaseInfo
|
||||
{
|
||||
TrackId firstTrackId;
|
||||
std::filesystem::path releaseDirectory;
|
||||
};
|
||||
|
||||
Session& session {_db.getTLSSession()};
|
||||
|
||||
auto getReleaseInfo {[&]
|
||||
{
|
||||
std::optional<ReleaseInfo> res;
|
||||
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
|
||||
const auto tracks {Track::find(session, Track::FindParameters {}.setRelease(releaseId).setRange({0, 1}).setSortMethod(TrackSortMethod::Release))};
|
||||
|
||||
if (!tracks.results.empty())
|
||||
{
|
||||
if (const Track::pointer track {Track::find(session, tracks.results.front())})
|
||||
{
|
||||
res = ReleaseInfo {};
|
||||
res->firstTrackId = track->getId();
|
||||
res->releaseDirectory = track->getPath().parent_path();
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}};
|
||||
|
||||
if (const std::optional<ReleaseInfo> releaseInfo {getReleaseInfo()})
|
||||
{
|
||||
cover = getFromDirectory(releaseInfo->releaseDirectory, width);
|
||||
if (!cover)
|
||||
cover = getFromTrack(session, releaseInfo->firstTrackId, width, false /* no release fallback */);
|
||||
}
|
||||
|
||||
if (!cover)
|
||||
cover = getDefault(width);
|
||||
|
||||
if (cover)
|
||||
saveToCache(cacheEntryDesc, cover);
|
||||
|
||||
return cover;
|
||||
}
|
||||
|
||||
void
|
||||
CoverService::flushCache()
|
||||
{
|
||||
std::unique_lock lock {_cacheMutex};
|
||||
|
||||
LMS_LOG(COVER, DEBUG) << "Cache stats: hits = " << _cacheHits << ", misses = " << _cacheMisses << ", nb entries = " << _cache.size() << ", size = " << _cacheSize;
|
||||
_cacheHits = 0;
|
||||
_cacheMisses = 0;
|
||||
_cacheSize = 0;
|
||||
_cache.clear();
|
||||
}
|
||||
|
||||
void
|
||||
CoverService::setJpegQuality(unsigned quality)
|
||||
{
|
||||
_jpegQuality = Utils::clamp<unsigned>(quality, 1, 100);
|
||||
|
||||
LMS_LOG(COVER, INFO) << "JPEG export quality = " << _jpegQuality;
|
||||
}
|
||||
|
||||
void
|
||||
CoverService::saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr<IEncodedImage> image)
|
||||
{
|
||||
std::unique_lock lock {_cacheMutex};
|
||||
|
||||
while (_cacheSize + image->getDataSize() > _maxCacheSize && !_cache.empty())
|
||||
{
|
||||
auto itRandom {Random::pickRandom(_cache)};
|
||||
_cacheSize -= itRandom->second->getDataSize();
|
||||
_cache.erase(itRandom);
|
||||
}
|
||||
|
||||
_cacheSize += image->getDataSize();
|
||||
_cache[entryDesc] = image;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage>
|
||||
CoverService::loadFromCache(const CacheEntryDesc& entryDesc)
|
||||
{
|
||||
std::shared_lock lock {_cacheMutex};
|
||||
|
||||
auto it {_cache.find(entryDesc)};
|
||||
if (it == std::cend(_cache))
|
||||
{
|
||||
++_cacheMisses;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
++_cacheHits;
|
||||
return it->second;
|
||||
}
|
||||
try
|
||||
{
|
||||
getDefault(512);
|
||||
}
|
||||
catch (const Image::ImageException& e)
|
||||
{
|
||||
throw LmsException("Cannot read default cover file '" + _defaultCoverPath.string() + "': " + e.what());
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage> CoverService::getFromAvMediaFile(const Av::IAudioFile& input, ImageSize width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
|
||||
input.visitAttachedPictures([&](const Av::Picture& picture)
|
||||
{
|
||||
if (image)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
std::unique_ptr<IRawImage> rawImage{ decodeImage(picture.data, picture.dataSize) };
|
||||
rawImage->resize(width);
|
||||
image = rawImage->encodeToJPEG(_jpegQuality);
|
||||
}
|
||||
catch (const Image::ImageException& e)
|
||||
{
|
||||
LMS_LOG(COVER, ERROR) << "Cannot read embedded cover: " << e.what();
|
||||
}
|
||||
});
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage> CoverService::getFromCoverFile(const std::filesystem::path& p, ImageSize width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
|
||||
try
|
||||
{
|
||||
std::unique_ptr<IRawImage> rawImage{ decodeImage(p) };
|
||||
rawImage->resize(width);
|
||||
image = rawImage->encodeToJPEG(_jpegQuality);
|
||||
}
|
||||
catch (const ImageException& e)
|
||||
{
|
||||
LMS_LOG(COVER, ERROR) << "Cannot read cover in file '" << p.string() << "': " << e.what();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> CoverService::getDefault(ImageSize width)
|
||||
{
|
||||
{
|
||||
std::shared_lock lock{ _cacheMutex };
|
||||
|
||||
if (auto it{ _defaultCoverCache.find(width) }; it != std::cend(_defaultCoverCache))
|
||||
return it->second;
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock lock{ _cacheMutex };
|
||||
|
||||
if (auto it{ _defaultCoverCache.find(width) }; it != std::cend(_defaultCoverCache))
|
||||
return it->second;
|
||||
|
||||
std::shared_ptr<IEncodedImage> image{ getFromCoverFile(_defaultCoverPath, width) };
|
||||
_defaultCoverCache[width] = image;
|
||||
LMS_LOG(COVER, DEBUG) << "Default cache entries = " << _defaultCoverCache.size();
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage> CoverService::getFromDirectory(const std::filesystem::path& directory, ImageSize width) const
|
||||
{
|
||||
const std::multimap<std::string, std::filesystem::path> coverPaths{ getCoverPaths(directory) };
|
||||
|
||||
auto tryLoadImageFromFilename = [&](std::string_view fileName)
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
|
||||
auto range{ coverPaths.equal_range(std::string {fileName}) };
|
||||
for (auto it{ range.first }; it != range.second; ++it)
|
||||
{
|
||||
image = getFromCoverFile(it->second, width);
|
||||
if (image)
|
||||
break;
|
||||
}
|
||||
return image;
|
||||
};
|
||||
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
|
||||
for (std::string_view filename : _preferredFileNames)
|
||||
{
|
||||
image = tryLoadImageFromFilename(filename);
|
||||
if (image)
|
||||
return image;
|
||||
}
|
||||
|
||||
// Just pick one
|
||||
for (const auto& [filename, coverPath] : coverPaths)
|
||||
{
|
||||
image = getFromCoverFile(coverPath, width);
|
||||
if (image)
|
||||
return image;
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage> CoverService::getFromSameNamedFile(const std::filesystem::path& filePath, ImageSize width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> res;
|
||||
|
||||
std::filesystem::path coverPath{ filePath };
|
||||
for (const std::filesystem::path& extension : _fileExtensions)
|
||||
{
|
||||
coverPath.replace_extension(extension);
|
||||
|
||||
if (!checkCoverFile(coverPath))
|
||||
continue;
|
||||
|
||||
res = getFromCoverFile(coverPath, width);
|
||||
if (res)
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool CoverService::checkCoverFile(const std::filesystem::path& filePath) const
|
||||
{
|
||||
std::error_code ec;
|
||||
|
||||
if (!isFileSupported(filePath, _fileExtensions))
|
||||
return false;
|
||||
|
||||
if (!std::filesystem::exists(filePath, ec))
|
||||
return false;
|
||||
|
||||
if (!std::filesystem::is_regular_file(filePath, ec))
|
||||
return false;
|
||||
|
||||
if (std::filesystem::file_size(filePath, ec) > _maxFileSize && !ec)
|
||||
{
|
||||
LMS_LOG(COVER, INFO) << "Cover file '" << filePath.string() << " is too big (" << std::filesystem::file_size(filePath, ec) << "), limit is " << _maxFileSize;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::multimap<std::string, std::filesystem::path> CoverService::getCoverPaths(const std::filesystem::path& directoryPath) const
|
||||
{
|
||||
std::multimap<std::string, std::filesystem::path> res;
|
||||
std::error_code ec;
|
||||
|
||||
std::filesystem::directory_iterator itPath(directoryPath, ec);
|
||||
std::filesystem::directory_iterator itEnd;
|
||||
while (!ec && itPath != itEnd)
|
||||
{
|
||||
const std::filesystem::path& path{ *itPath };
|
||||
|
||||
if (checkCoverFile(path))
|
||||
res.emplace(std::filesystem::path{ path }.filename().replace_extension("").string(), path);
|
||||
|
||||
itPath.increment(ec);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::unique_ptr<IEncodedImage> CoverService::getFromTrack(const std::filesystem::path& p, ImageSize width) const
|
||||
{
|
||||
std::unique_ptr<IEncodedImage> image;
|
||||
|
||||
try
|
||||
{
|
||||
image = getFromAvMediaFile(*Av::parseAudioFile(p), width);
|
||||
}
|
||||
catch (Av::Exception& e)
|
||||
{
|
||||
LMS_LOG(COVER, ERROR) << "Cannot get covers from track " << p.string() << ": " << e.what();
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> CoverService::getFromTrack(Database::TrackId trackId, ImageSize width)
|
||||
{
|
||||
return getFromTrack(_db.getTLSSession(), trackId, width, true /* allow release fallback*/);
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> CoverService::getFromTrack(Database::Session& dbSession, Database::TrackId trackId, ImageSize width, bool allowReleaseFallback)
|
||||
{
|
||||
using namespace Database;
|
||||
|
||||
const CacheEntryDesc cacheEntryDesc{ trackId, width };
|
||||
|
||||
std::shared_ptr<IEncodedImage> cover{ loadFromCache(cacheEntryDesc) };
|
||||
if (cover)
|
||||
return cover;
|
||||
|
||||
if (const std::optional<TrackInfo> trackInfo{ getTrackInfo(dbSession, trackId) })
|
||||
{
|
||||
if (trackInfo->hasCover)
|
||||
cover = getFromTrack(trackInfo->trackPath, width);
|
||||
|
||||
if (!cover)
|
||||
cover = getFromSameNamedFile(trackInfo->trackPath, width);
|
||||
|
||||
if (!cover && trackInfo->releaseId && allowReleaseFallback)
|
||||
cover = getFromRelease(*trackInfo->releaseId, width);
|
||||
|
||||
if (!cover && trackInfo->isMultiDisc)
|
||||
{
|
||||
if (trackInfo->trackPath.parent_path().has_parent_path())
|
||||
cover = getFromDirectory(trackInfo->trackPath.parent_path().parent_path(), width);
|
||||
}
|
||||
}
|
||||
|
||||
if (!cover)
|
||||
cover = getDefault(width);
|
||||
|
||||
if (cover)
|
||||
saveToCache(cacheEntryDesc, cover);
|
||||
|
||||
return cover;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> CoverService::getFromRelease(Database::ReleaseId releaseId, ImageSize width)
|
||||
{
|
||||
using namespace Database;
|
||||
const CacheEntryDesc cacheEntryDesc{ releaseId, width };
|
||||
|
||||
std::shared_ptr<IEncodedImage> cover{ loadFromCache(cacheEntryDesc) };
|
||||
if (cover)
|
||||
return cover;
|
||||
|
||||
struct ReleaseInfo
|
||||
{
|
||||
TrackId firstTrackId;
|
||||
std::filesystem::path releaseDirectory;
|
||||
};
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
|
||||
auto getReleaseInfo{ [&]
|
||||
{
|
||||
std::optional<ReleaseInfo> res;
|
||||
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
const auto tracks{ Track::find(session, Track::FindParameters {}.setRelease(releaseId).setRange({0, 1}).setSortMethod(TrackSortMethod::Release)) };
|
||||
|
||||
if (!tracks.results.empty())
|
||||
{
|
||||
const Track::pointer& track{ tracks.results.front() };
|
||||
res = ReleaseInfo{};
|
||||
res->firstTrackId = track->getId();
|
||||
res->releaseDirectory = track->getPath().parent_path();
|
||||
}
|
||||
|
||||
return res;
|
||||
} };
|
||||
|
||||
if (const std::optional<ReleaseInfo> releaseInfo{ getReleaseInfo() })
|
||||
{
|
||||
cover = getFromDirectory(releaseInfo->releaseDirectory, width);
|
||||
if (!cover)
|
||||
cover = getFromTrack(session, releaseInfo->firstTrackId, width, false /* no release fallback */);
|
||||
}
|
||||
|
||||
if (!cover)
|
||||
cover = getDefault(width);
|
||||
|
||||
if (cover)
|
||||
saveToCache(cacheEntryDesc, cover);
|
||||
|
||||
return cover;
|
||||
}
|
||||
|
||||
void CoverService::flushCache()
|
||||
{
|
||||
std::unique_lock lock{ _cacheMutex };
|
||||
|
||||
LMS_LOG(COVER, DEBUG) << "Cache stats: hits = " << _cacheHits << ", misses = " << _cacheMisses << ", nb entries = " << _cache.size() << ", size = " << _cacheSize;
|
||||
_cacheHits = 0;
|
||||
_cacheMisses = 0;
|
||||
_cacheSize = 0;
|
||||
_cache.clear();
|
||||
}
|
||||
|
||||
void CoverService::setJpegQuality(unsigned quality)
|
||||
{
|
||||
_jpegQuality = Utils::clamp<unsigned>(quality, 1, 100);
|
||||
|
||||
LMS_LOG(COVER, INFO) << "JPEG export quality = " << _jpegQuality;
|
||||
}
|
||||
|
||||
void CoverService::saveToCache(const CacheEntryDesc& entryDesc, std::shared_ptr<IEncodedImage> image)
|
||||
{
|
||||
std::unique_lock lock{ _cacheMutex };
|
||||
|
||||
while (_cacheSize + image->getDataSize() > _maxCacheSize && !_cache.empty())
|
||||
{
|
||||
auto itRandom{ Random::pickRandom(_cache) };
|
||||
_cacheSize -= itRandom->second->getDataSize();
|
||||
_cache.erase(itRandom);
|
||||
}
|
||||
|
||||
_cacheSize += image->getDataSize();
|
||||
_cache[entryDesc] = image;
|
||||
}
|
||||
|
||||
std::shared_ptr<IEncodedImage> CoverService::loadFromCache(const CacheEntryDesc& entryDesc)
|
||||
{
|
||||
std::shared_lock lock{ _cacheMutex };
|
||||
|
||||
auto it{ _cache.find(entryDesc) };
|
||||
if (it == std::cend(_cache))
|
||||
{
|
||||
++_cacheMisses;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
++_cacheHits;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
} // namespace Cover
|
||||
|
||||
|
||||
@@ -39,11 +39,12 @@ namespace Database
|
||||
{
|
||||
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();
|
||||
|
||||
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());
|
||||
for (std::string_view keyword : params.keywords)
|
||||
@@ -168,6 +169,21 @@ namespace Database
|
||||
|
||||
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)
|
||||
@@ -257,7 +273,7 @@ namespace Database
|
||||
return res;
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Track::findTrackMBIDDuplicates(Session& session, Range range)
|
||||
RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, Range range)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
|
||||
@@ -267,7 +283,7 @@ namespace Database
|
||||
return Utils::execQuery(query, range);
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Track::findWithRecordingMBIDAndMissingFeatures(Session& session, Range range)
|
||||
RangeResults<TrackId> Track::findIdsWithRecordingMBIDAndMissingFeatures(Session& session, Range range)
|
||||
{
|
||||
session.checkSharedLocked();
|
||||
|
||||
@@ -295,16 +311,23 @@ namespace Database
|
||||
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();
|
||||
|
||||
auto query{ createQuery(session, parameters) };
|
||||
|
||||
auto query{ createQuery<TrackId>(session, parameters) };
|
||||
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());
|
||||
session.checkSharedLocked();
|
||||
|
||||
@@ -21,11 +21,10 @@
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <ostream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@@ -111,12 +110,13 @@ class Track final : public Object<Track, TrackId>
|
||||
static bool exists(Session& session, TrackId id);
|
||||
static std::vector<pointer> findByRecordingMBID(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<TrackId> findTrackMBIDDuplicates(Session& session, Range range);
|
||||
static RangeResults<TrackId> findWithRecordingMBIDAndMissingFeatures(Session& session, Range range);
|
||||
static RangeResults<TrackId> findIdsTrackMBIDDuplicates(Session& session, Range range);
|
||||
static RangeResults<TrackId> findIdsWithRecordingMBIDAndMissingFeatures(Session& session, Range range);
|
||||
|
||||
// Accessors
|
||||
void setScanVersion(std::size_t version) { _scanVersion = version; }
|
||||
|
||||
@@ -23,415 +23,414 @@ using namespace Database;
|
||||
|
||||
TEST_F(DatabaseFixture, Artist)
|
||||
{
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
EXPECT_FALSE(Artist::exists(session, 35));
|
||||
EXPECT_FALSE(Artist::exists(session, 0));
|
||||
EXPECT_FALSE(Artist::exists(session, 1));
|
||||
EXPECT_EQ(Artist::getCount(session), 0);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
EXPECT_FALSE(Artist::exists(session, 35));
|
||||
EXPECT_FALSE(Artist::exists(session, 0));
|
||||
EXPECT_FALSE(Artist::exists(session, 1));
|
||||
EXPECT_EQ(Artist::getCount(session), 0);
|
||||
}
|
||||
|
||||
ScopedArtist artist {session, "MyArtist"};
|
||||
ScopedArtist artist{ session, "MyArtist" };
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
EXPECT_TRUE(artist.get());
|
||||
EXPECT_FALSE(!artist.get());
|
||||
EXPECT_EQ(artist.get()->getId(), artist.getId());
|
||||
EXPECT_TRUE(artist.get());
|
||||
EXPECT_FALSE(!artist.get());
|
||||
EXPECT_EQ(artist.get()->getId(), artist.getId());
|
||||
|
||||
EXPECT_TRUE(Artist::exists(session, artist.getId()));
|
||||
EXPECT_EQ(Artist::getCount(session), 1);
|
||||
}
|
||||
EXPECT_TRUE(Artist::exists(session, artist.getId()));
|
||||
EXPECT_EQ(Artist::getCount(session), 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
auto artists {Artist::find(session, Artist::FindParameters {})};
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
auto artists{ Artist::find(session, Artist::FindParameters {}) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
|
||||
artists = Artist::findAllOrphans(session, Range {});
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
}
|
||||
artists = Artist::findAllOrphans(session, Range{});
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_singleTrack)
|
||||
{
|
||||
ScopedTrack track {session, "MyTrack"};
|
||||
ScopedArtist artist {session, "MyArtist"};
|
||||
ScopedTrack track{ session, "MyTrack" };
|
||||
ScopedArtist artist{ session, "MyArtist" };
|
||||
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
|
||||
track.get().modify()->setName("MyTrackName");
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
track.get().modify()->setName("MyTrackName");
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
EXPECT_TRUE(Artist::findAllOrphans(session, Range {}).results.empty());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
auto artists {track->getArtists({TrackArtistLinkType::Artist})};
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||
auto artists{ track->getArtists({TrackArtistLinkType::Artist}) };
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||
|
||||
ASSERT_EQ(track->getArtistLinks().size(), 1);
|
||||
auto artistLink {track->getArtistLinks().front()};
|
||||
EXPECT_EQ(artistLink->getTrack()->getId(), track.getId());
|
||||
EXPECT_EQ(artistLink->getArtist()->getId(), artist.getId());
|
||||
ASSERT_EQ(track->getArtistLinks().size(), 1);
|
||||
auto artistLink{ track->getArtistLinks().front() };
|
||||
EXPECT_EQ(artistLink->getTrack()->getId(), track.getId());
|
||||
EXPECT_EQ(artistLink->getArtist()->getId(), artist.getId());
|
||||
|
||||
ASSERT_EQ(track->getArtists({TrackArtistLinkType::Artist}).size(), 1);
|
||||
EXPECT_TRUE(track->getArtists({TrackArtistLinkType::ReleaseArtist}).empty());
|
||||
EXPECT_EQ(track->getArtists({}).size(), 1);
|
||||
}
|
||||
ASSERT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 1);
|
||||
EXPECT_TRUE(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).empty());
|
||||
EXPECT_EQ(track->getArtists({}).size(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
auto artists {track->getArtistIds({TrackArtistLinkType::Artist})};
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
auto artists{ track->getArtistIds({TrackArtistLinkType::Artist}) };
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
|
||||
ASSERT_EQ(track->getArtistIds({TrackArtistLinkType::Artist}).size(), 1);
|
||||
EXPECT_TRUE(track->getArtistIds({TrackArtistLinkType::ReleaseArtist}).empty());
|
||||
EXPECT_EQ(track->getArtistIds({}).size(), 1);
|
||||
}
|
||||
ASSERT_EQ(track->getArtistIds({ TrackArtistLinkType::Artist }).size(), 1);
|
||||
EXPECT_TRUE(track->getArtistIds({ TrackArtistLinkType::ReleaseArtist }).empty());
|
||||
EXPECT_EQ(track->getArtistIds({}).size(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
auto tracks {Track::find(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtist"))};
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
}
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
auto tracks {Track::find(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtistFoo"))};
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
auto tracks {Track::find(session, Track::FindParameters{}.setName("MyTrackNameFoo").setArtistName("MyArtist"))};
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
auto artists {Artist::find(session, Artist::FindParameters{}.setTrack(track->getId()))};
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtist")) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtistFoo")) };
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackNameFoo").setArtistName("MyArtist")) };
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
auto artists{ Artist::find(session, Artist::FindParameters{}.setTrack(track->getId())) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_singleTracktMultiRoles)
|
||||
{
|
||||
ScopedTrack track {session, "MyTrack"};
|
||||
ScopedArtist artist {session, "MyArtist"};
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
ScopedTrack track{ session, "MyTrack" };
|
||||
ScopedArtist artist{ session, "MyArtist" };
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist);
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Writer);
|
||||
}
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist);
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Writer);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
EXPECT_TRUE(Artist::findAllOrphans(session, Range {}).results.empty());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
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::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::Composer)).results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
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::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::Composer)).results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
auto artists {track->getArtists({TrackArtistLinkType::Artist})};
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||
auto artists{ track->getArtists({TrackArtistLinkType::Artist}) };
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||
|
||||
artists = track->getArtists({TrackArtistLinkType::ReleaseArtist});
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||
artists = track->getArtists({ TrackArtistLinkType::ReleaseArtist });
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
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()))};
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist.getId())) };
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
|
||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::ReleaseArtist}));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Artist}));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Writer}));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Writer }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
|
||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Composer}));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer }));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
EnumSet<TrackArtistLinkType> types{ TrackArtistLink::findUsedTypes(session, artist.getId()) };
|
||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::ReleaseArtist));
|
||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::Artist));
|
||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::Writer));
|
||||
EXPECT_FALSE(types.contains(TrackArtistLinkType::Composer));
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
EnumSet<TrackArtistLinkType> types{ TrackArtistLink::findUsedTypes(session, artist.getId()) };
|
||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::ReleaseArtist));
|
||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::Artist));
|
||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::Writer));
|
||||
EXPECT_FALSE(types.contains(TrackArtistLinkType::Composer));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_singleTrackMultiArtists)
|
||||
{
|
||||
ScopedTrack track {session, "track"};
|
||||
ScopedArtist artist1 {session, "artist1"};
|
||||
ScopedArtist artist2 {session, "artist2"};
|
||||
ASSERT_NE(artist1.getId(), artist2.getId());
|
||||
ScopedTrack track{ session, "track" };
|
||||
ScopedArtist artist1{ session, "artist1" };
|
||||
ScopedArtist artist2{ session, "artist2" };
|
||||
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(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
TrackArtistLink::create(session, track.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
EXPECT_TRUE(Artist::findAllOrphans(session, Range {}).results.empty());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
EXPECT_TRUE(Artist::findAllOrphans(session, Range{}).results.empty());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
auto artists {track->getArtists({TrackArtistLinkType::Artist})};
|
||||
ASSERT_EQ(artists.size(), 2);
|
||||
EXPECT_TRUE((artists[0]->getId() == artist1.getId() && artists[1]->getId() == artist2.getId())
|
||||
|| (artists[0]->getId() == artist2.getId() && artists[1]->getId() == artist1.getId()));
|
||||
auto artists{ track->getArtists({TrackArtistLinkType::Artist}) };
|
||||
ASSERT_EQ(artists.size(), 2);
|
||||
EXPECT_TRUE((artists[0]->getId() == artist1.getId() && artists[1]->getId() == artist2.getId())
|
||||
|| (artists[0]->getId() == artist2.getId() && artists[1]->getId() == artist1.getId()));
|
||||
|
||||
EXPECT_EQ(track->getArtists({}).size(), 2);
|
||||
EXPECT_EQ(track->getArtists({TrackArtistLinkType::Artist}).size(), 2);
|
||||
EXPECT_TRUE(track->getArtists({TrackArtistLinkType::ReleaseArtist}).empty());
|
||||
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(track->getArtists({}).size(), 2);
|
||||
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 2);
|
||||
EXPECT_TRUE(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).empty());
|
||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}).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()))};
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track->getId());
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist1->getId())) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track->getId());
|
||||
|
||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist2->getId()));
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track->getId());
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId()));
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track->getId());
|
||||
|
||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist1->getId(), {TrackArtistLinkType::ReleaseArtist}));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::ReleaseArtist }));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
|
||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist1->getId(), {TrackArtistLinkType::Artist}));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::Artist }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
|
||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist2->getId(), {TrackArtistLinkType::ReleaseArtist}));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::ReleaseArtist }));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
|
||||
tracks = Track::find(session, Track::FindParameters {}.setArtist(artist2->getId(), {TrackArtistLinkType::Artist}));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
}
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::Artist }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_findByName)
|
||||
{
|
||||
ScopedArtist artist {session, "AAA"};
|
||||
ScopedTrack track {session, "MyTrack"}; // filters does not work on orphans
|
||||
ScopedArtist artist{ session, "AAA" };
|
||||
ScopedTrack track{ session, "MyTrack" }; // filters does not work on orphans
|
||||
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
artist.get().modify()->setSortName("ZZZ");
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
artist.get().modify()->setSortName("ZZZ");
|
||||
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"}))};
|
||||
ASSERT_EQ(artistsByAAA.results.size(), 1);
|
||||
EXPECT_EQ(artistsByAAA.results.front(), artist.getId());
|
||||
const auto artistsByAAA{ Artist::find(session, Artist::FindParameters {}.setKeywords({"A"})) };
|
||||
ASSERT_EQ(artistsByAAA.results.size(), 1);
|
||||
EXPECT_EQ(artistsByAAA.results.front(), artist.getId());
|
||||
|
||||
const auto artistsByZZZ {Artist::Artist::find(session, Artist::FindParameters {}.setKeywords({"Z"}))};
|
||||
ASSERT_EQ(artistsByZZZ.results.size(), 1);
|
||||
EXPECT_EQ(artistsByZZZ.results.front(), artist.getId());
|
||||
const auto artistsByZZZ{ Artist::Artist::find(session, Artist::FindParameters {}.setKeywords({"Z"})) };
|
||||
ASSERT_EQ(artistsByZZZ.results.size(), 1);
|
||||
EXPECT_EQ(artistsByZZZ.results.front(), artist.getId());
|
||||
|
||||
EXPECT_TRUE(Artist::find(session, "NNN").empty());
|
||||
EXPECT_EQ(Artist::find(session, "AAA").size(), 1);
|
||||
}
|
||||
EXPECT_TRUE(Artist::find(session, "NNN").empty());
|
||||
EXPECT_EQ(Artist::find(session, "AAA").size(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_findByNameEscaped)
|
||||
{
|
||||
ScopedArtist artist1 {session, "MyArtist%"};
|
||||
ScopedArtist artist2 {session, "%MyArtist"};
|
||||
ScopedArtist artist3 {session, "%_MyArtist"};
|
||||
ScopedArtist artist1{ session, "MyArtist%" };
|
||||
ScopedArtist artist2{ session, "%MyArtist" };
|
||||
ScopedArtist artist3{ session, "%_MyArtist" };
|
||||
|
||||
ScopedArtist artist4 {session, "MyArtist%foo"};
|
||||
ScopedArtist artist5 {session, "foo%MyArtist"};
|
||||
ScopedArtist artist6 {session, "%AMyArtist"};
|
||||
ScopedArtist artist4{ session, "MyArtist%foo" };
|
||||
ScopedArtist artist5{ session, "foo%MyArtist" };
|
||||
ScopedArtist artist6{ session, "%AMyArtist" };
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
const auto artists {Artist::find(session, "MyArtist%")};
|
||||
ASSERT_TRUE(artists.size() == 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist1.getId());
|
||||
EXPECT_TRUE(Artist::find(session, "MyArtistFoo").empty());
|
||||
}
|
||||
{
|
||||
const auto artists {Artist::find(session, "%MyArtist")};
|
||||
ASSERT_TRUE(artists.size() == 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist2.getId());
|
||||
EXPECT_TRUE(Artist::find(session, "FooMyArtist").empty());
|
||||
}
|
||||
{
|
||||
const auto artists {Artist::find(session, "%_MyArtist")};
|
||||
ASSERT_TRUE(artists.size() == 1);
|
||||
ASSERT_EQ(artists.front()->getId(), artist3.getId());
|
||||
EXPECT_TRUE(Artist::find(session, "%CMyArtist").empty());
|
||||
}
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
{
|
||||
const auto artists{ Artist::find(session, "MyArtist%") };
|
||||
ASSERT_TRUE(artists.size() == 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist1.getId());
|
||||
EXPECT_TRUE(Artist::find(session, "MyArtistFoo").empty());
|
||||
}
|
||||
{
|
||||
const auto artists{ Artist::find(session, "%MyArtist") };
|
||||
ASSERT_TRUE(artists.size() == 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist2.getId());
|
||||
EXPECT_TRUE(Artist::find(session, "FooMyArtist").empty());
|
||||
}
|
||||
{
|
||||
const auto artists{ Artist::find(session, "%_MyArtist") };
|
||||
ASSERT_TRUE(artists.size() == 1);
|
||||
ASSERT_EQ(artists.front()->getId(), artist3.getId());
|
||||
EXPECT_TRUE(Artist::find(session, "%CMyArtist").empty());
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setKeywords({"MyArtist"}))};
|
||||
EXPECT_EQ(artists.results.size(), 6);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
{
|
||||
const auto artists{ Artist::find(session, Artist::FindParameters {}.setKeywords({"MyArtist"})) };
|
||||
EXPECT_EQ(artists.results.size(), 6);
|
||||
}
|
||||
|
||||
{
|
||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setKeywords({"MyArtist%"}).setSortMethod(ArtistSortMethod::ByName))};
|
||||
ASSERT_EQ(artists.results.size(), 2);
|
||||
EXPECT_EQ(artists.results[0], artist1.getId());
|
||||
EXPECT_EQ(artists.results[1], artist4.getId());
|
||||
}
|
||||
{
|
||||
const auto artists{ Artist::find(session, Artist::FindParameters {}.setKeywords({"MyArtist%"}).setSortMethod(ArtistSortMethod::ByName)) };
|
||||
ASSERT_EQ(artists.results.size(), 2);
|
||||
EXPECT_EQ(artists.results[0], artist1.getId());
|
||||
EXPECT_EQ(artists.results[1], artist4.getId());
|
||||
}
|
||||
|
||||
{
|
||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setKeywords({"%MyArtist"}).setSortMethod(ArtistSortMethod::ByName))};
|
||||
ASSERT_EQ(artists.results.size(), 2);
|
||||
EXPECT_EQ(artists.results[0], artist2.getId());
|
||||
EXPECT_EQ(artists.results[1], artist5.getId());
|
||||
}
|
||||
{
|
||||
const auto artists{ Artist::find(session, Artist::FindParameters {}.setKeywords({"%MyArtist"}).setSortMethod(ArtistSortMethod::ByName)) };
|
||||
ASSERT_EQ(artists.results.size(), 2);
|
||||
EXPECT_EQ(artists.results[0], artist2.getId());
|
||||
EXPECT_EQ(artists.results[1], artist5.getId());
|
||||
}
|
||||
|
||||
{
|
||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setKeywords({"_MyArtist"}).setSortMethod(ArtistSortMethod::ByName))};
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results[0], artist3.getId());
|
||||
}
|
||||
}
|
||||
{
|
||||
const auto artists{ Artist::find(session, Artist::FindParameters {}.setKeywords({"_MyArtist"}).setSortMethod(ArtistSortMethod::ByName)) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results[0], artist3.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_sortMethod)
|
||||
{
|
||||
ScopedArtist artistA {session, "artistA"};
|
||||
ScopedArtist artistB {session, "artistB"};
|
||||
ScopedArtist artistA{ session, "artistA" };
|
||||
ScopedArtist artistB{ session, "artistB" };
|
||||
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
|
||||
artistA.get().modify()->setSortName("sortNameB");
|
||||
artistB.get().modify()->setSortName("sortNameA");
|
||||
}
|
||||
artistA.get().modify()->setSortName("sortNameB");
|
||||
artistB.get().modify()->setSortName("sortNameA");
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
auto allArtistsByName {Artist::find(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::ByName))};
|
||||
auto allArtistsBySortName {Artist::find(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::BySortName))};
|
||||
auto allArtistsByName{ Artist::find(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::ByName)) };
|
||||
auto allArtistsBySortName{ Artist::find(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::BySortName)) };
|
||||
|
||||
ASSERT_EQ(allArtistsByName.results.size(), 2);
|
||||
EXPECT_EQ(allArtistsByName.results.front(), artistA.getId());
|
||||
EXPECT_EQ(allArtistsByName.results.back(), artistB.getId());
|
||||
ASSERT_EQ(allArtistsByName.results.size(), 2);
|
||||
EXPECT_EQ(allArtistsByName.results.front(), artistA.getId());
|
||||
EXPECT_EQ(allArtistsByName.results.back(), artistB.getId());
|
||||
|
||||
ASSERT_EQ(allArtistsBySortName.results.size(), 2);
|
||||
EXPECT_EQ(allArtistsBySortName.results.front(), artistB.getId());
|
||||
EXPECT_EQ(allArtistsBySortName.results.back(), artistA.getId());
|
||||
}
|
||||
ASSERT_EQ(allArtistsBySortName.results.size(), 2);
|
||||
EXPECT_EQ(allArtistsBySortName.results.front(), artistB.getId());
|
||||
EXPECT_EQ(allArtistsBySortName.results.back(), artistA.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_nonReleaseTracks)
|
||||
{
|
||||
ScopedArtist artist {session, "artist"};
|
||||
ScopedTrack track1 {session, "MyTrack1"};
|
||||
ScopedTrack track2 {session, "MyTrack2"};
|
||||
ScopedRelease release{session, "MyRelease"};
|
||||
ScopedArtist artist{ session, "artist" };
|
||||
ScopedTrack track1{ session, "MyTrack1" };
|
||||
ScopedTrack track2{ session, "MyTrack2" };
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
auto tracks {Track::find(session, Track::FindParameters {}.setNonRelease(true).setArtist(artist->getId()))};
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters {}.setNonRelease(true).setArtist(artist->getId())) };
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
|
||||
TrackArtistLink::create(session, track1.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track2.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track1.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
TrackArtistLink::create(session, track2.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
|
||||
track1.get().modify()->setRelease(release.get());
|
||||
}
|
||||
track1.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
const auto tracks {Track::find(session, Track::FindParameters {}.setArtist(artist.getId()).setNonRelease(true))};
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track2.getId());
|
||||
}
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist.getId()).setNonRelease(true)) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track2.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Artist_findByRelease)
|
||||
{
|
||||
ScopedArtist artist {session, "artist"};
|
||||
ScopedTrack track {session, "MyTrack"};
|
||||
ScopedRelease release{session, "MyRelease"};
|
||||
ScopedArtist artist{ session, "artist" };
|
||||
ScopedTrack track{ session, "MyTrack" };
|
||||
ScopedRelease release{ session, "MyRelease" };
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setRelease(release.getId()))};
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
const auto artists{ Artist::find(session, Artist::FindParameters {}.setRelease(release.getId())) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setRelease(release.getId()))};
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
const auto artists{ Artist::find(session, Artist::FindParameters {}.setRelease(release.getId())) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
track.get().modify()->setRelease(release.get());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track.get().modify()->setRelease(release.get());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
const auto artists {Artist::find(session, Artist::FindParameters {}.setRelease(release.getId()))};
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
const auto artists{ Artist::find(session, Artist::FindParameters {}.setRelease(release.getId())) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,11 +133,11 @@ TEST_F(DatabaseFixture, Cluster_singleTrack)
|
||||
{
|
||||
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);
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ TEST_F(DatabaseFixture, Cluster_singleTrackWithSeveralClusters)
|
||||
{
|
||||
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());
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
}
|
||||
@@ -550,7 +550,7 @@ TEST_F(DatabaseFixture, MultipleTracksSingleClusterSimilarity)
|
||||
{
|
||||
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);
|
||||
for (const TrackId similarTrackId : similarTracks.results)
|
||||
{
|
||||
@@ -591,14 +591,14 @@ TEST_F(DatabaseFixture, MultipleTracksMultipleClustersSimilarity)
|
||||
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);
|
||||
for (const TrackId similarTrackId : similarTracks.results)
|
||||
EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 5), std::next(std::cend(tracks), -1), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks));
|
||||
}
|
||||
|
||||
{
|
||||
auto similarTracks{ Track::findSimilarTracks(session, {tracks.front().getId()}, Range {}) };
|
||||
auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}, Range {}) };
|
||||
EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1);
|
||||
for (const TrackId similarTrackId : similarTracks.results)
|
||||
EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks));
|
||||
|
||||
@@ -78,7 +78,7 @@ TEST_F(DatabaseFixture, Release_singleTrack)
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
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);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
}
|
||||
@@ -92,18 +92,18 @@ TEST_F(DatabaseFixture, Release_singleTrack)
|
||||
|
||||
{
|
||||
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);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
}
|
||||
{
|
||||
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);
|
||||
}
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,7 @@ TEST_F(DatabaseFixture, Release_singleTrack)
|
||||
{
|
||||
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());
|
||||
|
||||
auto releases{ Release::findOrphanIds(session, Range {}) };
|
||||
@@ -285,8 +285,8 @@ TEST_F(DatabaseFixture, MultiTracksSingleReleaseFirstTrack)
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
EXPECT_TRUE(Track::find(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(release1.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() };
|
||||
|
||||
{
|
||||
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());
|
||||
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());
|
||||
EXPECT_EQ(tracks.results.front(), track2B.getId());
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ TEST_F(DatabaseFixture, StarredTrack)
|
||||
EXPECT_FALSE(starredTrack);
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -53,13 +53,13 @@ TEST_F(DatabaseFixture, StarredTrack)
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
|
||||
auto tracks {Track::find(session, Track::FindParameters {})};
|
||||
auto tracks {Track::findIds(session, Track::FindParameters {})};
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -73,11 +73,11 @@ TEST_F(DatabaseFixture, Starredtrack_PendingDestroy)
|
||||
{
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -96,7 +96,7 @@ TEST_F(DatabaseFixture, StarredTrack_dateTime)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ TEST_F(DatabaseFixture, StarredTrack_dateTime)
|
||||
starredTrack1.get().modify()->setDateTime(dateTime);
|
||||
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);
|
||||
EXPECT_EQ(tracks.results[0], starredTrack1->getTrack()->getId());
|
||||
EXPECT_EQ(tracks.results[1], starredTrack2->getTrack()->getId());
|
||||
@@ -117,7 +117,7 @@ TEST_F(DatabaseFixture, StarredTrack_dateTime)
|
||||
starredTrack1.get().modify()->setDateTime(dateTime);
|
||||
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);
|
||||
EXPECT_EQ(tracks.results[0], starredTrack2->getTrack()->getId());
|
||||
EXPECT_EQ(tracks.results[1], starredTrack1->getTrack()->getId());
|
||||
|
||||
@@ -25,137 +25,138 @@ using namespace Database;
|
||||
|
||||
TEST_F(DatabaseFixture, Track)
|
||||
{
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
EXPECT_EQ(Track::find(session, Track::FindParameters {}).results.size(), 0);
|
||||
EXPECT_EQ(Track::getCount(session), 0);
|
||||
EXPECT_FALSE(Track::exists(session, 0));
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
EXPECT_EQ(Track::find(session, Track::FindParameters{}).results.size(), 0);
|
||||
EXPECT_EQ(Track::findIds(session, Track::FindParameters{}).results.size(), 0);
|
||||
EXPECT_EQ(Track::getCount(session), 0);
|
||||
EXPECT_FALSE(Track::exists(session, 0));
|
||||
}
|
||||
|
||||
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::getCount(session), 1);
|
||||
EXPECT_TRUE(Track::exists(session, track.getId()));
|
||||
auto myTrack {Track::find(session, track.getId())};
|
||||
ASSERT_TRUE(myTrack);
|
||||
EXPECT_EQ(myTrack->getId(), track.getId());
|
||||
}
|
||||
EXPECT_EQ(Track::find(session, Track::FindParameters{}).results.size(), 1);
|
||||
EXPECT_EQ(Track::getCount(session), 1);
|
||||
EXPECT_TRUE(Track::exists(session, track.getId()));
|
||||
auto myTrack{ Track::find(session, track.getId()) };
|
||||
ASSERT_TRUE(myTrack);
|
||||
EXPECT_EQ(myTrack->getId(), track.getId());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, MultipleTracks)
|
||||
{
|
||||
ScopedTrack track1 {session, "MyTrackFile1"};
|
||||
ScopedTrack track2 {session, "MyTrackFile2"};
|
||||
ScopedTrack track1{ session, "MyTrackFile1" };
|
||||
ScopedTrack track2{ session, "MyTrackFile2" };
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
EXPECT_TRUE(track1.getId() != track2.getId());
|
||||
EXPECT_TRUE(track1.get() != track2.get());
|
||||
EXPECT_FALSE(track1.get() == track2.get());
|
||||
}
|
||||
EXPECT_TRUE(track1.getId() != track2.getId());
|
||||
EXPECT_TRUE(track1.get() != track2.get());
|
||||
EXPECT_FALSE(track1.get() == track2.get());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, MultipleTracksSearchByFilter)
|
||||
{
|
||||
ScopedTrack track1 {session, ""};
|
||||
ScopedTrack track2 {session, ""};
|
||||
ScopedTrack track3 {session, ""};
|
||||
ScopedTrack track4 {session, ""};
|
||||
ScopedTrack track5 {session, ""};
|
||||
ScopedTrack track6 {session, ""};
|
||||
ScopedTrack track1{ session, "" };
|
||||
ScopedTrack track2{ session, "" };
|
||||
ScopedTrack track3{ session, "" };
|
||||
ScopedTrack track4{ session, "" };
|
||||
ScopedTrack track5{ session, "" };
|
||||
ScopedTrack track6{ session, "" };
|
||||
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
track1.get().modify()->setName("MyTrack");
|
||||
track2.get().modify()->setName("MyTrack%");
|
||||
track3.get().modify()->setName("MyTrack%Foo");
|
||||
track4.get().modify()->setName("%MyTrack");
|
||||
track5.get().modify()->setName("Foo%MyTrack");
|
||||
track6.get().modify()->setName("M_Track");
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track1.get().modify()->setName("MyTrack");
|
||||
track2.get().modify()->setName("MyTrack%");
|
||||
track3.get().modify()->setName("MyTrack%Foo");
|
||||
track4.get().modify()->setName("%MyTrack");
|
||||
track5.get().modify()->setName("Foo%MyTrack");
|
||||
track6.get().modify()->setName("M_Track");
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
{
|
||||
const auto tracks {Track::find(session, Track::FindParameters {}.setKeywords({"Track"}))};
|
||||
EXPECT_EQ(tracks.results.size(), 6);
|
||||
}
|
||||
{
|
||||
const auto tracks {Track::find(session, Track::FindParameters {}.setKeywords({"MyTrack"}))};
|
||||
EXPECT_EQ(tracks.results.size(), 5);
|
||||
EXPECT_TRUE(std::none_of(std::cbegin(tracks.results), std::cend(tracks.results), [&](const TrackId trackId) { return trackId == track6.getId(); }));
|
||||
}
|
||||
{
|
||||
const auto tracks {Track::find(session, Track::FindParameters {}.setKeywords({"MyTrack%"}))};
|
||||
ASSERT_EQ(tracks.results.size(), 2);
|
||||
EXPECT_EQ(tracks.results[0], track2.getId());
|
||||
EXPECT_EQ(tracks.results[1], track3.getId());
|
||||
}
|
||||
{
|
||||
const auto tracks {Track::find(session, Track::FindParameters {}.setKeywords({"%MyTrack"}))};
|
||||
ASSERT_EQ(tracks.results.size(), 2);
|
||||
EXPECT_EQ(tracks.results[0], track4.getId());
|
||||
EXPECT_EQ(tracks.results[1], track5.getId());
|
||||
}
|
||||
}
|
||||
{
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"Track"})) };
|
||||
EXPECT_EQ(tracks.results.size(), 6);
|
||||
}
|
||||
{
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"MyTrack"})) };
|
||||
EXPECT_EQ(tracks.results.size(), 5);
|
||||
EXPECT_TRUE(std::none_of(std::cbegin(tracks.results), std::cend(tracks.results), [&](const TrackId trackId) { return trackId == track6.getId(); }));
|
||||
}
|
||||
{
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"MyTrack%"})) };
|
||||
ASSERT_EQ(tracks.results.size(), 2);
|
||||
EXPECT_EQ(tracks.results[0], track2.getId());
|
||||
EXPECT_EQ(tracks.results[1], track3.getId());
|
||||
}
|
||||
{
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"%MyTrack"})) };
|
||||
ASSERT_EQ(tracks.results.size(), 2);
|
||||
EXPECT_EQ(tracks.results[0], track4.getId());
|
||||
EXPECT_EQ(tracks.results[1], track5.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Track_date)
|
||||
{
|
||||
ScopedTrack track {session, "MyTrack"};
|
||||
ScopedTrack track{ session, "MyTrack" };
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
EXPECT_EQ(track->getYear(), std::nullopt);
|
||||
EXPECT_EQ(track->getOriginalYear(), std::nullopt);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
EXPECT_EQ(track->getYear(), std::nullopt);
|
||||
EXPECT_EQ(track->getOriginalYear(), std::nullopt);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createUniqueTransaction()};
|
||||
track.get().modify()->setDate(Wt::WDate {1995, 5, 5});
|
||||
track.get().modify()->setOriginalDate(Wt::WDate {1994, 2, 2});
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track.get().modify()->setDate(Wt::WDate{ 1995, 5, 5 });
|
||||
track.get().modify()->setOriginalDate(Wt::WDate{ 1994, 2, 2 });
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
EXPECT_EQ(track->getYear(), 1995);
|
||||
EXPECT_EQ(track->getOriginalYear(), 1994);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
EXPECT_EQ(track->getYear(), 1995);
|
||||
EXPECT_EQ(track->getOriginalYear(), 1994);
|
||||
}
|
||||
}
|
||||
|
||||
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()};
|
||||
track.get().modify()->setLastWriteTime(dateTime);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createUniqueTransaction() };
|
||||
track.get().modify()->setLastWriteTime(dateTime);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
const auto tracks {Track::find(session, Track::FindParameters {})};
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters {}) };
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
const auto tracks {Track::find(session, Track::FindParameters {}.setWrittenAfter(dateTime.addSecs(-1)))};
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setWrittenAfter(dateTime.addSecs(-1))) };
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction {session.createSharedTransaction()};
|
||||
const auto tracks {Track::find(session, Track::FindParameters {}.setWrittenAfter(dateTime.addSecs(+1)))};
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters {}.setWrittenAfter(dateTime.addSecs(+1))) };
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,10 +49,10 @@ TEST_F(DatabaseFixture, SingleTrackListSingleTrack)
|
||||
{
|
||||
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);
|
||||
|
||||
tracks = Track::find(session, Track::FindParameters {}.setTrackList(trackList2.getId()));
|
||||
tracks = Track::findIds(session, Track::FindParameters {}.setTrackList(trackList2.getId()));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
}
|
||||
|
||||
@@ -65,11 +65,11 @@ TEST_F(DatabaseFixture, SingleTrackListSingleTrack)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ namespace Feedback
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createSharedTransaction() };
|
||||
|
||||
return Track::find(session, params);
|
||||
return Track::findIds(session, params);
|
||||
}
|
||||
} // ns Feedback
|
||||
|
||||
|
||||
@@ -30,89 +30,86 @@
|
||||
|
||||
namespace Recommendation
|
||||
{
|
||||
using namespace Database;
|
||||
using namespace Database;
|
||||
|
||||
std::unique_ptr<IPlaylistGeneratorService>
|
||||
createPlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService)
|
||||
{
|
||||
return std::make_unique<PlaylistGeneratorService>(db, recommendationService);
|
||||
}
|
||||
std::unique_ptr<IPlaylistGeneratorService> createPlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService)
|
||||
{
|
||||
return std::make_unique<PlaylistGeneratorService>(db, recommendationService);
|
||||
}
|
||||
|
||||
PlaylistGeneratorService::PlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService)
|
||||
: _db {db}
|
||||
, _recommendationService {recommendationService}
|
||||
{
|
||||
_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::DuplicateTracks>());
|
||||
}
|
||||
PlaylistGeneratorService::PlaylistGeneratorService(Db& db, Recommendation::IRecommendationService& recommendationService)
|
||||
: _db{ db }
|
||||
, _recommendationService{ recommendationService }
|
||||
{
|
||||
_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::DuplicateTracks>());
|
||||
}
|
||||
|
||||
std::vector<TrackId>
|
||||
PlaylistGeneratorService::extendPlaylist(TrackListId tracklistId, std::size_t maxCount) const
|
||||
{
|
||||
LMS_LOG(RECOMMENDATION, DEBUG) << "Requested to extend playlist by " << maxCount << " similar tracks";
|
||||
std::vector<TrackId> PlaylistGeneratorService::extendPlaylist(TrackListId tracklistId, std::size_t maxCount) const
|
||||
{
|
||||
LMS_LOG(RECOMMENDATION, DEBUG) << "Requested to extend playlist by " << maxCount << " similar tracks";
|
||||
|
||||
// supposed to be ordered from most similar to least similar
|
||||
std::vector<TrackId> similarTracks {_recommendationService.findSimilarTracks(tracklistId, maxCount * 2)}; // ask for more tracks than we need as it will be easier to respect constraints
|
||||
// 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
|
||||
|
||||
const std::vector<TrackId> startingTracks {getTracksFromTrackList(tracklistId)};
|
||||
const std::vector<TrackId> startingTracks{ getTracksFromTrackList(tracklistId) };
|
||||
|
||||
std::vector<TrackId> finalResult = startingTracks;
|
||||
finalResult.reserve(startingTracks.size() + maxCount);
|
||||
std::vector<TrackId> finalResult = startingTracks;
|
||||
finalResult.reserve(startingTracks.size() + maxCount);
|
||||
|
||||
std::vector<float> scores;
|
||||
for (std::size_t i {}; i < maxCount; ++i)
|
||||
{
|
||||
if (similarTracks.empty())
|
||||
break;
|
||||
std::vector<float> scores;
|
||||
for (std::size_t i{}; i < maxCount; ++i)
|
||||
{
|
||||
if (similarTracks.empty())
|
||||
break;
|
||||
|
||||
scores.resize(similarTracks.size(), {});
|
||||
scores.resize(similarTracks.size(), {});
|
||||
|
||||
// select the similar track that has the best score
|
||||
for (std::size_t trackIndex {}; trackIndex < similarTracks.size(); ++trackIndex)
|
||||
{
|
||||
using namespace Database::Debug;
|
||||
// select the similar track that has the best score
|
||||
for (std::size_t trackIndex{}; trackIndex < similarTracks.size(); ++trackIndex)
|
||||
{
|
||||
using namespace Database::Debug;
|
||||
|
||||
finalResult.push_back(similarTracks[trackIndex]);
|
||||
finalResult.push_back(similarTracks[trackIndex]);
|
||||
|
||||
scores[trackIndex] = 0;
|
||||
for (const auto& constraint : _constraints)
|
||||
scores[trackIndex] += constraint->computeScore(finalResult, finalResult.size() - 1);
|
||||
scores[trackIndex] = 0;
|
||||
for (const auto& constraint : _constraints)
|
||||
scores[trackIndex] += constraint->computeScore(finalResult, finalResult.size() - 1);
|
||||
|
||||
finalResult.pop_back();
|
||||
finalResult.pop_back();
|
||||
|
||||
// early exit if we consider we found a track with no constraint violation (since similarTracks sorted from most to least similar)
|
||||
if (scores[trackIndex] < 0.01)
|
||||
break;
|
||||
}
|
||||
// early exit if we consider we found a track with no constraint violation (since similarTracks sorted from most to least similar)
|
||||
if (scores[trackIndex] < 0.01)
|
||||
break;
|
||||
}
|
||||
|
||||
// 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))))};
|
||||
// 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)))) };
|
||||
|
||||
finalResult.push_back(similarTracks[bestScoreIndex]);
|
||||
similarTracks.erase(std::begin(similarTracks) + bestScoreIndex);
|
||||
}
|
||||
finalResult.push_back(similarTracks[bestScoreIndex]);
|
||||
similarTracks.erase(std::begin(similarTracks) + bestScoreIndex);
|
||||
}
|
||||
|
||||
// for now, just get some more similar tracks
|
||||
return std::vector(std::cbegin(finalResult) + startingTracks.size(), std::cend(finalResult));
|
||||
}
|
||||
// for now, just get some more similar tracks
|
||||
return std::vector(std::cbegin(finalResult) + startingTracks.size(), std::cend(finalResult));
|
||||
}
|
||||
|
||||
TrackContainer
|
||||
PlaylistGeneratorService::getTracksFromTrackList(Database::TrackListId tracklistId) const
|
||||
{
|
||||
TrackContainer tracks;
|
||||
TrackContainer PlaylistGeneratorService::getTracksFromTrackList(Database::TrackListId tracklistId) const
|
||||
{
|
||||
TrackContainer tracks;
|
||||
|
||||
Session& dbSession {_db.getTLSSession()};
|
||||
auto transaction {dbSession.createSharedTransaction()};
|
||||
Session& dbSession{ _db.getTLSSession() };
|
||||
auto transaction{ dbSession.createSharedTransaction() };
|
||||
|
||||
Track::FindParameters params;
|
||||
params.setTrackList(tracklistId);
|
||||
params.setSortMethod(TrackSortMethod::TrackList);
|
||||
params.setDistinct(false);
|
||||
Track::FindParameters params;
|
||||
params.setTrackList(tracklistId);
|
||||
params.setSortMethod(TrackSortMethod::TrackList);
|
||||
params.setDistinct(false);
|
||||
|
||||
for (const TrackId trackId : Track::find(dbSession, params).results)
|
||||
tracks.push_back(trackId);
|
||||
for (const TrackId trackId : Track::findIds(dbSession, params).results)
|
||||
tracks.push_back(trackId);
|
||||
|
||||
return tracks;
|
||||
}
|
||||
return tracks;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Recommendation {
|
||||
Session& dbSession{ _db.getTLSSession() };
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Scanner
|
||||
Session& session {_db.getTLSSession()};
|
||||
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)
|
||||
{
|
||||
const Track::pointer track {Track::find(session, trackId)};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -204,16 +204,13 @@ namespace API::Subsonic
|
||||
if (!user)
|
||||
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::Node& randomSongsNode{ response.createNode("randomSongs") };
|
||||
for (const TrackId trackId : trackIds.results)
|
||||
{
|
||||
const Track::pointer track{ Track::find(context.dbSession, trackId) };
|
||||
for (const Track::pointer& track : tracks.results)
|
||||
randomSongsNode.addArrayChild("song", createSongNode(context, track, user));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
@@ -250,12 +247,9 @@ namespace API::Subsonic
|
||||
params.setClusters({ cluster->getId() });
|
||||
params.setRange({ offset, size });
|
||||
|
||||
auto trackIds{ Track::find(context.dbSession, params) };
|
||||
for (const TrackId trackId : trackIds.results)
|
||||
{
|
||||
const Track::pointer track{ Track::find(context.dbSession, trackId) };
|
||||
const auto tracks{ Track::find(context.dbSession, params) };
|
||||
for (const Track::pointer& track : tracks.results)
|
||||
songsByGenreNode.addArrayChild("song", createSongNode(context, track, user));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ namespace API::Subsonic
|
||||
params.setRange({ 0, meanTrackCountPerArtist });
|
||||
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),
|
||||
std::begin(artistTracks.results),
|
||||
std::end(artistTracks.results));
|
||||
@@ -225,7 +225,7 @@ namespace API::Subsonic
|
||||
params.setRange({ 0, meanTrackCountPerRelease });
|
||||
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),
|
||||
std::begin(releaseTracks.results),
|
||||
std::end(releaseTracks.results));
|
||||
@@ -349,11 +349,8 @@ namespace API::Subsonic
|
||||
directoryNode.setAttribute("name", Utils::makeNameFilesystemCompatible(release->getName()));
|
||||
|
||||
const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release)) };
|
||||
for (const TrackId trackId : tracks.results)
|
||||
{
|
||||
const Track::pointer track{ Track::find(context.dbSession, trackId) };
|
||||
for (const Track::pointer& track : tracks.results)
|
||||
directoryNode.addArrayChild("child", createSongNode(context, track, user));
|
||||
}
|
||||
}
|
||||
else
|
||||
throw BadParameterGenericError{ "id" };
|
||||
@@ -432,11 +429,8 @@ namespace API::Subsonic
|
||||
Response::Node albumNode{ createAlbumNode(context, release, user, true /* id3 */) };
|
||||
|
||||
const auto tracks{ Track::find(context.dbSession, Track::FindParameters {}.setRelease(id).setSortMethod(TrackSortMethod::Release)) };
|
||||
for (const TrackId trackId : tracks.results)
|
||||
{
|
||||
const Track::pointer track{ Track::find(context.dbSession, trackId) };
|
||||
for (const Track::pointer& track : tracks.results)
|
||||
albumNode.addArrayChild("song", createSongNode(context, track, user));
|
||||
}
|
||||
|
||||
response.addNode("album", std::move(albumNode));
|
||||
|
||||
|
||||
@@ -86,8 +86,7 @@ namespace API::Subsonic
|
||||
params.setKeywords(keywords);
|
||||
params.setRange({ albumOffset, albumCount });
|
||||
|
||||
RangeResults<Release::pointer> releases{ Release::find(context.dbSession, params) };
|
||||
for (const Release::pointer& release : releases.results)
|
||||
for (const Release::pointer& release : Release::find(context.dbSession, params).results)
|
||||
searchResult2Node.addArrayChild("album", createAlbumNode(context, release, user, id3));
|
||||
}
|
||||
|
||||
@@ -97,12 +96,8 @@ namespace API::Subsonic
|
||||
params.setKeywords(keywords);
|
||||
params.setRange({ songOffset, songCount });
|
||||
|
||||
RangeResults<TrackId> trackIds{ Track::find(context.dbSession, params) };
|
||||
for (const TrackId trackId : trackIds.results)
|
||||
{
|
||||
const auto track{ Track::find(context.dbSession, trackId) };
|
||||
for (const Track::pointer& track : Track::find(context.dbSession, params).results)
|
||||
searchResult2Node.addArrayChild("song", createSongNode(context, track, user));
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
|
||||
@@ -110,6 +110,7 @@ namespace API::Subsonic
|
||||
trackResponse.setAttribute("playCount", Listen::getCount(context.dbSession, user->getId(), user->getScrobblingBackend(), track->getId()));
|
||||
trackResponse.setAttribute("path", getTrackPath(track));
|
||||
{
|
||||
// TODO, store this in DB
|
||||
std::error_code ec;
|
||||
const auto fileSize{ std::filesystem::file_size(track->getPath(), ec) };
|
||||
if (!ec)
|
||||
|
||||
Reference in New Issue
Block a user