Initial support for multi library, still WIP
This commit is contained in:
@@ -99,6 +99,7 @@ namespace Feedback
|
||||
searchParams.setLinkType(params.linkType);
|
||||
searchParams.setSortMethod(params.sortMethod);
|
||||
searchParams.setRange(params.range);
|
||||
searchParams.setMediaLibrary(params.library);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
@@ -137,6 +138,7 @@ namespace Feedback
|
||||
searchParams.setClusters(params.clusters);
|
||||
searchParams.setSortMethod(ReleaseSortMethod::StarredDateDesc);
|
||||
searchParams.setRange(params.range);
|
||||
searchParams.setMediaLibrary(params.library);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
@@ -175,6 +177,7 @@ namespace Feedback
|
||||
searchParams.setClusters(params.clusters);
|
||||
searchParams.setSortMethod(TrackSortMethod::StarredDateDesc);
|
||||
searchParams.setRange(params.range);
|
||||
searchParams.setMediaLibrary(params.library);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#include "database/Types.hpp"
|
||||
#include "database/ArtistId.hpp"
|
||||
#include "database/ClusterId.hpp"
|
||||
#include "database/MediaLibraryId.hpp"
|
||||
#include "database/ReleaseId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
#include "database/UserId.hpp"
|
||||
@@ -53,10 +54,12 @@ namespace Feedback
|
||||
Database::UserId user;
|
||||
std::vector<Database::ClusterId> clusters; // if non empty, at least one artist that belongs to these clusters
|
||||
std::optional<Database::Range> range;
|
||||
Database::MediaLibraryId library;
|
||||
|
||||
FindParameters& setUser(const Database::UserId _user) { user = _user; return *this; }
|
||||
FindParameters& setClusters(const std::vector<Database::ClusterId>& _clusters) { clusters = _clusters; return *this; }
|
||||
FindParameters& setRange(std::optional<Database::Range> _range) { range = _range; return *this; }
|
||||
FindParameters& setMediaLibrary(Database::MediaLibraryId _library) { library = _library; return *this; }
|
||||
};
|
||||
|
||||
// Artists
|
||||
|
||||
@@ -20,26 +20,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "services/scanner/ScannerStats.hpp"
|
||||
|
||||
namespace Scanner
|
||||
{
|
||||
class IScanStep
|
||||
{
|
||||
public:
|
||||
virtual ~IScanStep() = default;
|
||||
class IScanStep
|
||||
{
|
||||
public:
|
||||
virtual ~IScanStep() = default;
|
||||
|
||||
virtual ScanStep getStep() const = 0;
|
||||
virtual std::string_view getStepName() const = 0;
|
||||
virtual ScanStep getStep() const = 0;
|
||||
virtual std::string_view getStepName() const = 0;
|
||||
|
||||
struct ScanContext
|
||||
{
|
||||
const std::filesystem::path directory;
|
||||
const bool forceScan;
|
||||
ScanStats stats;
|
||||
ScanStepStats currentStepStats;
|
||||
};
|
||||
virtual void process(ScanContext& context) = 0;
|
||||
};
|
||||
struct ScanContext
|
||||
{
|
||||
const bool forceScan;
|
||||
ScanStats stats;
|
||||
ScanStepStats currentStepStats;
|
||||
};
|
||||
virtual void process(ScanContext& context) = 0;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include "ScanStepDiscoverFiles.hpp"
|
||||
|
||||
#include "utils/ILogger.hpp"
|
||||
#include "utils/Path.hpp"
|
||||
|
||||
@@ -26,22 +27,30 @@ namespace Scanner
|
||||
void ScanStepDiscoverFiles::process(ScanContext& context)
|
||||
{
|
||||
context.stats.filesScanned = 0;
|
||||
PathUtils::exploreFilesRecursive(context.directory, [&](std::error_code ec, const std::filesystem::path& path)
|
||||
{
|
||||
if (_abortScan)
|
||||
return false;
|
||||
|
||||
if (!ec && PathUtils::hasFileAnyExtension(path, _settings.supportedExtensions))
|
||||
for (const ScannerSettings::MediaLibraryInfo& mediaLibrary : _settings.mediaLibraries)
|
||||
{
|
||||
std::size_t currentDirectoryProcessElemsCount{};
|
||||
PathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path)
|
||||
{
|
||||
context.currentStepStats.processedElems++;
|
||||
_progressCallback(context.currentStepStats);
|
||||
}
|
||||
if (_abortScan)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}, &excludeDirFileName);
|
||||
if (!ec && PathUtils::hasFileAnyExtension(path, _settings.supportedExtensions))
|
||||
{
|
||||
context.currentStepStats.processedElems++;
|
||||
currentDirectoryProcessElemsCount++;
|
||||
_progressCallback(context.currentStepStats);
|
||||
}
|
||||
|
||||
return true;
|
||||
}, &excludeDirFileName);
|
||||
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Discovered " << currentDirectoryProcessElemsCount << " files in '" << mediaLibrary.rootDirectory << "'");
|
||||
}
|
||||
|
||||
context.stats.filesScanned = context.currentStepStats.processedElems;
|
||||
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Discovered " << context.stats.filesScanned << " files in '" << context.directory << "'");
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Discovered " << context.stats.filesScanned << " files in all directories");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +183,11 @@ namespace Scanner
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!PathUtils::isPathInRootPath(p, _settings.mediaDirectory, &excludeDirFileName))
|
||||
if (std::none_of(std::cbegin(_settings.mediaLibraries), std::cend(_settings.mediaLibraries),
|
||||
[&](const ScannerSettings::MediaLibraryInfo& libraryInfo)
|
||||
{
|
||||
return PathUtils::isPathInRootPath(p, libraryInfo.rootDirectory, &excludeDirFileName);
|
||||
}))
|
||||
{
|
||||
LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': out of media directory");
|
||||
return false;
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "database/Artist.hpp"
|
||||
#include "database/Cluster.hpp"
|
||||
#include "database/Db.hpp"
|
||||
#include "database/MediaLibrary.hpp"
|
||||
#include "database/Release.hpp"
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Track.hpp"
|
||||
@@ -232,33 +233,36 @@ namespace Scanner
|
||||
|
||||
context.currentStepStats.totalElems = context.stats.filesScanned;
|
||||
|
||||
PathUtils::exploreFilesRecursive(context.directory, [&](std::error_code ec, const std::filesystem::path& path)
|
||||
{
|
||||
if (_abortScan)
|
||||
return false;
|
||||
|
||||
if (ec)
|
||||
for (const ScannerSettings::MediaLibraryInfo& mediaLibrary : _settings.mediaLibraries)
|
||||
{
|
||||
PathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, ERROR, "Cannot process entry '" << path.string() << "': " << ec.message());
|
||||
context.stats.errors.emplace_back(ScanError{ path, ScanErrorType::CannotReadFile, ec.message() });
|
||||
}
|
||||
else if (PathUtils::hasFileAnyExtension(path, _settings.supportedExtensions))
|
||||
{
|
||||
scanAudioFile(path, context);
|
||||
if (_abortScan)
|
||||
return false;
|
||||
|
||||
context.currentStepStats.processedElems++;
|
||||
_progressCallback(context.currentStepStats);
|
||||
if (ec)
|
||||
{
|
||||
LMS_LOG(DBUPDATER, ERROR, "Cannot process entry '" << path.string() << "': " << ec.message());
|
||||
context.stats.errors.emplace_back(ScanError{ path, ScanErrorType::CannotReadFile, ec.message() });
|
||||
}
|
||||
else if (PathUtils::hasFileAnyExtension(path, _settings.supportedExtensions))
|
||||
{
|
||||
scanAudioFile(path, context, mediaLibrary);
|
||||
|
||||
// optimize the database during scan (if we import a very large database, it may be too late to do it once at end)
|
||||
if ((context.currentStepStats.processedElems % 1'000) == 0)
|
||||
_db.getTLSSession().optimize();
|
||||
}
|
||||
context.currentStepStats.processedElems++;
|
||||
_progressCallback(context.currentStepStats);
|
||||
|
||||
return true;
|
||||
}, &excludeDirFileName);
|
||||
// optimize the database during scan (if we import a very large database, it may be too late to do it once at end)
|
||||
if ((context.currentStepStats.processedElems % 1'000) == 0)
|
||||
_db.getTLSSession().optimize();
|
||||
}
|
||||
|
||||
return true;
|
||||
}, &excludeDirFileName);
|
||||
}
|
||||
}
|
||||
|
||||
void ScanStepScanFiles::scanAudioFile(const std::filesystem::path& file, ScanContext& context)
|
||||
void ScanStepScanFiles::scanAudioFile(const std::filesystem::path& file, ScanContext& context, const ScannerSettings::MediaLibraryInfo& libraryInfo)
|
||||
{
|
||||
ScanStats& stats{ context.stats };
|
||||
Wt::WDateTime lastWriteTime;
|
||||
@@ -273,6 +277,7 @@ namespace Scanner
|
||||
return;
|
||||
}
|
||||
|
||||
bool needUpdateLibrary{};
|
||||
if (!context.forceScan)
|
||||
{
|
||||
// Skip file if last write is the same
|
||||
@@ -281,14 +286,35 @@ namespace Scanner
|
||||
|
||||
const Track::pointer track{ Track::findByPath(dbSession, file) };
|
||||
|
||||
if (track && track->getLastWriteTime().toTime_t() == lastWriteTime.toTime_t()
|
||||
&& track->getScanVersion() == _settings.scanVersion)
|
||||
if (track
|
||||
&& track->getLastWriteTime().toTime_t() == lastWriteTime.toTime_t()
|
||||
&& track->getScanVersion() == _settings.scanVersion
|
||||
)
|
||||
{
|
||||
stats.skips++;
|
||||
return;
|
||||
// this file may have been moved from one library to another, then we just need to update the media library id instead of a full rescan
|
||||
auto trackMediaLibrary{ track->getMediaLibrary() };
|
||||
if (trackMediaLibrary && trackMediaLibrary->getId() == libraryInfo.id)
|
||||
{
|
||||
stats.skips++;
|
||||
return;
|
||||
}
|
||||
|
||||
needUpdateLibrary = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (needUpdateLibrary)
|
||||
{
|
||||
Database::Session& dbSession{ _db.getTLSSession() };
|
||||
auto transaction{ _db.getTLSSession().createWriteTransaction() };
|
||||
|
||||
Track::pointer track{ Track::findByPath(dbSession, file) };
|
||||
assert(track);
|
||||
track.modify()->setMediaLibrary(Database::MediaLibrary::find(dbSession, libraryInfo.id)); // may be null, will be handled in the next scan anyway
|
||||
stats.updates++;
|
||||
return;
|
||||
}
|
||||
|
||||
std::optional<MetaData::Track> trackInfo{ _metadataParser->parse(file) };
|
||||
if (!trackInfo)
|
||||
{
|
||||
@@ -330,8 +356,14 @@ namespace Scanner
|
||||
continue;
|
||||
|
||||
// Skip if duplicate files no longer in media root: as it will be removed later, we will end up with no file
|
||||
if (!PathUtils::isPathInRootPath(file, _settings.mediaDirectory, &excludeDirFileName))
|
||||
if (std::none_of(std::cbegin(_settings.mediaLibraries), std::cend(_settings.mediaLibraries),
|
||||
[&](const ScannerSettings::MediaLibraryInfo& libraryInfo)
|
||||
{
|
||||
return PathUtils::isPathInRootPath(file, libraryInfo.rootDirectory, &excludeDirFileName);
|
||||
}))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Skipped '" << file.string() << "' (similar MBID in '" << otherTrack->getPath().string() << "')");
|
||||
// As this MBID already exists, just remove what we just scanned
|
||||
@@ -389,6 +421,7 @@ namespace Scanner
|
||||
// Track related data
|
||||
assert(track);
|
||||
|
||||
track.modify()->setMediaLibrary(MediaLibrary::find(dbSession, libraryInfo.id)); // may be null if settings are updated in // => next scan will correct this
|
||||
track.modify()->clearArtistLinks();
|
||||
// Do not fallback on artists with the same name but having a MBID for artist and releaseArtists, as it may be corrected by properly tagging files
|
||||
for (const Artist::pointer& artist : getOrCreateArtists(dbSession, trackInfo->artists, false))
|
||||
@@ -450,7 +483,7 @@ namespace Scanner
|
||||
// If a file has an OriginalDate but no date, set it to ease filtering
|
||||
if (!trackInfo->date.isValid() && trackInfo->originalDate.isValid())
|
||||
track.modify()->setDate(trackInfo->originalDate);
|
||||
|
||||
|
||||
// If a file has an OriginalYear but no Year, set it to ease filtering
|
||||
if (!trackInfo->year && trackInfo->originalYear)
|
||||
track.modify()->setYear(trackInfo->originalYear);
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace Scanner
|
||||
std::string_view getStepName() const override { return "Scanning files"; }
|
||||
void process(ScanContext& context) override;
|
||||
|
||||
void scanAudioFile(const std::filesystem::path& file, ScanContext& context);
|
||||
void scanAudioFile(const std::filesystem::path& file, ScanContext& context, const ScannerSettings::MediaLibraryInfo& libraryInfo);
|
||||
|
||||
std::unique_ptr<MetaData::IParser> _metadataParser;
|
||||
const std::vector<std::string> _extraTagsToParse{ "GENRE", "MOOD", "LANGUAGE", "ALBUMGROUPING" };
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#include <ctime>
|
||||
#include <boost/asio/placeholders.hpp>
|
||||
|
||||
#include "database/Cluster.hpp"
|
||||
#include "database/MediaLibrary.hpp"
|
||||
#include "database/TrackFeatures.hpp"
|
||||
#include "database/ScanSettings.hpp"
|
||||
#include "utils/Exception.hpp"
|
||||
@@ -113,6 +113,12 @@ namespace Scanner
|
||||
|
||||
void ScannerService::abortScan()
|
||||
{
|
||||
bool isRunning{};
|
||||
{
|
||||
std::scoped_lock lock{ _statusMutex };
|
||||
isRunning = _curState == State::InProgress;
|
||||
}
|
||||
|
||||
LMS_LOG(DBUPDATER, DEBUG, "Aborting scan...");
|
||||
std::scoped_lock lock{ _controlMutex };
|
||||
|
||||
@@ -125,6 +131,9 @@ namespace Scanner
|
||||
|
||||
_abortScan = false;
|
||||
_ioService.start();
|
||||
|
||||
if (isRunning)
|
||||
_events.scanAborted.emit();
|
||||
}
|
||||
|
||||
void ScannerService::requestImmediateScan(bool force)
|
||||
@@ -139,6 +148,11 @@ namespace Scanner
|
||||
});
|
||||
}
|
||||
|
||||
void ScannerService::requestStop()
|
||||
{
|
||||
abortScan();
|
||||
}
|
||||
|
||||
void ScannerService::requestReload()
|
||||
{
|
||||
abortScan();
|
||||
@@ -261,7 +275,7 @@ namespace Scanner
|
||||
|
||||
refreshScanSettings();
|
||||
|
||||
IScanStep::ScanContext scanContext{ _settings.mediaDirectory, forceScan, ScanStats {}, ScanStepStats {} };
|
||||
IScanStep::ScanContext scanContext{ forceScan, ScanStats {}, ScanStepStats {} };
|
||||
ScanStats& stats{ scanContext.stats };
|
||||
stats.startTime = Wt::WDateTime::currentDateTime();
|
||||
|
||||
@@ -359,7 +373,11 @@ namespace Scanner
|
||||
std::transform(std::cbegin(fileExtensions), std::end(fileExtensions), std::back_inserter(newSettings.supportedExtensions),
|
||||
[](const std::filesystem::path& extension) { return std::filesystem::path{ StringUtils::stringToLower(extension.string()) }; });
|
||||
}
|
||||
newSettings.mediaDirectory = scanSettings->getMediaDirectory();
|
||||
|
||||
MediaLibrary::find(_dbSession, [&](const MediaLibrary::pointer& mediaLibrary)
|
||||
{
|
||||
newSettings.mediaLibraries.push_back(ScannerSettings::MediaLibraryInfo{ mediaLibrary->getId(), mediaLibrary->getPath().lexically_normal() });
|
||||
});
|
||||
|
||||
{
|
||||
const auto& tags{ scanSettings->getExtraTagsToScan() };
|
||||
|
||||
@@ -48,7 +48,8 @@ namespace Scanner
|
||||
|
||||
ScannerService(const ScannerService&) = delete;
|
||||
ScannerService& operator=(const ScannerService&) = delete;
|
||||
|
||||
private:
|
||||
void requestStop() override;
|
||||
void requestReload() override;
|
||||
void requestImmediateScan(bool force) override;
|
||||
|
||||
|
||||
@@ -23,29 +23,38 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <Wt/WDateTime.h>
|
||||
#include "database/MediaLibraryId.hpp"
|
||||
#include "database/ScanSettings.hpp"
|
||||
|
||||
namespace Scanner
|
||||
{
|
||||
struct ScannerSettings
|
||||
{
|
||||
std::size_t scanVersion {};
|
||||
Wt::WTime startTime;
|
||||
Database::ScanSettings::UpdatePeriod updatePeriod {Database::ScanSettings::UpdatePeriod::Never};
|
||||
std::vector<std::filesystem::path> supportedExtensions;
|
||||
std::filesystem::path mediaDirectory;
|
||||
bool skipDuplicateMBID {};
|
||||
std::vector<std::string> extraTags;
|
||||
struct ScannerSettings
|
||||
{
|
||||
std::size_t scanVersion{};
|
||||
Wt::WTime startTime;
|
||||
Database::ScanSettings::UpdatePeriod updatePeriod{ Database::ScanSettings::UpdatePeriod::Never };
|
||||
std::vector<std::filesystem::path> supportedExtensions;
|
||||
bool skipDuplicateMBID{};
|
||||
std::vector<std::string> extraTags;
|
||||
|
||||
bool operator==(const ScannerSettings& rhs) const
|
||||
{
|
||||
return scanVersion == rhs.scanVersion
|
||||
&& startTime == rhs.startTime
|
||||
&& updatePeriod == rhs.updatePeriod
|
||||
&& supportedExtensions == rhs.supportedExtensions
|
||||
&& mediaDirectory == rhs.mediaDirectory
|
||||
&& skipDuplicateMBID == rhs.skipDuplicateMBID
|
||||
&& extraTags == rhs.extraTags;
|
||||
}
|
||||
};
|
||||
struct MediaLibraryInfo
|
||||
{
|
||||
Database::MediaLibraryId id;
|
||||
std::filesystem::path rootDirectory;
|
||||
|
||||
bool operator==(const MediaLibraryInfo& other) const { return id == other.id && rootDirectory == other.rootDirectory; }
|
||||
};
|
||||
std::vector<MediaLibraryInfo> mediaLibraries;
|
||||
|
||||
bool operator==(const ScannerSettings& rhs) const
|
||||
{
|
||||
return scanVersion == rhs.scanVersion
|
||||
&& startTime == rhs.startTime
|
||||
&& updatePeriod == rhs.updatePeriod
|
||||
&& supportedExtensions == rhs.supportedExtensions
|
||||
&& mediaLibraries == rhs.mediaLibraries
|
||||
&& skipDuplicateMBID == rhs.skipDuplicateMBID
|
||||
&& extraTags == rhs.extraTags;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace Scanner
|
||||
virtual ~IScannerService() = default;
|
||||
|
||||
// Async requests
|
||||
virtual void requestStop() = 0;
|
||||
virtual void requestReload() = 0;
|
||||
virtual void requestImmediateScan(bool force) = 0;
|
||||
|
||||
|
||||
@@ -27,20 +27,23 @@
|
||||
namespace Scanner
|
||||
{
|
||||
|
||||
struct Events
|
||||
{
|
||||
// Called just after scan start
|
||||
Wt::Signal<> scanStarted;
|
||||
struct Events
|
||||
{
|
||||
// Called if scan was aborted
|
||||
Wt::Signal<> scanAborted;
|
||||
|
||||
// Called just after scan complete (true if changes have been made)
|
||||
Wt::Signal<ScanStats> scanComplete;
|
||||
// Called just after scan start
|
||||
Wt::Signal<> scanStarted;
|
||||
|
||||
// Called during scan in progress
|
||||
Wt::Signal<ScanStepStats> scanInProgress;
|
||||
// Called just after scan complete (true if changes have been made)
|
||||
Wt::Signal<ScanStats> scanComplete;
|
||||
|
||||
// Called after a schedule
|
||||
Wt::Signal<Wt::WDateTime> scanScheduled;
|
||||
};
|
||||
// Called during scan in progress
|
||||
Wt::Signal<ScanStepStats> scanInProgress;
|
||||
|
||||
// Called after a schedule
|
||||
Wt::Signal<Wt::WDateTime> scanScheduled;
|
||||
};
|
||||
|
||||
} // ns Scanner
|
||||
|
||||
|
||||
@@ -35,6 +35,26 @@ namespace Scrobbling
|
||||
{
|
||||
using namespace Database;
|
||||
|
||||
namespace
|
||||
{
|
||||
Database::Listen::StatsFindParameters convertToListenFindParameters(const ScrobblingService::FindParameters& params)
|
||||
{
|
||||
Database::Listen::StatsFindParameters listenFindParams;
|
||||
listenFindParams.setUser(params.user);
|
||||
listenFindParams.setClusters(params.clusters);
|
||||
listenFindParams.setRange(params.range);
|
||||
listenFindParams.setMediaLibrary(params.library);
|
||||
listenFindParams.setArtist(params.artist);
|
||||
|
||||
return listenFindParams;
|
||||
}
|
||||
|
||||
Database::Listen::ArtistStatsFindParameters convertToListenFindParameters(const ScrobblingService::ArtistFindParameters& params)
|
||||
{
|
||||
return Database::Listen::ArtistStatsFindParameters{ convertToListenFindParameters(static_cast<const ScrobblingService::FindParameters&>(params)), params.linkType };
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<IScrobblingService> createScrobblingService(boost::asio::io_context& ioContext, Db& db)
|
||||
{
|
||||
return std::make_unique<ScrobblingService>(ioContext, db);
|
||||
@@ -84,48 +104,57 @@ namespace Scrobbling
|
||||
return backend;
|
||||
}
|
||||
|
||||
ScrobblingService::ArtistContainer ScrobblingService::getRecentArtists(UserId userId, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range)
|
||||
ScrobblingService::ArtistContainer ScrobblingService::getRecentArtists(const ArtistFindParameters& params)
|
||||
{
|
||||
ArtistContainer res;
|
||||
|
||||
const auto backend{ getUserBackend(userId) };
|
||||
const auto backend{ getUserBackend(params.user) };
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::ArtistStatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getRecentArtists(session, userId, *backend, clusterIds, linkType, range);
|
||||
res = Database::Listen::getRecentArtists(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
|
||||
ScrobblingService::ReleaseContainer ScrobblingService::getRecentReleases(UserId userId, const std::vector<ClusterId>& clusterIds, Range range)
|
||||
ScrobblingService::ReleaseContainer ScrobblingService::getRecentReleases(const FindParameters& params)
|
||||
{
|
||||
ReleaseContainer res;
|
||||
|
||||
const auto backend{ getUserBackend(userId) };
|
||||
const auto backend{ getUserBackend(params.user) };
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getRecentReleases(session, userId, *backend, clusterIds, range);
|
||||
res = Database::Listen::getRecentReleases(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
|
||||
ScrobblingService::TrackContainer ScrobblingService::getRecentTracks(UserId userId, const std::vector<ClusterId>& clusterIds, Range range)
|
||||
ScrobblingService::TrackContainer ScrobblingService::getRecentTracks(const FindParameters& params)
|
||||
{
|
||||
TrackContainer res;
|
||||
|
||||
const auto backend{ getUserBackend(userId) };
|
||||
const auto backend{ getUserBackend(params.user) };
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getRecentTracks(session, userId, *backend, clusterIds, range);
|
||||
res = Database::Listen::getRecentTracks(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -170,63 +199,57 @@ namespace Scrobbling
|
||||
}
|
||||
|
||||
// Top
|
||||
ScrobblingService::ArtistContainer ScrobblingService::getTopArtists(UserId userId, const std::vector<ClusterId>& clusterIds, std::optional<TrackArtistLinkType> linkType, Range range)
|
||||
ScrobblingService::ArtistContainer ScrobblingService::getTopArtists(const ArtistFindParameters& params)
|
||||
{
|
||||
ArtistContainer res;
|
||||
|
||||
const auto backend{ getUserBackend(userId) };
|
||||
const auto backend{ getUserBackend(params.user) };
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::ArtistStatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getTopArtists(session, userId, *backend, clusterIds, linkType, range);
|
||||
res = Database::Listen::getTopArtists(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
|
||||
ScrobblingService::ReleaseContainer ScrobblingService::getTopReleases(UserId userId, const std::vector<ClusterId>& clusterIds, Range range)
|
||||
ScrobblingService::ReleaseContainer ScrobblingService::getTopReleases(const FindParameters& params)
|
||||
{
|
||||
ReleaseContainer res;
|
||||
|
||||
const auto backend{ getUserBackend(userId) };
|
||||
const auto backend{ getUserBackend(params.user) };
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getTopReleases(session, userId, *backend, clusterIds, range);
|
||||
res = Database::Listen::getTopReleases(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
|
||||
ScrobblingService::TrackContainer ScrobblingService::getTopTracks(UserId userId, const std::vector<ClusterId>& clusterIds, Range range)
|
||||
ScrobblingService::TrackContainer ScrobblingService::getTopTracks(const FindParameters& params)
|
||||
{
|
||||
TrackContainer res;
|
||||
|
||||
const auto backend{ getUserBackend(userId) };
|
||||
const auto backend{ getUserBackend(params.user) };
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Database::Listen::StatsFindParameters listenFindParams{ convertToListenFindParameters(params) };
|
||||
listenFindParams.setScrobblingBackend(backend);
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getTopTracks(session, userId, *backend, clusterIds, range);
|
||||
return res;
|
||||
}
|
||||
|
||||
ScrobblingService::TrackContainer ScrobblingService::getTopTracks(UserId userId, Database::ArtistId artistId, const std::vector<ClusterId>& clusterIds, Range range)
|
||||
{
|
||||
TrackContainer res;
|
||||
|
||||
const auto backend{ getUserBackend(userId) };
|
||||
if (!backend)
|
||||
return res;
|
||||
|
||||
Session& session{ _db.getTLSSession() };
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
res = Database::Listen::getTopTracks(session, userId, artistId, *backend, clusterIds, range);
|
||||
res = Database::Listen::getTopTracks(session, listenFindParams);
|
||||
return res;
|
||||
}
|
||||
} // ns Scrobbling
|
||||
|
||||
@@ -39,9 +39,9 @@ namespace Scrobbling
|
||||
void listenFinished(const Listen& listen, std::optional<std::chrono::seconds> duration) override;
|
||||
void addTimedListen(const TimedListen& listen) override;
|
||||
|
||||
ArtistContainer getRecentArtists(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, std::optional<Database::TrackArtistLinkType> linkType,Database::Range range) override;
|
||||
ReleaseContainer getRecentReleases(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds,Database::Range range) override;
|
||||
TrackContainer getRecentTracks(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) override;
|
||||
ArtistContainer getRecentArtists(const ArtistFindParameters& params) override;
|
||||
ReleaseContainer getRecentReleases(const FindParameters& params) override;
|
||||
TrackContainer getRecentTracks(const FindParameters& params) override;
|
||||
|
||||
std::size_t getCount(Database::UserId userId, Database::ReleaseId releaseId) override;
|
||||
std::size_t getCount(Database::UserId userId, Database::TrackId trackId) override;
|
||||
@@ -49,10 +49,9 @@ namespace Scrobbling
|
||||
Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::ReleaseId releaseId) override;
|
||||
Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::TrackId trackId) override;
|
||||
|
||||
ArtistContainer getTopArtists(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, std::optional<Database::TrackArtistLinkType> linkType, Database::Range range) override;
|
||||
ReleaseContainer getTopReleases(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) override;
|
||||
TrackContainer getTopTracks(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) override;
|
||||
TrackContainer getTopTracks(Database::UserId userId, Database::ArtistId artistId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) override;
|
||||
ArtistContainer getTopArtists(const ArtistFindParameters& params) override;
|
||||
ReleaseContainer getTopReleases(const FindParameters& params) override;
|
||||
TrackContainer getTopTracks(const FindParameters& params) override;
|
||||
|
||||
std::optional<Database::ScrobblingBackend> getUserBackend(Database::UserId userId);
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "services/scrobbling/Listen.hpp"
|
||||
#include "database/ArtistId.hpp"
|
||||
#include "database/ClusterId.hpp"
|
||||
#include "database/MediaLibraryId.hpp"
|
||||
#include "database/ReleaseId.hpp"
|
||||
#include "database/TrackId.hpp"
|
||||
#include "database/Types.hpp"
|
||||
@@ -39,7 +40,6 @@ namespace Database
|
||||
|
||||
namespace Scrobbling
|
||||
{
|
||||
|
||||
class IScrobblingService
|
||||
{
|
||||
public:
|
||||
@@ -55,11 +55,36 @@ namespace Scrobbling
|
||||
using ArtistContainer = Database::RangeResults<Database::ArtistId>;
|
||||
using ReleaseContainer = Database::RangeResults<Database::ReleaseId>;
|
||||
using TrackContainer = Database::RangeResults<Database::TrackId>;
|
||||
|
||||
|
||||
struct FindParameters
|
||||
{
|
||||
Database::UserId user;
|
||||
std::vector<Database::ClusterId> clusters; // if non empty, at least one artist that belongs to these clusters
|
||||
std::optional<Database::Range> range;
|
||||
Database::MediaLibraryId library; // if set, match this library
|
||||
Database::ArtistId artist; // if set, match this artist
|
||||
|
||||
FindParameters& setUser(const Database::UserId _user) { user = _user; return *this; }
|
||||
FindParameters& setClusters(const std::vector<Database::ClusterId>& _clusters) { clusters = _clusters; return *this; }
|
||||
FindParameters& setRange(std::optional<Database::Range> _range) { range = _range; return *this; }
|
||||
FindParameters& setMediaLibrary(Database::MediaLibraryId _library) { library = _library; return *this; }
|
||||
FindParameters& setArtist(Database::ArtistId _artist) { artist = _artist; return *this; }
|
||||
};
|
||||
|
||||
// Artists
|
||||
struct ArtistFindParameters : public FindParameters
|
||||
{
|
||||
std::optional<Database::TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
|
||||
Database::ArtistSortMethod sortMethod{ Database::ArtistSortMethod::None };
|
||||
|
||||
ArtistFindParameters& setLinkType(std::optional<Database::TrackArtistLinkType> _linkType) { linkType = _linkType; return *this; }
|
||||
ArtistFindParameters& setSortMethod(Database::ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; }
|
||||
};
|
||||
|
||||
// From most recent to oldest
|
||||
virtual ArtistContainer getRecentArtists(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, std::optional<Database::TrackArtistLinkType> linkType, Database::Range range) = 0;
|
||||
virtual ReleaseContainer getRecentReleases(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) = 0;
|
||||
virtual TrackContainer getRecentTracks(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) = 0;
|
||||
virtual ArtistContainer getRecentArtists(const ArtistFindParameters& params) = 0;
|
||||
virtual ReleaseContainer getRecentReleases(const FindParameters& params) = 0;
|
||||
virtual TrackContainer getRecentTracks(const FindParameters& params) = 0;
|
||||
|
||||
virtual std::size_t getCount(Database::UserId userId, Database::ReleaseId releaseId) = 0;
|
||||
virtual std::size_t getCount(Database::UserId userId, Database::TrackId trackId) = 0;
|
||||
@@ -68,13 +93,10 @@ namespace Scrobbling
|
||||
virtual Wt::WDateTime getLastListenDateTime(Database::UserId userId, Database::TrackId trackId) = 0;
|
||||
|
||||
// Top
|
||||
virtual ArtistContainer getTopArtists(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, std::optional<Database::TrackArtistLinkType> linkType, Database::Range) = 0;
|
||||
virtual ReleaseContainer getTopReleases(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) = 0;
|
||||
virtual TrackContainer getTopTracks(Database::UserId userId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) = 0;
|
||||
virtual TrackContainer getTopTracks(Database::UserId userId, Database::ArtistId artistId, const std::vector<Database::ClusterId>& clusterIds, Database::Range range) = 0;
|
||||
virtual ArtistContainer getTopArtists(const ArtistFindParameters& params) = 0;
|
||||
virtual ReleaseContainer getTopReleases(const FindParameters& params) = 0;
|
||||
virtual TrackContainer getTopTracks(const FindParameters& params) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IScrobblingService> createScrobblingService(boost::asio::io_service& ioService, Database::Db& db);
|
||||
|
||||
} // ns Scrobbling
|
||||
|
||||
|
||||
Reference in New Issue
Block a user