From 6fca5d4d4e4ecc12b5fa60de279e268434d25f3b Mon Sep 17 00:00:00 2001 From: emeric Date: Tue, 24 Nov 2020 08:48:37 +0100 Subject: [PATCH] Reload cover resources when the database has been updated --- src/libs/scanner/CMakeLists.txt | 4 +- .../impl/{MediaScanner.cpp => Scanner.cpp} | 68 +++++++-------- .../impl/{MediaScanner.hpp => Scanner.hpp} | 32 +++---- ...MediaScannerStats.cpp => ScannerStats.cpp} | 2 +- .../scanner/include/scanner/IMediaScanner.hpp | 84 ------------------- src/libs/scanner/include/scanner/IScanner.hpp | 72 ++++++++++++++++ .../scanner/include/scanner/ScannerEvents.hpp | 46 ++++++++++ ...MediaScannerStats.hpp => ScannerStats.hpp} | 0 src/libs/subsonic/impl/Scan.cpp | 10 +-- src/lms/CMakeLists.txt | 2 +- src/lms/main.cpp | 55 ++++++++++-- src/lms/ui/LmsApplication.cpp | 77 ++++------------- src/lms/ui/LmsApplication.hpp | 56 ++++++------- src/lms/ui/MediaPlayer.cpp | 23 ++--- src/lms/ui/MediaPlayer.hpp | 5 ++ src/lms/ui/PlayQueue.cpp | 10 +-- src/lms/ui/SettingsView.cpp | 4 +- src/lms/ui/admin/DatabaseSettingsView.cpp | 7 +- src/lms/ui/admin/InitWizardView.cpp | 2 +- src/lms/ui/admin/ScannerController.cpp | 23 ++--- src/lms/ui/admin/UserView.cpp | 2 +- src/lms/ui/explore/ArtistView.cpp | 1 + src/lms/ui/explore/ArtistsView.cpp | 5 +- src/lms/ui/explore/Filters.cpp | 2 +- src/lms/ui/explore/ReleaseListHelpers.cpp | 4 +- src/lms/ui/explore/ReleasePopup.cpp | 2 + src/lms/ui/explore/ReleaseView.cpp | 4 +- src/lms/ui/explore/ReleasesView.cpp | 2 +- src/lms/ui/explore/SearchView.cpp | 1 - src/lms/ui/explore/TrackListHelpers.cpp | 12 +-- src/lms/ui/explore/TrackPopup.cpp | 2 + src/lms/ui/explore/TracksView.cpp | 4 +- .../{ImageResource.cpp => CoverResource.cpp} | 19 +++-- .../{ImageResource.hpp => CoverResource.hpp} | 5 +- 34 files changed, 348 insertions(+), 299 deletions(-) rename src/libs/scanner/impl/{MediaScanner.cpp => Scanner.cpp} (94%) rename src/libs/scanner/impl/{MediaScanner.hpp => Scanner.hpp} (78%) rename src/libs/scanner/impl/{MediaScannerStats.cpp => ScannerStats.cpp} (96%) delete mode 100644 src/libs/scanner/include/scanner/IMediaScanner.hpp create mode 100644 src/libs/scanner/include/scanner/IScanner.hpp create mode 100644 src/libs/scanner/include/scanner/ScannerEvents.hpp rename src/libs/scanner/include/scanner/{MediaScannerStats.hpp => ScannerStats.hpp} (100%) rename src/lms/ui/resource/{ImageResource.cpp => CoverResource.cpp} (86%) rename src/lms/ui/resource/{ImageResource.hpp => CoverResource.hpp} (93%) diff --git a/src/libs/scanner/CMakeLists.txt b/src/libs/scanner/CMakeLists.txt index 4f5fa30b..5f1467b8 100644 --- a/src/libs/scanner/CMakeLists.txt +++ b/src/libs/scanner/CMakeLists.txt @@ -1,8 +1,8 @@ add_library(lmsscanner SHARED impl/AcousticBrainzUtils.cpp - impl/MediaScanner.cpp - impl/MediaScannerStats.cpp + impl/Scanner.cpp + impl/ScannerStats.cpp ) target_include_directories(lmsscanner INTERFACE diff --git a/src/libs/scanner/impl/MediaScanner.cpp b/src/libs/scanner/impl/Scanner.cpp similarity index 94% rename from src/libs/scanner/impl/MediaScanner.cpp rename to src/libs/scanner/impl/Scanner.cpp index 5f3660f2..5c1b61b5 100644 --- a/src/libs/scanner/impl/MediaScanner.cpp +++ b/src/libs/scanner/impl/Scanner.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "MediaScanner.hpp" +#include "Scanner.hpp" #include #include @@ -242,13 +242,13 @@ getOrCreateClusters(Session& session, const MetaData::Clusters& clustersNames) namespace Scanner { -std::unique_ptr -createMediaScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine) +std::unique_ptr +createScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine) { - return std::make_unique(db, recommendationEngine); + return std::make_unique(db, recommendationEngine); } -MediaScanner::MediaScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine) +Scanner::Scanner(Database::Db& db, Recommendation::IEngine& recommendationEngine) : _recommendationEngine {recommendationEngine} , _dbSession {db} { @@ -262,14 +262,14 @@ MediaScanner::MediaScanner(Database::Db& db, Recommendation::IEngine& recommenda start(); } -MediaScanner::~MediaScanner() +Scanner::~Scanner() { - LMS_LOG(DBUPDATER, INFO) << "Shutting down MediaScanner..."; + LMS_LOG(DBUPDATER, INFO) << "Shutting down Scanner..."; stop(); } void -MediaScanner::start() +Scanner::start() { std::scoped_lock lock {_controlMutex}; @@ -290,7 +290,7 @@ MediaScanner::start() } void -MediaScanner::stop() +Scanner::stop() { std::scoped_lock lock {_controlMutex}; @@ -301,7 +301,7 @@ MediaScanner::stop() } void -MediaScanner::abortScan() +Scanner::abortScan() { LMS_LOG(DBUPDATER, DEBUG) << "Aborting scan..."; std::scoped_lock lock {_controlMutex}; @@ -319,7 +319,7 @@ MediaScanner::abortScan() } void -MediaScanner::requestImmediateScan(bool force) +Scanner::requestImmediateScan(bool force) { abortScan(); _ioService.post([=]() @@ -332,7 +332,7 @@ MediaScanner::requestImmediateScan(bool force) } void -MediaScanner::requestReload() +Scanner::requestReload() { abortScan(); _ioService.post([=]() @@ -344,8 +344,8 @@ MediaScanner::requestReload() }); } -MediaScanner::Status -MediaScanner::getStatus() const +Scanner::Status +Scanner::getStatus() const { Status res; @@ -360,7 +360,7 @@ MediaScanner::getStatus() const } void -MediaScanner::scheduleNextScan() +Scanner::scheduleNextScan() { LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan"; @@ -411,11 +411,11 @@ MediaScanner::scheduleNextScan() _nextScheduledScan = nextScanDateTime; } - _sigScheduled.emit(_nextScheduledScan); + _events.scanScheduled.emit(_nextScheduledScan); } void -MediaScanner::countAllFiles(ScanStats& stats) +Scanner::countAllFiles(ScanStats& stats) { ScanStepStats stepStats{stats.startTime, ScanProgressStep::DiscoveringFiles}; @@ -440,7 +440,7 @@ MediaScanner::countAllFiles(ScanStats& stats) } void -MediaScanner::scheduleScan(bool force, const Wt::WDateTime& dateTime) +Scanner::scheduleScan(bool force, const Wt::WDateTime& dateTime) { auto cb {[=](boost::system::error_code ec) { @@ -469,9 +469,9 @@ MediaScanner::scheduleScan(bool force, const Wt::WDateTime& dateTime) } void -MediaScanner::scan(bool forceScan) +Scanner::scan(bool forceScan) { - scanStarted().emit(); + _events.scanStarted.emit(); { std::unique_lock lock {_statusMutex}; @@ -517,14 +517,14 @@ MediaScanner::scan(bool forceScan) { std::unique_lock lock {_statusMutex}; - _lastCompleteScanStats = std::move(stats); + _lastCompleteScanStats = stats; _currentScanStepStats.reset(); } LMS_LOG(DBUPDATER, DEBUG) << "Scan not aborted, scheduling next scan!"; scheduleNextScan(); - scanComplete().emit(); + _events.scanComplete.emit(stats); } else { @@ -538,7 +538,7 @@ MediaScanner::scan(bool forceScan) } bool -MediaScanner::fetchTrackFeatures(Database::IdType trackId, const UUID& MBID) +Scanner::fetchTrackFeatures(Database::IdType trackId, const UUID& MBID) { std::map features; @@ -564,7 +564,7 @@ MediaScanner::fetchTrackFeatures(Database::IdType trackId, const UUID& MBID) } void -MediaScanner::fetchTrackFeatures(ScanStats& stats) +Scanner::fetchTrackFeatures(ScanStats& stats) { if (_recommendationEngineType != ScanSettings::RecommendationEngineType::Features) return; @@ -614,7 +614,7 @@ MediaScanner::fetchTrackFeatures(ScanStats& stats) } void -MediaScanner::refreshScanSettings() +Scanner::refreshScanSettings() { auto transaction {_dbSession.createSharedTransaction()}; @@ -646,7 +646,7 @@ MediaScanner::refreshScanSettings() } void -MediaScanner::notifyInProgress(const ScanStepStats& stepStats) +Scanner::notifyInProgress(const ScanStepStats& stepStats) { { std::unique_lock lock {_statusMutex}; @@ -654,12 +654,12 @@ MediaScanner::notifyInProgress(const ScanStepStats& stepStats) } const std::chrono::system_clock::time_point now {std::chrono::system_clock::now()}; - _sigScanInProgress(stepStats); + _events.scanInProgress(stepStats); _lastScanInProgressEmit = now; } void -MediaScanner::notifyInProgressIfNeeded(const ScanStepStats& stepStats) +Scanner::notifyInProgressIfNeeded(const ScanStepStats& stepStats) { std::chrono::system_clock::time_point now {std::chrono::system_clock::now()}; @@ -668,7 +668,7 @@ MediaScanner::notifyInProgressIfNeeded(const ScanStepStats& stepStats) } void -MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, ScanStats& stats) +Scanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, ScanStats& stats) { Wt::WDateTime lastWriteTime; try @@ -831,7 +831,7 @@ MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, S } void -MediaScanner::scanMediaDirectory(const std::filesystem::path& mediaDirectory, bool forceScan, ScanStats& stats) +Scanner::scanMediaDirectory(const std::filesystem::path& mediaDirectory, bool forceScan, ScanStats& stats) { ScanStepStats stepStats{stats.startTime, ScanProgressStep::ScanningFiles}; stepStats.totalElems = stats.filesScanned; @@ -899,7 +899,7 @@ checkFile(const std::filesystem::path& p, const std::filesystem::path& mediaDire } void -MediaScanner::removeMissingTracks(ScanStats& stats) +Scanner::removeMissingTracks(ScanStats& stats) { static constexpr std::size_t batchSize {50}; @@ -966,7 +966,7 @@ MediaScanner::removeMissingTracks(ScanStats& stats) } void -MediaScanner::removeOrphanEntries() +Scanner::removeOrphanEntries() { LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters..."; { @@ -1009,7 +1009,7 @@ MediaScanner::removeOrphanEntries() } void -MediaScanner::checkDuplicatedAudioFiles(ScanStats& stats) +Scanner::checkDuplicatedAudioFiles(ScanStats& stats) { LMS_LOG(DBUPDATER, INFO) << "Checking duplicated audio files"; @@ -1029,7 +1029,7 @@ MediaScanner::checkDuplicatedAudioFiles(ScanStats& stats) } void -MediaScanner::reloadSimilarityEngine(ScanStats& stats) +Scanner::reloadSimilarityEngine(ScanStats& stats) { ScanStepStats stepStats {stats.startTime, ScanProgressStep::ReloadingSimilarityEngine}; diff --git a/src/libs/scanner/impl/MediaScanner.hpp b/src/libs/scanner/impl/Scanner.hpp similarity index 78% rename from src/libs/scanner/impl/MediaScanner.hpp rename to src/libs/scanner/impl/Scanner.hpp index 6a78b86c..43a8b964 100644 --- a/src/libs/scanner/impl/MediaScanner.hpp +++ b/src/libs/scanner/impl/Scanner.hpp @@ -33,7 +33,7 @@ #include "database/ScanSettings.hpp" #include "database/Session.hpp" #include "metadata/IParser.hpp" -#include "scanner/IMediaScanner.hpp" +#include "scanner/IScanner.hpp" class UUID; @@ -44,29 +44,24 @@ namespace Recommendation namespace Scanner { -class MediaScanner : public IMediaScanner +class Scanner : public IScanner { public: - MediaScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine); - ~MediaScanner(); + Scanner(Database::Db& db, Recommendation::IEngine& recommendationEngine); + ~Scanner(); - MediaScanner(const MediaScanner&) = delete; - MediaScanner(MediaScanner&&) = delete; - MediaScanner& operator=(const MediaScanner&) = delete; - MediaScanner& operator=(MediaScanner&&) = delete; + Scanner(const Scanner&) = delete; + Scanner(Scanner&&) = delete; + Scanner& operator=(const Scanner&) = delete; + Scanner& operator=(Scanner&&) = delete; void requestReload() override; void requestImmediateScan(bool force) override; - Status getStatus() const override; - - Wt::Signal<>& scanStarted() override { return _sigScanStarted; } - Wt::Signal<>& scanComplete() override { return _sigScanComplete; } - Wt::Signal& scanInProgress() override { return _sigScanInProgress; } - Wt::Signal& scheduled() override { return _sigScheduled; } + Status getStatus() const override; + Events& getEvents() override { return _events; } private: - void start(); void stop(); @@ -102,11 +97,8 @@ class MediaScanner : public IMediaScanner std::atomic _abortScan {}; Wt::WIOService _ioService; boost::asio::system_timer _scheduleTimer {_ioService}; - Wt::Signal<> _sigScanStarted; - Wt::Signal<> _sigScanComplete; - Wt::Signal _sigScanInProgress; + Events _events; std::chrono::system_clock::time_point _lastScanInProgressEmit {}; - Wt::Signal _sigScheduled; Database::Session _dbSession; std::unique_ptr _metadataParser; @@ -124,7 +116,7 @@ class MediaScanner : public IMediaScanner std::filesystem::path _mediaDirectory; Database::ScanSettings::RecommendationEngineType _recommendationEngineType; -}; // class MediaScanner +}; // class Scanner } // Scanner diff --git a/src/libs/scanner/impl/MediaScannerStats.cpp b/src/libs/scanner/impl/ScannerStats.cpp similarity index 96% rename from src/libs/scanner/impl/MediaScannerStats.cpp rename to src/libs/scanner/impl/ScannerStats.cpp index 17450156..afc12f99 100644 --- a/src/libs/scanner/impl/MediaScannerStats.cpp +++ b/src/libs/scanner/impl/ScannerStats.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "scanner/MediaScannerStats.hpp" +#include "scanner/ScannerStats.hpp" namespace Scanner { diff --git a/src/libs/scanner/include/scanner/IMediaScanner.hpp b/src/libs/scanner/include/scanner/IMediaScanner.hpp deleted file mode 100644 index ec8f64d5..00000000 --- a/src/libs/scanner/include/scanner/IMediaScanner.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (C) 2013 Emeric Poupon - * - * This file is part of LMS. - * - * LMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LMS. If not, see . - */ - -#pragma once - -#include - -#include -#include - -#include "MediaScannerStats.hpp" - -namespace Database -{ - class Db; -} - -namespace Recommendation -{ - class IEngine; -} - -namespace Scanner { - -class IMediaScanner -{ - public: - virtual ~IMediaScanner() = default; - - // Async requests - virtual void requestReload() = 0; - virtual void requestImmediateScan(bool force) = 0; - - enum class State - { - NotScheduled, - Scheduled, - InProgress, - }; - - struct Status - { - State currentState {State::NotScheduled}; - Wt::WDateTime nextScheduledScan; - std::optional lastCompleteScanStats; - std::optional currentScanStepStats; - }; - - virtual Status getStatus() const = 0; - - // Called just after scan start - virtual Wt::Signal<>& scanStarted() = 0; - - // Called just after scan complete - virtual Wt::Signal<>& scanComplete() = 0; - - // Called during scan in progress - virtual Wt::Signal& scanInProgress() = 0; - - // Called after a schedule - virtual Wt::Signal& scheduled() = 0; - -}; - -std::unique_ptr createMediaScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine); - -} // Scanner - diff --git a/src/libs/scanner/include/scanner/IScanner.hpp b/src/libs/scanner/include/scanner/IScanner.hpp new file mode 100644 index 00000000..018cca98 --- /dev/null +++ b/src/libs/scanner/include/scanner/IScanner.hpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include + +#include "ScannerEvents.hpp" +#include "ScannerStats.hpp" + +namespace Database +{ + class Db; +} + +namespace Recommendation +{ + class IEngine; +} + +namespace Scanner +{ + + class IScanner + { + public: + virtual ~IScanner() = default; + + // Async requests + virtual void requestReload() = 0; + virtual void requestImmediateScan(bool force) = 0; + + enum class State + { + NotScheduled, + Scheduled, + InProgress, + }; + + struct Status + { + State currentState {State::NotScheduled}; + Wt::WDateTime nextScheduledScan; + std::optional lastCompleteScanStats; + std::optional currentScanStepStats; + }; + + virtual Status getStatus() const = 0; + + virtual Events& getEvents() = 0; + }; + + std::unique_ptr createScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine); + +} // Scanner + diff --git a/src/libs/scanner/include/scanner/ScannerEvents.hpp b/src/libs/scanner/include/scanner/ScannerEvents.hpp new file mode 100644 index 00000000..c380bba6 --- /dev/null +++ b/src/libs/scanner/include/scanner/ScannerEvents.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2020 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include + +#include "ScannerStats.hpp" + +namespace Scanner +{ + + struct Events + { + // Called just after scan start + Wt::Signal<> scanStarted; + + // Called just after scan complete (true if changes have been made) + Wt::Signal scanComplete; + + // Called during scan in progress + Wt::Signal scanInProgress; + + // Called after a schedule + Wt::Signal scanScheduled; + }; + +} // ns Scanner + diff --git a/src/libs/scanner/include/scanner/MediaScannerStats.hpp b/src/libs/scanner/include/scanner/ScannerStats.hpp similarity index 100% rename from src/libs/scanner/include/scanner/MediaScannerStats.hpp rename to src/libs/scanner/include/scanner/ScannerStats.hpp diff --git a/src/libs/subsonic/impl/Scan.cpp b/src/libs/subsonic/impl/Scan.cpp index ff32b960..9d57fb17 100644 --- a/src/libs/subsonic/impl/Scan.cpp +++ b/src/libs/subsonic/impl/Scan.cpp @@ -19,7 +19,7 @@ #include "Scan.hpp" -#include "scanner/IMediaScanner.hpp" +#include "scanner/IScanner.hpp" #include "utils/Service.hpp" namespace API::Subsonic::Scan @@ -32,10 +32,10 @@ namespace API::Subsonic::Scan { Response::Node statusResponse; - const IMediaScanner::Status scanStatus {Service::get()->getStatus()}; + const IScanner::Status scanStatus {Service::get()->getStatus()}; - statusResponse.setAttribute("scanning", scanStatus.currentState == IMediaScanner::State::InProgress); - if (scanStatus.currentState == IMediaScanner::State::InProgress) + statusResponse.setAttribute("scanning", scanStatus.currentState == IScanner::State::InProgress); + if (scanStatus.currentState == IScanner::State::InProgress) { std::size_t count{}; @@ -61,7 +61,7 @@ namespace API::Subsonic::Scan Response handleStartScan(RequestContext& context) { - Service::get()->requestImmediateScan(false); + Service::get()->requestImmediateScan(false); Response response {Response::createOkResponse(context)}; response.addNode("scanStatus", createStatusResponseNode()); diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index ba77ba17..800c9e8d 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -32,8 +32,8 @@ add_executable(lms ui/explore/TracksView.cpp ui/resource/AudioFileResource.cpp ui/resource/AudioTranscodeResource.cpp + ui/resource/CoverResource.cpp ui/resource/DownloadResource.cpp - ui/resource/ImageResource.cpp ) target_include_directories(lms PRIVATE diff --git a/src/lms/main.cpp b/src/lms/main.cpp index 202b71d7..f190371f 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -31,7 +31,7 @@ #include "cover/ICoverArtGrabber.hpp" #include "database/Db.hpp" #include "database/Session.hpp" -#include "scanner/IMediaScanner.hpp" +#include "scanner/IScanner.hpp" #include "recommendation/IEngine.hpp" #include "subsonic/SubsonicResource.hpp" #include "ui/LmsApplication.hpp" @@ -119,6 +119,47 @@ generateWtConfig(std::string execPath) } +static +void +proxyScannerEventsToApplication(Scanner::IScanner& scanner, Wt::WServer& server) +{ + scanner.getEvents().scanStarted.connect([&] + { + server.postAll([] + { + LmsApp->getScannerEvents().scanStarted.emit(); + LmsApp->triggerUpdate(); + }); + }); + + scanner.getEvents().scanComplete.connect([&] (const Scanner::ScanStats& stats) + { + server.postAll([&] + { + LmsApp->getScannerEvents().scanComplete.emit(stats); + LmsApp->triggerUpdate(); + }); + }); + + scanner.getEvents().scanInProgress.connect([&] (const Scanner::ScanStepStats& stats) + { + server.postAll([&] + { + LmsApp->getScannerEvents().scanInProgress.emit(stats); + LmsApp->triggerUpdate(); + }); + }); + + scanner.getEvents().scanScheduled.connect([&] (const Wt::WDateTime dateTime) + { + server.postAll([&] + { + LmsApp->getScannerEvents().scanScheduled.emit(dateTime); + LmsApp->triggerUpdate(); + }); + }); +} + int main(int argc, char* argv[]) { std::filesystem::path configFilePath {"/etc/lms.conf"}; @@ -184,9 +225,9 @@ int main(int argc, char* argv[]) config->getULong("cover-max-file-size", 10) * 1000 * 1000, config->getULong("cover-jpeg-quality", 75))}; Service recommendationEngineService {Recommendation::createEngine(database)}; - Service mediaScannerService {Scanner::createMediaScanner(database, *recommendationEngineService)}; + Service scannerService {Scanner::createScanner(database, *recommendationEngineService)}; - mediaScannerService->scanComplete().connect([&]() + scannerService->getEvents().scanComplete.connect([&] { // Flush cover cache even if no changes: // covers may be external files that changed and we don't keep track of them @@ -201,8 +242,12 @@ int main(int argc, char* argv[]) // bind UI entry point server.addEntryPoint(Wt::EntryPointType::Application, - std::bind(UserInterface::LmsApplication::create, - std::placeholders::_1, std::ref(database), std::ref(appGroups))); + [&](const Wt::WEnvironment &env) + { + return UserInterface::LmsApplication::create(env, database, appGroups); + }); + + proxyScannerEventsToApplication(*scannerService, server); LMS_LOG(MAIN, INFO) << "Starting server..."; server.start(); diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index a38c3da1..2c81e9c7 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -34,8 +35,6 @@ #include "database/Release.hpp" #include "database/Session.hpp" #include "database/User.hpp" -#include "explore/Explore.hpp" -#include "explore/Filters.hpp" #include "utils/Logger.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" @@ -44,10 +43,12 @@ #include "admin/DatabaseSettingsView.hpp" #include "admin/UserView.hpp" #include "admin/UsersView.hpp" +#include "explore/Explore.hpp" +#include "explore/Filters.hpp" #include "resource/AudioFileResource.hpp" #include "resource/AudioTranscodeResource.hpp" #include "resource/DownloadResource.hpp" -#include "resource/ImageResource.hpp" +#include "resource/CoverResource.hpp" #include "Auth.hpp" #include "LmsApplicationException.hpp" #include "LmsTheme.hpp" @@ -227,6 +228,8 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, } } +LmsApplication::~LmsApplication() = default; + void LmsApplication::finalize() { @@ -440,9 +443,7 @@ LmsApplication::handleUserLoggedIn(Database::IdType userId, bool strongAuth) void LmsApplication::createHome() { - _audioFileResource = std::make_shared(); - _audioTranscodeResource = std::make_shared(); - _imageResource = std::make_shared(); + _coverResource = std::make_shared(); declareJavaScriptFunction("onLoadCover", "function(id) { id.className += \" Lms-cover-loaded\"}"); doJavaScript("$('body').tooltip({ selector: '[data-toggle=\"tooltip\"]'})"); @@ -532,59 +533,15 @@ LmsApplication::createHome() _mediaPlayer->loadTrack(trackId, play, replayGain); }); - _playQueue->trackUnselected.connect([this] () + _playQueue->trackUnselected.connect([this] { _mediaPlayer->stop(); }); - // Events from MediaScanner + if (isUserAdmin()) { - const std::string sessionId {LmsApp->sessionId()}; - - Service::get()->scanStarted().connect(this, [=] + _scannerEvents.scanComplete.connect([=] (const Scanner::ScanStats& stats) { - Wt::WServer::instance()->post(sessionId, [=] - { - _events.dbScanStarted.emit(); - triggerUpdate(); - }); - }); - - Service::get()->scanComplete().connect(this, [=] - { - Wt::WServer::instance()->post(sessionId, [this] - { - _events.dbScanned.emit(); - triggerUpdate(); - }); - }); - - Service::get()->scanInProgress().connect(this, [=] (Scanner::ScanStepStats stepStats) - { - Wt::WServer::instance()->post(sessionId, [=] - { - _events.dbScanInProgress.emit(stepStats); - triggerUpdate(); - }); - }); - - Service::get()->scheduled().connect(this, [=] (Wt::WDateTime dateTime) - { - Wt::WServer::instance()->post(sessionId, [=] - { - _events.dbScanScheduled.emit(dateTime); - triggerUpdate(); - }); - }); - - } - - _events.dbScanned.connect([=] () - { - if (isUserAdmin()) - { - const auto& stats {*Service::get()->getStatus().lastCompleteScanStats}; - notifyMsg(MsgType::Info, Wt::WString::tr("Lms.Admin.Database.scan-complete") .arg(static_cast(stats.nbFiles())) .arg(static_cast(stats.additions)) @@ -592,8 +549,8 @@ LmsApplication::createHome() .arg(static_cast(stats.deletions)) .arg(static_cast(stats.duplicates.size())) .arg(static_cast(stats.errors.size()))); - } - }); + }); + } // Events from Application group _events.appOpen.connect([=] @@ -632,14 +589,14 @@ LmsApplication::notify(const Wt::WEvent& event) } } -static std::string msgTypeToString(MsgType type) +static std::string msgTypeToString(LmsApplication::MsgType type) { switch(type) { - case MsgType::Success: return "success"; - case MsgType::Info: return "info"; - case MsgType::Warning: return "warning"; - case MsgType::Danger: return "danger"; + case LmsApplication::MsgType::Success: return "success"; + case LmsApplication::MsgType::Info: return "info"; + case LmsApplication::MsgType::Warning: return "warning"; + case LmsApplication::MsgType::Danger: return "danger"; } return ""; } diff --git a/src/lms/ui/LmsApplication.hpp b/src/lms/ui/LmsApplication.hpp index 757dc126..9718c534 100644 --- a/src/lms/ui/LmsApplication.hpp +++ b/src/lms/ui/LmsApplication.hpp @@ -22,13 +22,18 @@ #include #include -#include -#include "scanner/IMediaScanner.hpp" +#include "scanner/ScannerEvents.hpp" #include "LmsApplicationGroup.hpp" -namespace Database { +namespace Wt +{ + class WPopupMenu; +} + +namespace Database +{ class Artist; class Cluster; class Db; @@ -39,10 +44,8 @@ namespace Database { namespace UserInterface { -class AudioTranscodeResource; -class AudioFileResource; class Auth; -class ImageResource; +class CoverResource; class LmsApplicationException; class MediaPlayer; class PlayQueue; @@ -53,35 +56,19 @@ struct Events // Events relative to group Wt::Signal appOpen; Wt::Signal appClosed; - - // Database events - Wt::Signal<> dbScanStarted; - Wt::Signal<> dbScanned; - Wt::Signal dbScanInProgress; - Wt::Signal dbScanScheduled; -}; - -// Used to classify the message sent to the user -enum class MsgType -{ - Success, - Info, - Warning, - Danger, }; class LmsApplication : public Wt::WApplication { public: LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups); + ~LmsApplication(); static std::unique_ptr create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups); static LmsApplication* instance(); // Session application data - std::shared_ptr getImageResource() { return _imageResource; } - std::shared_ptr getAudioTranscodeResource() { return _audioTranscodeResource; } - std::shared_ptr getAudioFileResource() { return _audioFileResource; } + std::shared_ptr getCoverResource() { return _coverResource; } Database::Session& getDbSession(); // always thread safe Wt::Dbo::ptr getUser(); @@ -91,10 +78,20 @@ class LmsApplication : public Wt::WApplication std::string getUserLoginName(); // user must be logged in prior this call Events& getEvents() { return _events; } + Scanner::Events& getScannerEvents() { return _scannerEvents; } // Utils void post(std::function func); - void notifyMsg(MsgType type, const Wt::WString& message, std::chrono::milliseconds duration = std::chrono::milliseconds(4000)); + + // Used to classify the message sent to the user + enum class MsgType + { + Success, + Info, + Warning, + Danger, + }; + void notifyMsg(MsgType type, const Wt::WString& message, std::chrono::milliseconds duration = std::chrono::milliseconds {4000}); static Wt::WLink createArtistLink(Wt::Dbo::ptr artist); static std::unique_ptr createArtistAnchor(Wt::Dbo::ptr artist, bool addText = true); @@ -129,19 +126,18 @@ class LmsApplication : public Wt::WApplication Wt::Signal<> _preQuit; LmsApplicationGroupContainer& _appGroups; Events _events; + Scanner::Events _scannerEvents; std::optional _userId; std::optional _userAuthStrong; - std::shared_ptr _audioTranscodeResource; - std::shared_ptr _audioFileResource; - std::shared_ptr _imageResource; + std::shared_ptr _coverResource; MediaPlayer* _mediaPlayer {}; PlayQueue* _playQueue {}; - std::unique_ptr _popupMenu {}; + std::unique_ptr _popupMenu; }; // Helper to get session instance -#define LmsApp LmsApplication::instance() +#define LmsApp ::UserInterface::LmsApplication::instance() } // namespace UserInterface diff --git a/src/lms/ui/MediaPlayer.cpp b/src/lms/ui/MediaPlayer.cpp index abebac64..cb771f2b 100644 --- a/src/lms/ui/MediaPlayer.cpp +++ b/src/lms/ui/MediaPlayer.cpp @@ -33,7 +33,7 @@ #include "database/Types.hpp" #include "database/User.hpp" -#include "resource/ImageResource.hpp" +#include "resource/CoverResource.hpp" #include "resource/AudioTranscodeResource.hpp" #include "resource/AudioFileResource.hpp" @@ -192,14 +192,17 @@ static MediaPlayer::Settings settingsfromJSString(const std::string& strSettings } MediaPlayer::MediaPlayer() -: Wt::WTemplate {Wt::WString::tr("Lms.MediaPlayer.template")}, - playbackEnded {this, "playbackEnded"}, - playPrevious {this, "playPrevious"}, - playNext {this, "playNext"}, - _settingsLoaded {this, "settingsLoaded"} +: Wt::WTemplate {Wt::WString::tr("Lms.MediaPlayer.template")} +, playbackEnded {this, "playbackEnded"} +, playPrevious {this, "playPrevious"} +, playNext {this, "playNext"} +, _settingsLoaded {this, "settingsLoaded"} { addFunction("tr", &Wt::WTemplate::Functions::tr); + _audioTranscodeResource = std::make_unique(); + _audioFileResource = std::make_unique(); + _title = bindNew("title"); _artist = bindNew("artist"); _release = bindNew("release"); @@ -240,8 +243,8 @@ MediaPlayer::loadTrack(Database::IdType trackId, bool play, float replayGain) if (!track) return; - const std::string transcodeResource {LmsApp->getAudioTranscodeResource()->getUrl(trackId)}; - const std::string nativeResource {LmsApp->getAudioFileResource()->getUrl(trackId)}; + const std::string transcodeResource {_audioTranscodeResource->getUrl(trackId)}; + const std::string nativeResource {_audioFileResource->getUrl(trackId)}; const auto artists {track->getArtists({Database::TrackArtistLinkType::Artist})}; @@ -255,8 +258,8 @@ MediaPlayer::loadTrack(Database::IdType trackId, bool play, float replayGain) << " artist: \"" << (!artists.empty() ? StringUtils::jsEscape(artists.front()->getName()) : "") << "\"," << " release: \"" << (track->getRelease() ? StringUtils::jsEscape(track->getRelease()->getName()) : "") << "\"," << " artwork: [" - << " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, ImageResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" }," - << " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, ImageResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" }," + << " { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" }," + << " { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" }," << " ]" << "};"; oss << "LMS.mediaplayer.loadTrack(params, " << (play ? "true" : "false") << ")"; // true to autoplay diff --git a/src/lms/ui/MediaPlayer.hpp b/src/lms/ui/MediaPlayer.hpp index 58f7b432..af2374b9 100644 --- a/src/lms/ui/MediaPlayer.hpp +++ b/src/lms/ui/MediaPlayer.hpp @@ -29,6 +29,9 @@ namespace UserInterface { +class AudioFileResource; +class AudioTranscodeResource; + class MediaPlayer : public Wt::WTemplate { public: @@ -105,6 +108,8 @@ class MediaPlayer : public Wt::WTemplate Wt::Signal<> settingsLoaded; private: + std::unique_ptr _audioFileResource; + std::unique_ptr _audioTranscodeResource; std::optional _trackIdLoaded; std::optional _settings; diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp index 7b8e89cb..c1bd31f1 100644 --- a/src/lms/ui/PlayQueue.cpp +++ b/src/lms/ui/PlayQueue.cpp @@ -34,7 +34,7 @@ #include "utils/String.hpp" #include "common/LoadingIndicator.hpp" -#include "resource/ImageResource.hpp" +#include "resource/CoverResource.hpp" #include "LmsApplication.hpp" #include "MediaPlayer.hpp" #include "TrackStringUtils.hpp" @@ -394,10 +394,10 @@ PlayQueue::processTracks(PlayQueueAction action, const std::vector 0) - LmsApp->notifyMsg(MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", nbAddedTracks).arg(nbAddedTracks), std::chrono::milliseconds(2000)); + LmsApp->notifyMsg(LmsApplication::MsgType::Info, Wt::WString::trn("Lms.PlayQueue.nb-tracks-added", nbAddedTracks).arg(nbAddedTracks), std::chrono::milliseconds(2000)); if (isFull()) - LmsApp->notifyMsg(MsgType::Warning, Wt::WString::tr("Lms.PlayQueue.playqueue-full"), std::chrono::milliseconds(2000)); + LmsApp->notifyMsg(LmsApplication::MsgType::Warning, Wt::WString::tr("Lms.PlayQueue.playqueue-full"), std::chrono::milliseconds(2000)); } @@ -443,7 +443,7 @@ PlayQueue::addSome() { Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false)); auto cover = std::make_unique(); - cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), ImageResource::Size::Large)); + cover->setImageLink(LmsApp->getCoverResource()->getReleaseUrl(release.id(), CoverResource::Size::Large)); cover->setStyleClass("Lms-cover"); cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); anchor->setImage(std::move(cover)); @@ -452,7 +452,7 @@ PlayQueue::addSome() else { auto cover = entry->bindNew("cover"); - cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(track.id(), ImageResource::Size::Large)); + cover->setImageLink(LmsApp->getCoverResource()->getTrackUrl(track.id(), CoverResource::Size::Large)); cover->setStyleClass("Lms-cover"); cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); } diff --git a/src/lms/ui/SettingsView.cpp b/src/lms/ui/SettingsView.cpp index 33f50856..1d5531b4 100644 --- a/src/lms/ui/SettingsView.cpp +++ b/src/lms/ui/SettingsView.cpp @@ -514,7 +514,7 @@ SettingsView::refreshView() if (LmsApp->getUser()->isDemo()) { - LmsApp->notifyMsg(MsgType::Warning, Wt::WString::tr("Lms.Settings.demo-cannot-save")); + LmsApp->notifyMsg(LmsApplication::MsgType::Warning, Wt::WString::tr("Lms.Settings.demo-cannot-save")); return; } } @@ -524,7 +524,7 @@ SettingsView::refreshView() if (model->validate()) { model->saveData(); - LmsApp->notifyMsg(MsgType::Success, Wt::WString::tr("Lms.Settings.settings-saved")); + LmsApp->notifyMsg(LmsApplication::MsgType::Success, Wt::WString::tr("Lms.Settings.settings-saved")); } // Udate the view: Delete any validation message in the view, etc. diff --git a/src/lms/ui/admin/DatabaseSettingsView.cpp b/src/lms/ui/admin/DatabaseSettingsView.cpp index af11f6ea..aa35e0ae 100644 --- a/src/lms/ui/admin/DatabaseSettingsView.cpp +++ b/src/lms/ui/admin/DatabaseSettingsView.cpp @@ -29,6 +29,7 @@ #include "database/Cluster.hpp" #include "database/ScanSettings.hpp" #include "database/Session.hpp" +#include "scanner/IScanner.hpp" #include "utils/Logger.hpp" #include "utils/Service.hpp" #include "utils/String.hpp" @@ -222,8 +223,8 @@ DatabaseSettingsView::refreshView() { model->saveData(); - Service::get()->requestImmediateScan(false); - LmsApp->notifyMsg(MsgType::Success, Wt::WString::tr("Lms.Admin.Database.settings-saved")); + Service::get()->requestImmediateScan(false); + LmsApp->notifyMsg(LmsApplication::MsgType::Success, Wt::WString::tr("Lms.Admin.Database.settings-saved")); } // Udate the view: Delete any validation message in the view, etc. @@ -239,7 +240,7 @@ DatabaseSettingsView::refreshView() immScanBtn->clicked().connect([=] { - Service::get()->requestImmediateScan(false); + Service::get()->requestImmediateScan(false); }); t->updateView(model.get()); diff --git a/src/lms/ui/admin/InitWizardView.cpp b/src/lms/ui/admin/InitWizardView.cpp index 8f39e2b0..39b5776f 100644 --- a/src/lms/ui/admin/InitWizardView.cpp +++ b/src/lms/ui/admin/InitWizardView.cpp @@ -202,7 +202,7 @@ InitWizardView::InitWizardView() if (model->validate()) { model->saveData(); - LmsApp->notifyMsg(MsgType::Success, Wt::WString::tr("Lms.Admin.InitWizard.done")); + LmsApp->notifyMsg(LmsApplication::MsgType::Success, Wt::WString::tr("Lms.Admin.InitWizard.done")); saveButton->setEnabled(false); } diff --git a/src/lms/ui/admin/ScannerController.cpp b/src/lms/ui/admin/ScannerController.cpp index 87f9ae9b..d19b4974 100644 --- a/src/lms/ui/admin/ScannerController.cpp +++ b/src/lms/ui/admin/ScannerController.cpp @@ -30,6 +30,7 @@ #include "database/Session.hpp" #include "database/Track.hpp" +#include "scanner/IScanner.hpp" #include "utils/Service.hpp" #include "LmsApplication.hpp" @@ -139,13 +140,13 @@ ScannerController::ScannerController() auto onDbEvent = [&]() { refreshContents(); }; - LmsApp->getEvents().dbScanStarted.connect(this, [] + LmsApp->getScannerEvents().scanStarted.connect(this, [] { - LmsApp->notifyMsg(MsgType::Info, Wt::WString::tr("Lms.Admin.Database.scan-launched")); + LmsApp->notifyMsg(LmsApplication::MsgType::Info, Wt::WString::tr("Lms.Admin.Database.scan-launched")); }); - LmsApp->getEvents().dbScanned.connect(this, onDbEvent); - LmsApp->getEvents().dbScanInProgress.connect(this, onDbEvent); - LmsApp->getEvents().dbScanScheduled.connect(this, onDbEvent); + LmsApp->getScannerEvents().scanComplete.connect(this, onDbEvent); + LmsApp->getScannerEvents().scanInProgress.connect(this, onDbEvent); + LmsApp->getScannerEvents().scanScheduled.connect(this, onDbEvent); refreshContents(); } @@ -162,20 +163,20 @@ ScannerController::refreshContents() actionBtn->actionButton()->setText(Wt::WString::tr("Lms.Admin.ScannerController.scan-now")); actionBtn->actionButton()->clicked().connect([] { - Service::get()->requestImmediateScan(false); + Service::get()->requestImmediateScan(false); }); auto popup = std::make_unique(); popup->addItem(Wt::WString::tr("Lms.Admin.ScannerController.force-scan-now")); popup->itemSelected().connect([] { - Service::get()->requestImmediateScan(true); + Service::get()->requestImmediateScan(true); }); actionBtn->dropDownButton()->setMenu(std::move(popup)); actionBtn->dropDownButton()->addStyleClass("btn-primary"); - const IMediaScanner::Status status {Service::get()->getStatus()}; + const IScanner::Status status {Service::get()->getStatus()}; if (status.lastCompleteScanStats) { bindString("last-scan", Wt::WString::tr("Lms.Admin.ScannerController.last-scan-status") @@ -199,16 +200,16 @@ ScannerController::refreshContents() switch (status.currentState) { - case IMediaScanner::State::NotScheduled: + case IScanner::State::NotScheduled: bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-not-scheduled")); bindEmpty("step-status"); break; - case IMediaScanner::State::Scheduled: + case IScanner::State::Scheduled: bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-scheduled") .arg(status.nextScheduledScan.toString())); bindEmpty("step-status"); break; - case IMediaScanner::State::InProgress: + case IScanner::State::InProgress: bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-in-progress") .arg(static_cast(status.currentScanStepStats->currentStep) + 1) .arg(Scanner::ScanProgressStepCount)); diff --git a/src/lms/ui/admin/UserView.cpp b/src/lms/ui/admin/UserView.cpp index 33cfb6fb..4ec962cb 100644 --- a/src/lms/ui/admin/UserView.cpp +++ b/src/lms/ui/admin/UserView.cpp @@ -316,7 +316,7 @@ UserView::refreshView() if (model->validate()) { model->saveData(); - LmsApp->notifyMsg(MsgType::Success, Wt::WString::tr(userId ? "Lms.Admin.User.user-updated" : "Lms.Admin.User.user-created")); + LmsApp->notifyMsg(LmsApplication::MsgType::Success, Wt::WString::tr(userId ? "Lms.Admin.User.user-updated" : "Lms.Admin.User.user-created")); LmsApp->setInternalPath("/admin/users", true); } else diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp index 70f4242d..79dbfc9f 100644 --- a/src/lms/ui/explore/ArtistView.cpp +++ b/src/lms/ui/explore/ArtistView.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include diff --git a/src/lms/ui/explore/ArtistsView.cpp b/src/lms/ui/explore/ArtistsView.cpp index bdf7430e..25f34307 100644 --- a/src/lms/ui/explore/ArtistsView.cpp +++ b/src/lms/ui/explore/ArtistsView.cpp @@ -73,9 +73,10 @@ Artists::Artists(Filters* filters) _linkType->changed().connect([this] { refreshView(); }); refreshArtistLinkTypes(); - LmsApp->getEvents().dbScanned.connect(this, [this] + LmsApp->getScannerEvents().scanComplete.connect(this, [this](const Scanner::ScanStats& stats) { - refreshArtistLinkTypes(); + if (stats.nbChanges()) + refreshArtistLinkTypes(); }); _container = bindNew("artists"); diff --git a/src/lms/ui/explore/Filters.cpp b/src/lms/ui/explore/Filters.cpp index 06c34da9..0796ab01 100644 --- a/src/lms/ui/explore/Filters.cpp +++ b/src/lms/ui/explore/Filters.cpp @@ -150,7 +150,7 @@ Filters::add(Database::IdType clusterId) _sigUpdated.emit(); }); - LmsApp->notifyMsg(MsgType::Info, Wt::WString::tr("Lms.Explore.filter-added"), std::chrono::seconds {2}); + LmsApp->notifyMsg(LmsApplication::MsgType::Info, Wt::WString::tr("Lms.Explore.filter-added"), std::chrono::seconds {2}); _sigUpdated.emit(); } diff --git a/src/lms/ui/explore/ReleaseListHelpers.cpp b/src/lms/ui/explore/ReleaseListHelpers.cpp index b77945dc..fc29d3c1 100644 --- a/src/lms/ui/explore/ReleaseListHelpers.cpp +++ b/src/lms/ui/explore/ReleaseListHelpers.cpp @@ -25,7 +25,7 @@ #include "database/Artist.hpp" #include "database/Release.hpp" -#include "resource/ImageResource.hpp" +#include "resource/CoverResource.hpp" #include "LmsApplication.hpp" @@ -44,7 +44,7 @@ namespace UserInterface::ReleaseListHelpers Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false)); auto cover = std::make_unique(); - cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), ImageResource::Size::Large)); + cover->setImageLink(LmsApp->getCoverResource()->getReleaseUrl(release.id(), CoverResource::Size::Large)); cover->setStyleClass("Lms-cover"); cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); anchor->setImage(std::move(cover)); diff --git a/src/lms/ui/explore/ReleasePopup.cpp b/src/lms/ui/explore/ReleasePopup.cpp index 79e64497..79617838 100644 --- a/src/lms/ui/explore/ReleasePopup.cpp +++ b/src/lms/ui/explore/ReleasePopup.cpp @@ -19,6 +19,8 @@ #include "ReleasePopup.hpp" +#include + #include "database/Release.hpp" #include "database/Session.hpp" #include "database/User.hpp" diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index d2dcacac..6c393eac 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -34,7 +34,7 @@ #include "utils/String.hpp" #include "resource/DownloadResource.hpp" -#include "resource/ImageResource.hpp" +#include "resource/CoverResource.hpp" #include "Filters.hpp" #include "LmsApplication.hpp" #include "LmsApplicationException.hpp" @@ -127,7 +127,7 @@ Release::refreshView() } { - Wt::WImage* cover {bindNew("cover", Wt::WLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), ImageResource::Size::Large)))}; + Wt::WImage* cover {bindNew("cover", Wt::WLink(LmsApp->getCoverResource()->getReleaseUrl(release.id(), CoverResource::Size::Large)))}; cover->setStyleClass("Lms-cover-large"); cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); } diff --git a/src/lms/ui/explore/ReleasesView.cpp b/src/lms/ui/explore/ReleasesView.cpp index afc4d3cf..84ac60d0 100644 --- a/src/lms/ui/explore/ReleasesView.cpp +++ b/src/lms/ui/explore/ReleasesView.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include "database/Release.hpp" @@ -34,7 +35,6 @@ #include "utils/String.hpp" #include "common/LoadingIndicator.hpp" -#include "resource/ImageResource.hpp" #include "ReleaseListHelpers.hpp" #include "Filters.hpp" #include "LmsApplication.hpp" diff --git a/src/lms/ui/explore/SearchView.cpp b/src/lms/ui/explore/SearchView.cpp index e62c5aff..61897b63 100644 --- a/src/lms/ui/explore/SearchView.cpp +++ b/src/lms/ui/explore/SearchView.cpp @@ -27,7 +27,6 @@ #include "database/Session.hpp" #include "database/Track.hpp" -#include "resource/ImageResource.hpp" #include "ArtistListHelpers.hpp" #include "Filters.hpp" #include "LmsApplication.hpp" diff --git a/src/lms/ui/explore/TrackListHelpers.cpp b/src/lms/ui/explore/TrackListHelpers.cpp index 7d241b3c..0fc280fb 100644 --- a/src/lms/ui/explore/TrackListHelpers.cpp +++ b/src/lms/ui/explore/TrackListHelpers.cpp @@ -28,7 +28,7 @@ #include "database/Release.hpp" #include "database/Track.hpp" #include "resource/DownloadResource.hpp" -#include "resource/ImageResource.hpp" +#include "resource/CoverResource.hpp" #include "LmsApplication.hpp" #include "MediaPlayer.hpp" #include "TrackPopup.hpp" @@ -71,9 +71,9 @@ namespace UserInterface::TrackListHelpers entry->setCondition("if-has-release", true); entry->bindWidget("release", LmsApplication::createReleaseAnchor(track->getRelease())); { - Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false)); - auto cover = std::make_unique(); - cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), ImageResource::Size::Large)); + Wt::WAnchor* anchor {entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false))}; + auto cover {std::make_unique()}; + cover->setImageLink(LmsApp->getCoverResource()->getReleaseUrl(release.id(), CoverResource::Size::Large)); cover->setStyleClass("Lms-cover"); cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); anchor->setImage(std::move(cover)); @@ -81,8 +81,8 @@ namespace UserInterface::TrackListHelpers } else { - auto cover = entry->bindNew("cover"); - cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(trackId, ImageResource::Size::Large)); + auto* cover {entry->bindNew("cover")}; + cover->setImageLink(LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Large)); cover->setStyleClass("Lms-cover"); cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); } diff --git a/src/lms/ui/explore/TrackPopup.cpp b/src/lms/ui/explore/TrackPopup.cpp index f1943355..ff1bbd5f 100644 --- a/src/lms/ui/explore/TrackPopup.cpp +++ b/src/lms/ui/explore/TrackPopup.cpp @@ -19,6 +19,8 @@ #include "TrackPopup.hpp" +#include + #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" diff --git a/src/lms/ui/explore/TracksView.cpp b/src/lms/ui/explore/TracksView.cpp index 909b0768..6093b45d 100644 --- a/src/lms/ui/explore/TracksView.cpp +++ b/src/lms/ui/explore/TracksView.cpp @@ -20,8 +20,9 @@ #include "TracksView.hpp" #include -#include #include +#include +#include #include #include "database/Artist.hpp" @@ -34,7 +35,6 @@ #include "utils/String.hpp" #include "common/LoadingIndicator.hpp" -#include "resource/ImageResource.hpp" #include "Filters.hpp" #include "LmsApplication.hpp" #include "MediaPlayer.hpp" diff --git a/src/lms/ui/resource/ImageResource.cpp b/src/lms/ui/resource/CoverResource.cpp similarity index 86% rename from src/lms/ui/resource/ImageResource.cpp rename to src/lms/ui/resource/CoverResource.cpp index 21edbb67..8adf85d8 100644 --- a/src/lms/ui/resource/ImageResource.cpp +++ b/src/lms/ui/resource/CoverResource.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "ImageResource.hpp" +#include "CoverResource.hpp" #include #include @@ -35,25 +35,34 @@ namespace UserInterface { -ImageResource::~ImageResource() +CoverResource::CoverResource() +{ + LmsApp->getScannerEvents().scanComplete.connect(this, [this](const Scanner::ScanStats& stats) + { + if (stats.nbChanges()) + setChanged(); + }); +} + +CoverResource::~CoverResource() { beingDeleted(); } std::string -ImageResource::getReleaseUrl(Database::IdType releaseId, Size size) const +CoverResource::getReleaseUrl(Database::IdType releaseId, Size size) const { return url() + "&releaseid=" + std::to_string(releaseId) + "&size=" + std::to_string(static_cast(size)); } std::string -ImageResource::getTrackUrl(Database::IdType trackId, Size size) const +CoverResource::getTrackUrl(Database::IdType trackId, Size size) const { return url() + "&trackid=" + std::to_string(trackId) + "&size=" + std::to_string(static_cast(size)); } void -ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) +CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) { // Retrieve parameters const std::string *trackIdStr = request.getParameter("trackid"); diff --git a/src/lms/ui/resource/ImageResource.hpp b/src/lms/ui/resource/CoverResource.hpp similarity index 93% rename from src/lms/ui/resource/ImageResource.hpp rename to src/lms/ui/resource/CoverResource.hpp index 76c574aa..f95a555d 100644 --- a/src/lms/ui/resource/ImageResource.hpp +++ b/src/lms/ui/resource/CoverResource.hpp @@ -25,12 +25,13 @@ namespace UserInterface { - class ImageResource : public Wt::WResource + class CoverResource : public Wt::WResource { public: static const std::size_t maxSize {512}; - ~ImageResource(); + CoverResource(); + ~CoverResource(); enum class Size : std::size_t {