diff --git a/src/libs/scanner/impl/MediaScanner.cpp b/src/libs/scanner/impl/MediaScanner.cpp index 36a3e659..f2b5a70a 100644 --- a/src/libs/scanner/impl/MediaScanner.cpp +++ b/src/libs/scanner/impl/MediaScanner.cpp @@ -310,7 +310,7 @@ MediaScanner::getStatus() { Status res; - std::unique_lock lock {_statusMutex}; + std::shared_lock lock {_statusMutex}; res.currentState = _curState; res.nextScheduledScan = _nextScheduledScan; @@ -367,7 +367,7 @@ MediaScanner::scheduleNextScan() } { - std::unique_lock lock {_statusMutex}; + std::unique_lock lock {_statusMutex}; _curState = nextScanDateTime.isValid() ? State::Scheduled : State::NotScheduled; _nextScheduledScan = nextScanDateTime; } @@ -421,8 +421,10 @@ MediaScanner::scan(boost::system::error_code err) if (err) return; + scanStarted().emit(); + { - std::unique_lock lock {_statusMutex}; + std::unique_lock lock {_statusMutex}; _curState = State::InProgress; _nextScheduledScan = {}; } @@ -464,7 +466,7 @@ MediaScanner::scan(boost::system::error_code err) { stats.stopTime = Wt::WLocalDateTime::currentDateTime().toUTC(); { - std::unique_lock lock {_statusMutex}; + std::unique_lock lock {_statusMutex}; _lastCompleteScanStats = std::move(stats); _inProgressScanStats.reset(); @@ -476,7 +478,7 @@ MediaScanner::scan(boost::system::error_code err) } else { - std::unique_lock lock {_statusMutex}; + std::unique_lock lock {_statusMutex}; _curState = State::NotScheduled; _inProgressScanStats.reset(); @@ -584,13 +586,15 @@ MediaScanner::refreshScanSettings() void MediaScanner::notifyInProgress(const ScanStats& stats) { + const ScanProgressStats progressStats {stats.toProgressStats()}; + { - std::unique_lock lock {_statusMutex}; - _inProgressScanStats = stats.toProgressStats(); + std::unique_lock lock {_statusMutex}; + _inProgressScanStats = progressStats; } - std::chrono::system_clock::time_point now {std::chrono::system_clock::now()}; - _sigScanInProgress(*_inProgressScanStats); + const std::chrono::system_clock::time_point now {std::chrono::system_clock::now()}; + _sigScanInProgress(progressStats); _lastScanInProgressEmit = now; } diff --git a/src/libs/scanner/impl/MediaScanner.hpp b/src/libs/scanner/impl/MediaScanner.hpp index a04ceb39..8c19defb 100644 --- a/src/libs/scanner/impl/MediaScanner.hpp +++ b/src/libs/scanner/impl/MediaScanner.hpp @@ -20,7 +20,7 @@ #pragma once #include -#include +#include #include #include @@ -59,6 +59,7 @@ class MediaScanner : public IMediaScanner Status getStatus() 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; } @@ -88,21 +89,22 @@ class MediaScanner : public IMediaScanner void notifyInProgressIfNeeded(const ScanStats& stats); void notifyInProgress(const ScanStats& stats); - bool _running {}; - Wt::WIOService _ioService; - boost::asio::system_timer _scheduleTimer {_ioService}; - Wt::Signal<> _sigScanComplete; - Wt::Signal _sigScanInProgress; + bool _running {}; + Wt::WIOService _ioService; + boost::asio::system_timer _scheduleTimer {_ioService}; + Wt::Signal<> _sigScanStarted; + Wt::Signal<> _sigScanComplete; + Wt::Signal _sigScanInProgress; std::chrono::system_clock::time_point _lastScanInProgressEmit {}; - Wt::Signal _sigScheduled; - Database::Session _dbSession; - std::unique_ptr _metadataParser; + Wt::Signal _sigScheduled; + Database::Session _dbSession; + std::unique_ptr _metadataParser; - std::mutex _statusMutex; - State _curState {State::NotScheduled}; - std::optional _lastCompleteScanStats; + std::shared_mutex _statusMutex; + State _curState {State::NotScheduled}; + std::optional _lastCompleteScanStats; std::optional _inProgressScanStats; - Wt::WDateTime _nextScheduledScan; + Wt::WDateTime _nextScheduledScan; // Current scan settings std::size_t _scanVersion {}; diff --git a/src/libs/scanner/include/scanner/IMediaScanner.hpp b/src/libs/scanner/include/scanner/IMediaScanner.hpp index e50be005..1c0d3fdc 100644 --- a/src/libs/scanner/include/scanner/IMediaScanner.hpp +++ b/src/libs/scanner/include/scanner/IMediaScanner.hpp @@ -64,6 +64,9 @@ class IMediaScanner virtual Status getStatus() = 0; + // Called just after scan start + virtual Wt::Signal<>& scanStarted() = 0; + // Called just after scan complete virtual Wt::Signal<>& scanComplete() = 0; diff --git a/src/libs/subsonic/CMakeLists.txt b/src/libs/subsonic/CMakeLists.txt index 34d3d68d..439e6289 100644 --- a/src/libs/subsonic/CMakeLists.txt +++ b/src/libs/subsonic/CMakeLists.txt @@ -1,6 +1,7 @@ add_library(lmssubsonic SHARED impl/ParameterParsing.cpp + impl/Scan.cpp impl/Stream.cpp impl/SubsonicId.cpp impl/SubsonicResource.cpp @@ -26,6 +27,7 @@ target_link_libraries(lmssubsonic PRIVATE target_link_libraries(lmssubsonic PUBLIC lmsdatabase + lmsscanner wt ) diff --git a/src/libs/subsonic/impl/Scan.cpp b/src/libs/subsonic/impl/Scan.cpp new file mode 100644 index 00000000..2e359c5a --- /dev/null +++ b/src/libs/subsonic/impl/Scan.cpp @@ -0,0 +1,65 @@ +/* + * 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 . + */ + +#include "Scan.hpp" + +#include "scanner/IMediaScanner.hpp" +#include "utils/Service.hpp" + +namespace API::Subsonic::Scan +{ + using namespace Scanner; + + static + Response::Node + createStatusResponseNode() + { + Response::Node statusResponse; + + const IMediaScanner::Status scanStatus {ServiceProvider::get()->getStatus()}; + + statusResponse.setAttribute("scanning", scanStatus.currentState == IMediaScanner::State::InProgress ? "true" : "false"); + if (scanStatus.currentState == IMediaScanner::State::InProgress && scanStatus.inProgressScanStats) + statusResponse.setAttribute("count", std::to_string(scanStatus.inProgressScanStats->processedFiles)); + + return statusResponse; + } + + + Response + handleGetScanStatus(RequestContext&) + { + Response response {Response::createOkResponse()}; + response.addNode("scanStatus", createStatusResponseNode()); + + return response; + } + + Response + handleStartScan(RequestContext&) + { + ServiceProvider::get()->requestImmediateScan(); + + Response response {Response::createOkResponse()}; + response.addNode("scanStatus", createStatusResponseNode()); + + return response; + } +} + diff --git a/src/libs/subsonic/impl/Scan.hpp b/src/libs/subsonic/impl/Scan.hpp new file mode 100644 index 00000000..3074a7b5 --- /dev/null +++ b/src/libs/subsonic/impl/Scan.hpp @@ -0,0 +1,30 @@ +/* + * 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 "RequestContext.hpp" +#include "SubsonicResponse.hpp" + +namespace API::Subsonic::Scan +{ + Response handleGetScanStatus(RequestContext& context); + Response handleStartScan(RequestContext& context); +} + diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index a78d028a..0adefb39 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -42,6 +42,7 @@ #include "utils/Utils.hpp" #include "ParameterParsing.hpp" #include "RequestContext.hpp" +#include "Scan.hpp" #include "Stream.hpp" #include "SubsonicResponse.hpp" @@ -1860,8 +1861,8 @@ static std::unordered_map requestEntryPoints {"savePlayQueue", {handleNotImplemented, false}}, // Media library scanning - {"getScanStatus", {handleNotImplemented, true}}, - {"startScan", {handleNotImplemented, true}}, + {"getScanStatus", {Scan::handleGetScanStatus, true}}, + {"startScan", {Scan::handleStartScan, true}}, }; using MediaRetrievalHandlerFunc = std::function; diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index 89cebb24..86fc7a15 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -523,6 +523,16 @@ LmsApplication::createHome() // Events from MediaScanner { const std::string sessionId {LmsApp->sessionId()}; + + ServiceProvider::get()->scanStarted().connect(this, [=] () + { + Wt::WServer::instance()->post(sessionId, [=] + { + _events.dbScanStarted.emit(); + triggerUpdate(); + }); + }); + ServiceProvider::get()->scanComplete().connect(this, [=] () { Wt::WServer::instance()->post(sessionId, [=] diff --git a/src/lms/ui/LmsApplication.hpp b/src/lms/ui/LmsApplication.hpp index 20cac9b7..2b3b4534 100644 --- a/src/lms/ui/LmsApplication.hpp +++ b/src/lms/ui/LmsApplication.hpp @@ -55,6 +55,7 @@ struct Events Wt::Signal appClosed; // Database events + Wt::Signal<> dbScanStarted; Wt::Signal<> dbScanned; Wt::Signal dbScanInProgress; Wt::Signal dbScanScheduled; diff --git a/src/lms/ui/admin/DatabaseSettingsView.cpp b/src/lms/ui/admin/DatabaseSettingsView.cpp index 323dfa7c..5b612ff6 100644 --- a/src/lms/ui/admin/DatabaseSettingsView.cpp +++ b/src/lms/ui/admin/DatabaseSettingsView.cpp @@ -248,7 +248,6 @@ DatabaseSettingsView::refreshView() immScanBtn->clicked().connect([=] () { ServiceProvider::get()->requestImmediateScan(); - LmsApp->notifyMsg(MsgType::Info, Wt::WString::tr("Lms.Admin.Database.scan-launched")); }); t->updateView(model.get()); diff --git a/src/lms/ui/admin/DatabaseStatus.cpp b/src/lms/ui/admin/DatabaseStatus.cpp index 34133e01..1030c452 100644 --- a/src/lms/ui/admin/DatabaseStatus.cpp +++ b/src/lms/ui/admin/DatabaseStatus.cpp @@ -121,6 +121,10 @@ DatabaseStatus::DatabaseStatus() auto onDbEvent = [&]() { refreshContents(); }; + LmsApp->getEvents().dbScanStarted.connect(this, []() + { + LmsApp->notifyMsg(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);