Initial support for multi library, still WIP

This commit is contained in:
emeric
2024-01-25 00:01:11 +01:00
parent f5f577af52
commit 0ed3f1ba79
85 changed files with 3076 additions and 1445 deletions
@@ -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
+15 -15
View File
@@ -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