Reload cover resources when the database has been updated
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
|
|
||||||
add_library(lmsscanner SHARED
|
add_library(lmsscanner SHARED
|
||||||
impl/AcousticBrainzUtils.cpp
|
impl/AcousticBrainzUtils.cpp
|
||||||
impl/MediaScanner.cpp
|
impl/Scanner.cpp
|
||||||
impl/MediaScannerStats.cpp
|
impl/ScannerStats.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(lmsscanner INTERFACE
|
target_include_directories(lmsscanner INTERFACE
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "MediaScanner.hpp"
|
#include "Scanner.hpp"
|
||||||
|
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
#include <boost/asio/placeholders.hpp>
|
#include <boost/asio/placeholders.hpp>
|
||||||
@@ -242,13 +242,13 @@ getOrCreateClusters(Session& session, const MetaData::Clusters& clustersNames)
|
|||||||
|
|
||||||
namespace Scanner {
|
namespace Scanner {
|
||||||
|
|
||||||
std::unique_ptr<IMediaScanner>
|
std::unique_ptr<IScanner>
|
||||||
createMediaScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine)
|
createScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine)
|
||||||
{
|
{
|
||||||
return std::make_unique<MediaScanner>(db, recommendationEngine);
|
return std::make_unique<Scanner>(db, recommendationEngine);
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaScanner::MediaScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine)
|
Scanner::Scanner(Database::Db& db, Recommendation::IEngine& recommendationEngine)
|
||||||
: _recommendationEngine {recommendationEngine}
|
: _recommendationEngine {recommendationEngine}
|
||||||
, _dbSession {db}
|
, _dbSession {db}
|
||||||
{
|
{
|
||||||
@@ -262,14 +262,14 @@ MediaScanner::MediaScanner(Database::Db& db, Recommendation::IEngine& recommenda
|
|||||||
start();
|
start();
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaScanner::~MediaScanner()
|
Scanner::~Scanner()
|
||||||
{
|
{
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Shutting down MediaScanner...";
|
LMS_LOG(DBUPDATER, INFO) << "Shutting down Scanner...";
|
||||||
stop();
|
stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::start()
|
Scanner::start()
|
||||||
{
|
{
|
||||||
std::scoped_lock lock {_controlMutex};
|
std::scoped_lock lock {_controlMutex};
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ MediaScanner::start()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::stop()
|
Scanner::stop()
|
||||||
{
|
{
|
||||||
std::scoped_lock lock {_controlMutex};
|
std::scoped_lock lock {_controlMutex};
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@ MediaScanner::stop()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::abortScan()
|
Scanner::abortScan()
|
||||||
{
|
{
|
||||||
LMS_LOG(DBUPDATER, DEBUG) << "Aborting scan...";
|
LMS_LOG(DBUPDATER, DEBUG) << "Aborting scan...";
|
||||||
std::scoped_lock lock {_controlMutex};
|
std::scoped_lock lock {_controlMutex};
|
||||||
@@ -319,7 +319,7 @@ MediaScanner::abortScan()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::requestImmediateScan(bool force)
|
Scanner::requestImmediateScan(bool force)
|
||||||
{
|
{
|
||||||
abortScan();
|
abortScan();
|
||||||
_ioService.post([=]()
|
_ioService.post([=]()
|
||||||
@@ -332,7 +332,7 @@ MediaScanner::requestImmediateScan(bool force)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::requestReload()
|
Scanner::requestReload()
|
||||||
{
|
{
|
||||||
abortScan();
|
abortScan();
|
||||||
_ioService.post([=]()
|
_ioService.post([=]()
|
||||||
@@ -344,8 +344,8 @@ MediaScanner::requestReload()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaScanner::Status
|
Scanner::Status
|
||||||
MediaScanner::getStatus() const
|
Scanner::getStatus() const
|
||||||
{
|
{
|
||||||
Status res;
|
Status res;
|
||||||
|
|
||||||
@@ -360,7 +360,7 @@ MediaScanner::getStatus() const
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::scheduleNextScan()
|
Scanner::scheduleNextScan()
|
||||||
{
|
{
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan";
|
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan";
|
||||||
|
|
||||||
@@ -411,11 +411,11 @@ MediaScanner::scheduleNextScan()
|
|||||||
_nextScheduledScan = nextScanDateTime;
|
_nextScheduledScan = nextScanDateTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
_sigScheduled.emit(_nextScheduledScan);
|
_events.scanScheduled.emit(_nextScheduledScan);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::countAllFiles(ScanStats& stats)
|
Scanner::countAllFiles(ScanStats& stats)
|
||||||
{
|
{
|
||||||
ScanStepStats stepStats{stats.startTime, ScanProgressStep::DiscoveringFiles};
|
ScanStepStats stepStats{stats.startTime, ScanProgressStep::DiscoveringFiles};
|
||||||
|
|
||||||
@@ -440,7 +440,7 @@ MediaScanner::countAllFiles(ScanStats& stats)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::scheduleScan(bool force, const Wt::WDateTime& dateTime)
|
Scanner::scheduleScan(bool force, const Wt::WDateTime& dateTime)
|
||||||
{
|
{
|
||||||
auto cb {[=](boost::system::error_code ec)
|
auto cb {[=](boost::system::error_code ec)
|
||||||
{
|
{
|
||||||
@@ -469,9 +469,9 @@ MediaScanner::scheduleScan(bool force, const Wt::WDateTime& dateTime)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::scan(bool forceScan)
|
Scanner::scan(bool forceScan)
|
||||||
{
|
{
|
||||||
scanStarted().emit();
|
_events.scanStarted.emit();
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unique_lock lock {_statusMutex};
|
std::unique_lock lock {_statusMutex};
|
||||||
@@ -517,14 +517,14 @@ MediaScanner::scan(bool forceScan)
|
|||||||
{
|
{
|
||||||
std::unique_lock lock {_statusMutex};
|
std::unique_lock lock {_statusMutex};
|
||||||
|
|
||||||
_lastCompleteScanStats = std::move(stats);
|
_lastCompleteScanStats = stats;
|
||||||
_currentScanStepStats.reset();
|
_currentScanStepStats.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
LMS_LOG(DBUPDATER, DEBUG) << "Scan not aborted, scheduling next scan!";
|
LMS_LOG(DBUPDATER, DEBUG) << "Scan not aborted, scheduling next scan!";
|
||||||
scheduleNextScan();
|
scheduleNextScan();
|
||||||
|
|
||||||
scanComplete().emit();
|
_events.scanComplete.emit(stats);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -538,7 +538,7 @@ MediaScanner::scan(bool forceScan)
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MediaScanner::fetchTrackFeatures(Database::IdType trackId, const UUID& MBID)
|
Scanner::fetchTrackFeatures(Database::IdType trackId, const UUID& MBID)
|
||||||
{
|
{
|
||||||
std::map<std::string, double> features;
|
std::map<std::string, double> features;
|
||||||
|
|
||||||
@@ -564,7 +564,7 @@ MediaScanner::fetchTrackFeatures(Database::IdType trackId, const UUID& MBID)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::fetchTrackFeatures(ScanStats& stats)
|
Scanner::fetchTrackFeatures(ScanStats& stats)
|
||||||
{
|
{
|
||||||
if (_recommendationEngineType != ScanSettings::RecommendationEngineType::Features)
|
if (_recommendationEngineType != ScanSettings::RecommendationEngineType::Features)
|
||||||
return;
|
return;
|
||||||
@@ -614,7 +614,7 @@ MediaScanner::fetchTrackFeatures(ScanStats& stats)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::refreshScanSettings()
|
Scanner::refreshScanSettings()
|
||||||
{
|
{
|
||||||
auto transaction {_dbSession.createSharedTransaction()};
|
auto transaction {_dbSession.createSharedTransaction()};
|
||||||
|
|
||||||
@@ -646,7 +646,7 @@ MediaScanner::refreshScanSettings()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::notifyInProgress(const ScanStepStats& stepStats)
|
Scanner::notifyInProgress(const ScanStepStats& stepStats)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
std::unique_lock lock {_statusMutex};
|
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()};
|
const std::chrono::system_clock::time_point now {std::chrono::system_clock::now()};
|
||||||
_sigScanInProgress(stepStats);
|
_events.scanInProgress(stepStats);
|
||||||
_lastScanInProgressEmit = now;
|
_lastScanInProgressEmit = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::notifyInProgressIfNeeded(const ScanStepStats& stepStats)
|
Scanner::notifyInProgressIfNeeded(const ScanStepStats& stepStats)
|
||||||
{
|
{
|
||||||
std::chrono::system_clock::time_point now {std::chrono::system_clock::now()};
|
std::chrono::system_clock::time_point now {std::chrono::system_clock::now()};
|
||||||
|
|
||||||
@@ -668,7 +668,7 @@ MediaScanner::notifyInProgressIfNeeded(const ScanStepStats& stepStats)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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;
|
Wt::WDateTime lastWriteTime;
|
||||||
try
|
try
|
||||||
@@ -831,7 +831,7 @@ MediaScanner::scanAudioFile(const std::filesystem::path& file, bool forceScan, S
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
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};
|
ScanStepStats stepStats{stats.startTime, ScanProgressStep::ScanningFiles};
|
||||||
stepStats.totalElems = stats.filesScanned;
|
stepStats.totalElems = stats.filesScanned;
|
||||||
@@ -899,7 +899,7 @@ checkFile(const std::filesystem::path& p, const std::filesystem::path& mediaDire
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::removeMissingTracks(ScanStats& stats)
|
Scanner::removeMissingTracks(ScanStats& stats)
|
||||||
{
|
{
|
||||||
static constexpr std::size_t batchSize {50};
|
static constexpr std::size_t batchSize {50};
|
||||||
|
|
||||||
@@ -966,7 +966,7 @@ MediaScanner::removeMissingTracks(ScanStats& stats)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::removeOrphanEntries()
|
Scanner::removeOrphanEntries()
|
||||||
{
|
{
|
||||||
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters...";
|
LMS_LOG(DBUPDATER, DEBUG) << "Checking orphan clusters...";
|
||||||
{
|
{
|
||||||
@@ -1009,7 +1009,7 @@ MediaScanner::removeOrphanEntries()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::checkDuplicatedAudioFiles(ScanStats& stats)
|
Scanner::checkDuplicatedAudioFiles(ScanStats& stats)
|
||||||
{
|
{
|
||||||
LMS_LOG(DBUPDATER, INFO) << "Checking duplicated audio files";
|
LMS_LOG(DBUPDATER, INFO) << "Checking duplicated audio files";
|
||||||
|
|
||||||
@@ -1029,7 +1029,7 @@ MediaScanner::checkDuplicatedAudioFiles(ScanStats& stats)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
MediaScanner::reloadSimilarityEngine(ScanStats& stats)
|
Scanner::reloadSimilarityEngine(ScanStats& stats)
|
||||||
{
|
{
|
||||||
ScanStepStats stepStats {stats.startTime, ScanProgressStep::ReloadingSimilarityEngine};
|
ScanStepStats stepStats {stats.startTime, ScanProgressStep::ReloadingSimilarityEngine};
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
#include "database/ScanSettings.hpp"
|
#include "database/ScanSettings.hpp"
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "metadata/IParser.hpp"
|
#include "metadata/IParser.hpp"
|
||||||
#include "scanner/IMediaScanner.hpp"
|
#include "scanner/IScanner.hpp"
|
||||||
|
|
||||||
class UUID;
|
class UUID;
|
||||||
|
|
||||||
@@ -44,29 +44,24 @@ namespace Recommendation
|
|||||||
|
|
||||||
namespace Scanner {
|
namespace Scanner {
|
||||||
|
|
||||||
class MediaScanner : public IMediaScanner
|
class Scanner : public IScanner
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
MediaScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine);
|
Scanner(Database::Db& db, Recommendation::IEngine& recommendationEngine);
|
||||||
~MediaScanner();
|
~Scanner();
|
||||||
|
|
||||||
MediaScanner(const MediaScanner&) = delete;
|
Scanner(const Scanner&) = delete;
|
||||||
MediaScanner(MediaScanner&&) = delete;
|
Scanner(Scanner&&) = delete;
|
||||||
MediaScanner& operator=(const MediaScanner&) = delete;
|
Scanner& operator=(const Scanner&) = delete;
|
||||||
MediaScanner& operator=(MediaScanner&&) = delete;
|
Scanner& operator=(Scanner&&) = delete;
|
||||||
|
|
||||||
void requestReload() override;
|
void requestReload() override;
|
||||||
void requestImmediateScan(bool force) override;
|
void requestImmediateScan(bool force) override;
|
||||||
|
|
||||||
Status getStatus() const override;
|
Status getStatus() const override;
|
||||||
|
Events& getEvents() override { return _events; }
|
||||||
Wt::Signal<>& scanStarted() override { return _sigScanStarted; }
|
|
||||||
Wt::Signal<>& scanComplete() override { return _sigScanComplete; }
|
|
||||||
Wt::Signal<ScanStepStats>& scanInProgress() override { return _sigScanInProgress; }
|
|
||||||
Wt::Signal<Wt::WDateTime>& scheduled() override { return _sigScheduled; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
void start();
|
void start();
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
@@ -102,11 +97,8 @@ class MediaScanner : public IMediaScanner
|
|||||||
std::atomic<bool> _abortScan {};
|
std::atomic<bool> _abortScan {};
|
||||||
Wt::WIOService _ioService;
|
Wt::WIOService _ioService;
|
||||||
boost::asio::system_timer _scheduleTimer {_ioService};
|
boost::asio::system_timer _scheduleTimer {_ioService};
|
||||||
Wt::Signal<> _sigScanStarted;
|
Events _events;
|
||||||
Wt::Signal<> _sigScanComplete;
|
|
||||||
Wt::Signal<ScanStepStats> _sigScanInProgress;
|
|
||||||
std::chrono::system_clock::time_point _lastScanInProgressEmit {};
|
std::chrono::system_clock::time_point _lastScanInProgressEmit {};
|
||||||
Wt::Signal<Wt::WDateTime> _sigScheduled;
|
|
||||||
Database::Session _dbSession;
|
Database::Session _dbSession;
|
||||||
std::unique_ptr<MetaData::IParser> _metadataParser;
|
std::unique_ptr<MetaData::IParser> _metadataParser;
|
||||||
|
|
||||||
@@ -124,7 +116,7 @@ class MediaScanner : public IMediaScanner
|
|||||||
std::filesystem::path _mediaDirectory;
|
std::filesystem::path _mediaDirectory;
|
||||||
Database::ScanSettings::RecommendationEngineType _recommendationEngineType;
|
Database::ScanSettings::RecommendationEngineType _recommendationEngineType;
|
||||||
|
|
||||||
}; // class MediaScanner
|
}; // class Scanner
|
||||||
|
|
||||||
} // Scanner
|
} // Scanner
|
||||||
|
|
||||||
+1
-1
@@ -17,7 +17,7 @@
|
|||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "scanner/MediaScannerStats.hpp"
|
#include "scanner/ScannerStats.hpp"
|
||||||
|
|
||||||
namespace Scanner {
|
namespace Scanner {
|
||||||
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include <Wt/WDateTime.h>
|
|
||||||
#include <Wt/WSignal.h>
|
|
||||||
|
|
||||||
#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<ScanStats> lastCompleteScanStats;
|
|
||||||
std::optional<ScanStepStats> 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<ScanStepStats>& scanInProgress() = 0;
|
|
||||||
|
|
||||||
// Called after a schedule
|
|
||||||
virtual Wt::Signal<Wt::WDateTime>& scheduled() = 0;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<IMediaScanner> createMediaScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine);
|
|
||||||
|
|
||||||
} // Scanner
|
|
||||||
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#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<ScanStats> lastCompleteScanStats;
|
||||||
|
std::optional<ScanStepStats> currentScanStepStats;
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual Status getStatus() const = 0;
|
||||||
|
|
||||||
|
virtual Events& getEvents() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<IScanner> createScanner(Database::Db& db, Recommendation::IEngine& recommendationEngine);
|
||||||
|
|
||||||
|
} // Scanner
|
||||||
|
|
||||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Wt/WDateTime.h>
|
||||||
|
#include <Wt/WSignal.h>
|
||||||
|
|
||||||
|
#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<ScanStats> scanComplete;
|
||||||
|
|
||||||
|
// Called during scan in progress
|
||||||
|
Wt::Signal<ScanStepStats> scanInProgress;
|
||||||
|
|
||||||
|
// Called after a schedule
|
||||||
|
Wt::Signal<Wt::WDateTime> scanScheduled;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // ns Scanner
|
||||||
|
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
#include "Scan.hpp"
|
#include "Scan.hpp"
|
||||||
|
|
||||||
#include "scanner/IMediaScanner.hpp"
|
#include "scanner/IScanner.hpp"
|
||||||
#include "utils/Service.hpp"
|
#include "utils/Service.hpp"
|
||||||
|
|
||||||
namespace API::Subsonic::Scan
|
namespace API::Subsonic::Scan
|
||||||
@@ -32,10 +32,10 @@ namespace API::Subsonic::Scan
|
|||||||
{
|
{
|
||||||
Response::Node statusResponse;
|
Response::Node statusResponse;
|
||||||
|
|
||||||
const IMediaScanner::Status scanStatus {Service<IMediaScanner>::get()->getStatus()};
|
const IScanner::Status scanStatus {Service<IScanner>::get()->getStatus()};
|
||||||
|
|
||||||
statusResponse.setAttribute("scanning", scanStatus.currentState == IMediaScanner::State::InProgress);
|
statusResponse.setAttribute("scanning", scanStatus.currentState == IScanner::State::InProgress);
|
||||||
if (scanStatus.currentState == IMediaScanner::State::InProgress)
|
if (scanStatus.currentState == IScanner::State::InProgress)
|
||||||
{
|
{
|
||||||
std::size_t count{};
|
std::size_t count{};
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ namespace API::Subsonic::Scan
|
|||||||
Response
|
Response
|
||||||
handleStartScan(RequestContext& context)
|
handleStartScan(RequestContext& context)
|
||||||
{
|
{
|
||||||
Service<IMediaScanner>::get()->requestImmediateScan(false);
|
Service<IScanner>::get()->requestImmediateScan(false);
|
||||||
|
|
||||||
Response response {Response::createOkResponse(context)};
|
Response response {Response::createOkResponse(context)};
|
||||||
response.addNode("scanStatus", createStatusResponseNode());
|
response.addNode("scanStatus", createStatusResponseNode());
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ add_executable(lms
|
|||||||
ui/explore/TracksView.cpp
|
ui/explore/TracksView.cpp
|
||||||
ui/resource/AudioFileResource.cpp
|
ui/resource/AudioFileResource.cpp
|
||||||
ui/resource/AudioTranscodeResource.cpp
|
ui/resource/AudioTranscodeResource.cpp
|
||||||
|
ui/resource/CoverResource.cpp
|
||||||
ui/resource/DownloadResource.cpp
|
ui/resource/DownloadResource.cpp
|
||||||
ui/resource/ImageResource.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(lms PRIVATE
|
target_include_directories(lms PRIVATE
|
||||||
|
|||||||
+50
-5
@@ -31,7 +31,7 @@
|
|||||||
#include "cover/ICoverArtGrabber.hpp"
|
#include "cover/ICoverArtGrabber.hpp"
|
||||||
#include "database/Db.hpp"
|
#include "database/Db.hpp"
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "scanner/IMediaScanner.hpp"
|
#include "scanner/IScanner.hpp"
|
||||||
#include "recommendation/IEngine.hpp"
|
#include "recommendation/IEngine.hpp"
|
||||||
#include "subsonic/SubsonicResource.hpp"
|
#include "subsonic/SubsonicResource.hpp"
|
||||||
#include "ui/LmsApplication.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[])
|
int main(int argc, char* argv[])
|
||||||
{
|
{
|
||||||
std::filesystem::path configFilePath {"/etc/lms.conf"};
|
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-max-file-size", 10) * 1000 * 1000,
|
||||||
config->getULong("cover-jpeg-quality", 75))};
|
config->getULong("cover-jpeg-quality", 75))};
|
||||||
Service<Recommendation::IEngine> recommendationEngineService {Recommendation::createEngine(database)};
|
Service<Recommendation::IEngine> recommendationEngineService {Recommendation::createEngine(database)};
|
||||||
Service<Scanner::IMediaScanner> mediaScannerService {Scanner::createMediaScanner(database, *recommendationEngineService)};
|
Service<Scanner::IScanner> scannerService {Scanner::createScanner(database, *recommendationEngineService)};
|
||||||
|
|
||||||
mediaScannerService->scanComplete().connect([&]()
|
scannerService->getEvents().scanComplete.connect([&]
|
||||||
{
|
{
|
||||||
// Flush cover cache even if no changes:
|
// Flush cover cache even if no changes:
|
||||||
// covers may be external files that changed and we don't keep track of them
|
// 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
|
// bind UI entry point
|
||||||
server.addEntryPoint(Wt::EntryPointType::Application,
|
server.addEntryPoint(Wt::EntryPointType::Application,
|
||||||
std::bind(UserInterface::LmsApplication::create,
|
[&](const Wt::WEnvironment &env)
|
||||||
std::placeholders::_1, std::ref(database), std::ref(appGroups)));
|
{
|
||||||
|
return UserInterface::LmsApplication::create(env, database, appGroups);
|
||||||
|
});
|
||||||
|
|
||||||
|
proxyScannerEventsToApplication(*scannerService, server);
|
||||||
|
|
||||||
LMS_LOG(MAIN, INFO) << "Starting server...";
|
LMS_LOG(MAIN, INFO) << "Starting server...";
|
||||||
server.start();
|
server.start();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <Wt/WAnchor.h>
|
#include <Wt/WAnchor.h>
|
||||||
#include <Wt/WEnvironment.h>
|
#include <Wt/WEnvironment.h>
|
||||||
#include <Wt/WLineEdit.h>
|
#include <Wt/WLineEdit.h>
|
||||||
|
#include <Wt/WPopupMenu.h>
|
||||||
#include <Wt/WPushButton.h>
|
#include <Wt/WPushButton.h>
|
||||||
#include <Wt/WServer.h>
|
#include <Wt/WServer.h>
|
||||||
#include <Wt/WStackedWidget.h>
|
#include <Wt/WStackedWidget.h>
|
||||||
@@ -34,8 +35,6 @@
|
|||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
#include "explore/Explore.hpp"
|
|
||||||
#include "explore/Filters.hpp"
|
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "utils/Service.hpp"
|
#include "utils/Service.hpp"
|
||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
@@ -44,10 +43,12 @@
|
|||||||
#include "admin/DatabaseSettingsView.hpp"
|
#include "admin/DatabaseSettingsView.hpp"
|
||||||
#include "admin/UserView.hpp"
|
#include "admin/UserView.hpp"
|
||||||
#include "admin/UsersView.hpp"
|
#include "admin/UsersView.hpp"
|
||||||
|
#include "explore/Explore.hpp"
|
||||||
|
#include "explore/Filters.hpp"
|
||||||
#include "resource/AudioFileResource.hpp"
|
#include "resource/AudioFileResource.hpp"
|
||||||
#include "resource/AudioTranscodeResource.hpp"
|
#include "resource/AudioTranscodeResource.hpp"
|
||||||
#include "resource/DownloadResource.hpp"
|
#include "resource/DownloadResource.hpp"
|
||||||
#include "resource/ImageResource.hpp"
|
#include "resource/CoverResource.hpp"
|
||||||
#include "Auth.hpp"
|
#include "Auth.hpp"
|
||||||
#include "LmsApplicationException.hpp"
|
#include "LmsApplicationException.hpp"
|
||||||
#include "LmsTheme.hpp"
|
#include "LmsTheme.hpp"
|
||||||
@@ -227,6 +228,8 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LmsApplication::~LmsApplication() = default;
|
||||||
|
|
||||||
void
|
void
|
||||||
LmsApplication::finalize()
|
LmsApplication::finalize()
|
||||||
{
|
{
|
||||||
@@ -440,9 +443,7 @@ LmsApplication::handleUserLoggedIn(Database::IdType userId, bool strongAuth)
|
|||||||
void
|
void
|
||||||
LmsApplication::createHome()
|
LmsApplication::createHome()
|
||||||
{
|
{
|
||||||
_audioFileResource = std::make_shared<AudioFileResource>();
|
_coverResource = std::make_shared<CoverResource>();
|
||||||
_audioTranscodeResource = std::make_shared<AudioTranscodeResource>();
|
|
||||||
_imageResource = std::make_shared<ImageResource>();
|
|
||||||
|
|
||||||
declareJavaScriptFunction("onLoadCover", "function(id) { id.className += \" Lms-cover-loaded\"}");
|
declareJavaScriptFunction("onLoadCover", "function(id) { id.className += \" Lms-cover-loaded\"}");
|
||||||
doJavaScript("$('body').tooltip({ selector: '[data-toggle=\"tooltip\"]'})");
|
doJavaScript("$('body').tooltip({ selector: '[data-toggle=\"tooltip\"]'})");
|
||||||
@@ -532,59 +533,15 @@ LmsApplication::createHome()
|
|||||||
_mediaPlayer->loadTrack(trackId, play, replayGain);
|
_mediaPlayer->loadTrack(trackId, play, replayGain);
|
||||||
});
|
});
|
||||||
|
|
||||||
_playQueue->trackUnselected.connect([this] ()
|
_playQueue->trackUnselected.connect([this]
|
||||||
{
|
{
|
||||||
_mediaPlayer->stop();
|
_mediaPlayer->stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Events from MediaScanner
|
if (isUserAdmin())
|
||||||
{
|
{
|
||||||
const std::string sessionId {LmsApp->sessionId()};
|
_scannerEvents.scanComplete.connect([=] (const Scanner::ScanStats& stats)
|
||||||
|
|
||||||
Service<Scanner::IMediaScanner>::get()->scanStarted().connect(this, [=]
|
|
||||||
{
|
{
|
||||||
Wt::WServer::instance()->post(sessionId, [=]
|
|
||||||
{
|
|
||||||
_events.dbScanStarted.emit();
|
|
||||||
triggerUpdate();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Service<Scanner::IMediaScanner>::get()->scanComplete().connect(this, [=]
|
|
||||||
{
|
|
||||||
Wt::WServer::instance()->post(sessionId, [this]
|
|
||||||
{
|
|
||||||
_events.dbScanned.emit();
|
|
||||||
triggerUpdate();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Service<Scanner::IMediaScanner>::get()->scanInProgress().connect(this, [=] (Scanner::ScanStepStats stepStats)
|
|
||||||
{
|
|
||||||
Wt::WServer::instance()->post(sessionId, [=]
|
|
||||||
{
|
|
||||||
_events.dbScanInProgress.emit(stepStats);
|
|
||||||
triggerUpdate();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Service<Scanner::IMediaScanner>::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<Scanner::IMediaScanner>::get()->getStatus().lastCompleteScanStats};
|
|
||||||
|
|
||||||
notifyMsg(MsgType::Info, Wt::WString::tr("Lms.Admin.Database.scan-complete")
|
notifyMsg(MsgType::Info, Wt::WString::tr("Lms.Admin.Database.scan-complete")
|
||||||
.arg(static_cast<unsigned>(stats.nbFiles()))
|
.arg(static_cast<unsigned>(stats.nbFiles()))
|
||||||
.arg(static_cast<unsigned>(stats.additions))
|
.arg(static_cast<unsigned>(stats.additions))
|
||||||
@@ -592,8 +549,8 @@ LmsApplication::createHome()
|
|||||||
.arg(static_cast<unsigned>(stats.deletions))
|
.arg(static_cast<unsigned>(stats.deletions))
|
||||||
.arg(static_cast<unsigned>(stats.duplicates.size()))
|
.arg(static_cast<unsigned>(stats.duplicates.size()))
|
||||||
.arg(static_cast<unsigned>(stats.errors.size())));
|
.arg(static_cast<unsigned>(stats.errors.size())));
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
// Events from Application group
|
// Events from Application group
|
||||||
_events.appOpen.connect([=]
|
_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)
|
switch(type)
|
||||||
{
|
{
|
||||||
case MsgType::Success: return "success";
|
case LmsApplication::MsgType::Success: return "success";
|
||||||
case MsgType::Info: return "info";
|
case LmsApplication::MsgType::Info: return "info";
|
||||||
case MsgType::Warning: return "warning";
|
case LmsApplication::MsgType::Warning: return "warning";
|
||||||
case MsgType::Danger: return "danger";
|
case LmsApplication::MsgType::Danger: return "danger";
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,13 +22,18 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include <Wt/WApplication.h>
|
#include <Wt/WApplication.h>
|
||||||
#include <Wt/WPopupMenu.h>
|
|
||||||
|
|
||||||
#include "scanner/IMediaScanner.hpp"
|
#include "scanner/ScannerEvents.hpp"
|
||||||
|
|
||||||
#include "LmsApplicationGroup.hpp"
|
#include "LmsApplicationGroup.hpp"
|
||||||
|
|
||||||
namespace Database {
|
namespace Wt
|
||||||
|
{
|
||||||
|
class WPopupMenu;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Database
|
||||||
|
{
|
||||||
class Artist;
|
class Artist;
|
||||||
class Cluster;
|
class Cluster;
|
||||||
class Db;
|
class Db;
|
||||||
@@ -39,10 +44,8 @@ namespace Database {
|
|||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
class AudioTranscodeResource;
|
|
||||||
class AudioFileResource;
|
|
||||||
class Auth;
|
class Auth;
|
||||||
class ImageResource;
|
class CoverResource;
|
||||||
class LmsApplicationException;
|
class LmsApplicationException;
|
||||||
class MediaPlayer;
|
class MediaPlayer;
|
||||||
class PlayQueue;
|
class PlayQueue;
|
||||||
@@ -53,35 +56,19 @@ struct Events
|
|||||||
// Events relative to group
|
// Events relative to group
|
||||||
Wt::Signal<LmsApplicationInfo> appOpen;
|
Wt::Signal<LmsApplicationInfo> appOpen;
|
||||||
Wt::Signal<LmsApplicationInfo> appClosed;
|
Wt::Signal<LmsApplicationInfo> appClosed;
|
||||||
|
|
||||||
// Database events
|
|
||||||
Wt::Signal<> dbScanStarted;
|
|
||||||
Wt::Signal<> dbScanned;
|
|
||||||
Wt::Signal<Scanner::ScanStepStats> dbScanInProgress;
|
|
||||||
Wt::Signal<Wt::WDateTime> dbScanScheduled;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Used to classify the message sent to the user
|
|
||||||
enum class MsgType
|
|
||||||
{
|
|
||||||
Success,
|
|
||||||
Info,
|
|
||||||
Warning,
|
|
||||||
Danger,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class LmsApplication : public Wt::WApplication
|
class LmsApplication : public Wt::WApplication
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups);
|
LmsApplication(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups);
|
||||||
|
~LmsApplication();
|
||||||
|
|
||||||
static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups);
|
static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env, Database::Db& db, LmsApplicationGroupContainer& appGroups);
|
||||||
static LmsApplication* instance();
|
static LmsApplication* instance();
|
||||||
|
|
||||||
// Session application data
|
// Session application data
|
||||||
std::shared_ptr<ImageResource> getImageResource() { return _imageResource; }
|
std::shared_ptr<CoverResource> getCoverResource() { return _coverResource; }
|
||||||
std::shared_ptr<AudioTranscodeResource> getAudioTranscodeResource() { return _audioTranscodeResource; }
|
|
||||||
std::shared_ptr<AudioFileResource> getAudioFileResource() { return _audioFileResource; }
|
|
||||||
Database::Session& getDbSession(); // always thread safe
|
Database::Session& getDbSession(); // always thread safe
|
||||||
|
|
||||||
Wt::Dbo::ptr<Database::User> getUser();
|
Wt::Dbo::ptr<Database::User> getUser();
|
||||||
@@ -91,10 +78,20 @@ class LmsApplication : public Wt::WApplication
|
|||||||
std::string getUserLoginName(); // user must be logged in prior this call
|
std::string getUserLoginName(); // user must be logged in prior this call
|
||||||
|
|
||||||
Events& getEvents() { return _events; }
|
Events& getEvents() { return _events; }
|
||||||
|
Scanner::Events& getScannerEvents() { return _scannerEvents; }
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
void post(std::function<void()> func);
|
void post(std::function<void()> 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<Database::Artist> artist);
|
static Wt::WLink createArtistLink(Wt::Dbo::ptr<Database::Artist> artist);
|
||||||
static std::unique_ptr<Wt::WAnchor> createArtistAnchor(Wt::Dbo::ptr<Database::Artist> artist, bool addText = true);
|
static std::unique_ptr<Wt::WAnchor> createArtistAnchor(Wt::Dbo::ptr<Database::Artist> artist, bool addText = true);
|
||||||
@@ -129,19 +126,18 @@ class LmsApplication : public Wt::WApplication
|
|||||||
Wt::Signal<> _preQuit;
|
Wt::Signal<> _preQuit;
|
||||||
LmsApplicationGroupContainer& _appGroups;
|
LmsApplicationGroupContainer& _appGroups;
|
||||||
Events _events;
|
Events _events;
|
||||||
|
Scanner::Events _scannerEvents;
|
||||||
std::optional<Database::IdType> _userId;
|
std::optional<Database::IdType> _userId;
|
||||||
std::optional<bool> _userAuthStrong;
|
std::optional<bool> _userAuthStrong;
|
||||||
std::shared_ptr<AudioTranscodeResource> _audioTranscodeResource;
|
std::shared_ptr<CoverResource> _coverResource;
|
||||||
std::shared_ptr<AudioFileResource> _audioFileResource;
|
|
||||||
std::shared_ptr<ImageResource> _imageResource;
|
|
||||||
MediaPlayer* _mediaPlayer {};
|
MediaPlayer* _mediaPlayer {};
|
||||||
PlayQueue* _playQueue {};
|
PlayQueue* _playQueue {};
|
||||||
std::unique_ptr<Wt::WPopupMenu> _popupMenu {};
|
std::unique_ptr<Wt::WPopupMenu> _popupMenu;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Helper to get session instance
|
// Helper to get session instance
|
||||||
#define LmsApp LmsApplication::instance()
|
#define LmsApp ::UserInterface::LmsApplication::instance()
|
||||||
|
|
||||||
} // namespace UserInterface
|
} // namespace UserInterface
|
||||||
|
|
||||||
|
|||||||
+13
-10
@@ -33,7 +33,7 @@
|
|||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
|
|
||||||
#include "resource/ImageResource.hpp"
|
#include "resource/CoverResource.hpp"
|
||||||
#include "resource/AudioTranscodeResource.hpp"
|
#include "resource/AudioTranscodeResource.hpp"
|
||||||
#include "resource/AudioFileResource.hpp"
|
#include "resource/AudioFileResource.hpp"
|
||||||
|
|
||||||
@@ -192,14 +192,17 @@ static MediaPlayer::Settings settingsfromJSString(const std::string& strSettings
|
|||||||
}
|
}
|
||||||
|
|
||||||
MediaPlayer::MediaPlayer()
|
MediaPlayer::MediaPlayer()
|
||||||
: Wt::WTemplate {Wt::WString::tr("Lms.MediaPlayer.template")},
|
: Wt::WTemplate {Wt::WString::tr("Lms.MediaPlayer.template")}
|
||||||
playbackEnded {this, "playbackEnded"},
|
, playbackEnded {this, "playbackEnded"}
|
||||||
playPrevious {this, "playPrevious"},
|
, playPrevious {this, "playPrevious"}
|
||||||
playNext {this, "playNext"},
|
, playNext {this, "playNext"}
|
||||||
_settingsLoaded {this, "settingsLoaded"}
|
, _settingsLoaded {this, "settingsLoaded"}
|
||||||
{
|
{
|
||||||
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
|
||||||
|
_audioTranscodeResource = std::make_unique<AudioTranscodeResource>();
|
||||||
|
_audioFileResource = std::make_unique<AudioFileResource>();
|
||||||
|
|
||||||
_title = bindNew<Wt::WText>("title");
|
_title = bindNew<Wt::WText>("title");
|
||||||
_artist = bindNew<Wt::WAnchor>("artist");
|
_artist = bindNew<Wt::WAnchor>("artist");
|
||||||
_release = bindNew<Wt::WAnchor>("release");
|
_release = bindNew<Wt::WAnchor>("release");
|
||||||
@@ -240,8 +243,8 @@ MediaPlayer::loadTrack(Database::IdType trackId, bool play, float replayGain)
|
|||||||
if (!track)
|
if (!track)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const std::string transcodeResource {LmsApp->getAudioTranscodeResource()->getUrl(trackId)};
|
const std::string transcodeResource {_audioTranscodeResource->getUrl(trackId)};
|
||||||
const std::string nativeResource {LmsApp->getAudioFileResource()->getUrl(trackId)};
|
const std::string nativeResource {_audioFileResource->getUrl(trackId)};
|
||||||
|
|
||||||
const auto artists {track->getArtists({Database::TrackArtistLinkType::Artist})};
|
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()) : "") << "\","
|
<< " artist: \"" << (!artists.empty() ? StringUtils::jsEscape(artists.front()->getName()) : "") << "\","
|
||||||
<< " release: \"" << (track->getRelease() ? StringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
|
<< " release: \"" << (track->getRelease() ? StringUtils::jsEscape(track->getRelease()->getName()) : "") << "\","
|
||||||
<< " artwork: ["
|
<< " artwork: ["
|
||||||
<< " { src: \"" << LmsApp->getImageResource()->getTrackUrl(trackId, ImageResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" },"
|
<< " { src: \"" << LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::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::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" },"
|
||||||
<< " ]"
|
<< " ]"
|
||||||
<< "};";
|
<< "};";
|
||||||
oss << "LMS.mediaplayer.loadTrack(params, " << (play ? "true" : "false") << ")"; // true to autoplay
|
oss << "LMS.mediaplayer.loadTrack(params, " << (play ? "true" : "false") << ")"; // true to autoplay
|
||||||
|
|||||||
@@ -29,6 +29,9 @@
|
|||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
|
class AudioFileResource;
|
||||||
|
class AudioTranscodeResource;
|
||||||
|
|
||||||
class MediaPlayer : public Wt::WTemplate
|
class MediaPlayer : public Wt::WTemplate
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@@ -105,6 +108,8 @@ class MediaPlayer : public Wt::WTemplate
|
|||||||
Wt::Signal<> settingsLoaded;
|
Wt::Signal<> settingsLoaded;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
std::unique_ptr<AudioFileResource> _audioFileResource;
|
||||||
|
std::unique_ptr<AudioTranscodeResource> _audioTranscodeResource;
|
||||||
|
|
||||||
std::optional<Database::IdType> _trackIdLoaded;
|
std::optional<Database::IdType> _trackIdLoaded;
|
||||||
std::optional<Settings> _settings;
|
std::optional<Settings> _settings;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
#include "common/LoadingIndicator.hpp"
|
#include "common/LoadingIndicator.hpp"
|
||||||
#include "resource/ImageResource.hpp"
|
#include "resource/CoverResource.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
#include "MediaPlayer.hpp"
|
#include "MediaPlayer.hpp"
|
||||||
#include "TrackStringUtils.hpp"
|
#include "TrackStringUtils.hpp"
|
||||||
@@ -394,10 +394,10 @@ PlayQueue::processTracks(PlayQueueAction action, const std::vector<Database::IdT
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nbAddedTracks > 0)
|
if (nbAddedTracks > 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())
|
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));
|
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
||||||
auto cover = std::make_unique<Wt::WImage>();
|
auto cover = std::make_unique<Wt::WImage>();
|
||||||
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->setStyleClass("Lms-cover");
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(cover));
|
||||||
@@ -452,7 +452,7 @@ PlayQueue::addSome()
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto cover = entry->bindNew<Wt::WImage>("cover");
|
auto cover = entry->bindNew<Wt::WImage>("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->setStyleClass("Lms-cover");
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -514,7 +514,7 @@ SettingsView::refreshView()
|
|||||||
|
|
||||||
if (LmsApp->getUser()->isDemo())
|
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;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -524,7 +524,7 @@ SettingsView::refreshView()
|
|||||||
if (model->validate())
|
if (model->validate())
|
||||||
{
|
{
|
||||||
model->saveData();
|
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.
|
// Udate the view: Delete any validation message in the view, etc.
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "database/Cluster.hpp"
|
#include "database/Cluster.hpp"
|
||||||
#include "database/ScanSettings.hpp"
|
#include "database/ScanSettings.hpp"
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
|
#include "scanner/IScanner.hpp"
|
||||||
#include "utils/Logger.hpp"
|
#include "utils/Logger.hpp"
|
||||||
#include "utils/Service.hpp"
|
#include "utils/Service.hpp"
|
||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
@@ -222,8 +223,8 @@ DatabaseSettingsView::refreshView()
|
|||||||
{
|
{
|
||||||
model->saveData();
|
model->saveData();
|
||||||
|
|
||||||
Service<Scanner::IMediaScanner>::get()->requestImmediateScan(false);
|
Service<Scanner::IScanner>::get()->requestImmediateScan(false);
|
||||||
LmsApp->notifyMsg(MsgType::Success, Wt::WString::tr("Lms.Admin.Database.settings-saved"));
|
LmsApp->notifyMsg(LmsApplication::MsgType::Success, Wt::WString::tr("Lms.Admin.Database.settings-saved"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Udate the view: Delete any validation message in the view, etc.
|
// Udate the view: Delete any validation message in the view, etc.
|
||||||
@@ -239,7 +240,7 @@ DatabaseSettingsView::refreshView()
|
|||||||
|
|
||||||
immScanBtn->clicked().connect([=]
|
immScanBtn->clicked().connect([=]
|
||||||
{
|
{
|
||||||
Service<Scanner::IMediaScanner>::get()->requestImmediateScan(false);
|
Service<Scanner::IScanner>::get()->requestImmediateScan(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
t->updateView(model.get());
|
t->updateView(model.get());
|
||||||
|
|||||||
@@ -202,7 +202,7 @@ InitWizardView::InitWizardView()
|
|||||||
if (model->validate())
|
if (model->validate())
|
||||||
{
|
{
|
||||||
model->saveData();
|
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);
|
saveButton->setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
|
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
|
#include "scanner/IScanner.hpp"
|
||||||
#include "utils/Service.hpp"
|
#include "utils/Service.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
@@ -139,13 +140,13 @@ ScannerController::ScannerController()
|
|||||||
|
|
||||||
auto onDbEvent = [&]() { refreshContents(); };
|
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->getScannerEvents().scanComplete.connect(this, onDbEvent);
|
||||||
LmsApp->getEvents().dbScanInProgress.connect(this, onDbEvent);
|
LmsApp->getScannerEvents().scanInProgress.connect(this, onDbEvent);
|
||||||
LmsApp->getEvents().dbScanScheduled.connect(this, onDbEvent);
|
LmsApp->getScannerEvents().scanScheduled.connect(this, onDbEvent);
|
||||||
|
|
||||||
refreshContents();
|
refreshContents();
|
||||||
}
|
}
|
||||||
@@ -162,20 +163,20 @@ ScannerController::refreshContents()
|
|||||||
actionBtn->actionButton()->setText(Wt::WString::tr("Lms.Admin.ScannerController.scan-now"));
|
actionBtn->actionButton()->setText(Wt::WString::tr("Lms.Admin.ScannerController.scan-now"));
|
||||||
actionBtn->actionButton()->clicked().connect([]
|
actionBtn->actionButton()->clicked().connect([]
|
||||||
{
|
{
|
||||||
Service<Scanner::IMediaScanner>::get()->requestImmediateScan(false);
|
Service<Scanner::IScanner>::get()->requestImmediateScan(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
auto popup = std::make_unique<Wt::WPopupMenu>();
|
auto popup = std::make_unique<Wt::WPopupMenu>();
|
||||||
popup->addItem(Wt::WString::tr("Lms.Admin.ScannerController.force-scan-now"));
|
popup->addItem(Wt::WString::tr("Lms.Admin.ScannerController.force-scan-now"));
|
||||||
popup->itemSelected().connect([]
|
popup->itemSelected().connect([]
|
||||||
{
|
{
|
||||||
Service<Scanner::IMediaScanner>::get()->requestImmediateScan(true);
|
Service<Scanner::IScanner>::get()->requestImmediateScan(true);
|
||||||
});
|
});
|
||||||
actionBtn->dropDownButton()->setMenu(std::move(popup));
|
actionBtn->dropDownButton()->setMenu(std::move(popup));
|
||||||
actionBtn->dropDownButton()->addStyleClass("btn-primary");
|
actionBtn->dropDownButton()->addStyleClass("btn-primary");
|
||||||
|
|
||||||
|
|
||||||
const IMediaScanner::Status status {Service<IMediaScanner>::get()->getStatus()};
|
const IScanner::Status status {Service<IScanner>::get()->getStatus()};
|
||||||
if (status.lastCompleteScanStats)
|
if (status.lastCompleteScanStats)
|
||||||
{
|
{
|
||||||
bindString("last-scan", Wt::WString::tr("Lms.Admin.ScannerController.last-scan-status")
|
bindString("last-scan", Wt::WString::tr("Lms.Admin.ScannerController.last-scan-status")
|
||||||
@@ -199,16 +200,16 @@ ScannerController::refreshContents()
|
|||||||
|
|
||||||
switch (status.currentState)
|
switch (status.currentState)
|
||||||
{
|
{
|
||||||
case IMediaScanner::State::NotScheduled:
|
case IScanner::State::NotScheduled:
|
||||||
bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-not-scheduled"));
|
bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-not-scheduled"));
|
||||||
bindEmpty("step-status");
|
bindEmpty("step-status");
|
||||||
break;
|
break;
|
||||||
case IMediaScanner::State::Scheduled:
|
case IScanner::State::Scheduled:
|
||||||
bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-scheduled")
|
bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-scheduled")
|
||||||
.arg(status.nextScheduledScan.toString()));
|
.arg(status.nextScheduledScan.toString()));
|
||||||
bindEmpty("step-status");
|
bindEmpty("step-status");
|
||||||
break;
|
break;
|
||||||
case IMediaScanner::State::InProgress:
|
case IScanner::State::InProgress:
|
||||||
bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-in-progress")
|
bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-in-progress")
|
||||||
.arg(static_cast<int>(status.currentScanStepStats->currentStep) + 1)
|
.arg(static_cast<int>(status.currentScanStepStats->currentStep) + 1)
|
||||||
.arg(Scanner::ScanProgressStepCount));
|
.arg(Scanner::ScanProgressStepCount));
|
||||||
|
|||||||
@@ -316,7 +316,7 @@ UserView::refreshView()
|
|||||||
if (model->validate())
|
if (model->validate())
|
||||||
{
|
{
|
||||||
model->saveData();
|
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);
|
LmsApp->setInternalPath("/admin/users", true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include <Wt/WAnchor.h>
|
#include <Wt/WAnchor.h>
|
||||||
#include <Wt/WImage.h>
|
#include <Wt/WImage.h>
|
||||||
|
#include <Wt/WPopupMenu.h>
|
||||||
#include <Wt/WTemplate.h>
|
#include <Wt/WTemplate.h>
|
||||||
#include <Wt/WText.h>
|
#include <Wt/WText.h>
|
||||||
|
|
||||||
|
|||||||
@@ -73,9 +73,10 @@ Artists::Artists(Filters* filters)
|
|||||||
_linkType->changed().connect([this] { refreshView(); });
|
_linkType->changed().connect([this] { refreshView(); });
|
||||||
refreshArtistLinkTypes();
|
refreshArtistLinkTypes();
|
||||||
|
|
||||||
LmsApp->getEvents().dbScanned.connect(this, [this]
|
LmsApp->getScannerEvents().scanComplete.connect(this, [this](const Scanner::ScanStats& stats)
|
||||||
{
|
{
|
||||||
refreshArtistLinkTypes();
|
if (stats.nbChanges())
|
||||||
|
refreshArtistLinkTypes();
|
||||||
});
|
});
|
||||||
|
|
||||||
_container = bindNew<Wt::WContainerWidget>("artists");
|
_container = bindNew<Wt::WContainerWidget>("artists");
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ Filters::add(Database::IdType clusterId)
|
|||||||
_sigUpdated.emit();
|
_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();
|
_sigUpdated.emit();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
#include "database/Artist.hpp"
|
#include "database/Artist.hpp"
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "resource/ImageResource.hpp"
|
#include "resource/CoverResource.hpp"
|
||||||
|
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ namespace UserInterface::ReleaseListHelpers
|
|||||||
|
|
||||||
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
||||||
auto cover = std::make_unique<Wt::WImage>();
|
auto cover = std::make_unique<Wt::WImage>();
|
||||||
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->setStyleClass("Lms-cover");
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(cover));
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#include "ReleasePopup.hpp"
|
#include "ReleasePopup.hpp"
|
||||||
|
|
||||||
|
#include <Wt/WPopupMenu.h>
|
||||||
|
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
#include "resource/DownloadResource.hpp"
|
#include "resource/DownloadResource.hpp"
|
||||||
#include "resource/ImageResource.hpp"
|
#include "resource/CoverResource.hpp"
|
||||||
#include "Filters.hpp"
|
#include "Filters.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
#include "LmsApplicationException.hpp"
|
#include "LmsApplicationException.hpp"
|
||||||
@@ -127,7 +127,7 @@ Release::refreshView()
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Wt::WImage* cover {bindNew<Wt::WImage>("cover", Wt::WLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), ImageResource::Size::Large)))};
|
Wt::WImage* cover {bindNew<Wt::WImage>("cover", Wt::WLink(LmsApp->getCoverResource()->getReleaseUrl(release.id(), CoverResource::Size::Large)))};
|
||||||
cover->setStyleClass("Lms-cover-large");
|
cover->setStyleClass("Lms-cover-large");
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
#include <Wt/WAnchor.h>
|
#include <Wt/WAnchor.h>
|
||||||
#include <Wt/WImage.h>
|
#include <Wt/WImage.h>
|
||||||
#include <Wt/WMenu.h>
|
#include <Wt/WMenu.h>
|
||||||
|
#include <Wt/WPopupMenu.h>
|
||||||
#include <Wt/WText.h>
|
#include <Wt/WText.h>
|
||||||
|
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
@@ -34,7 +35,6 @@
|
|||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
#include "common/LoadingIndicator.hpp"
|
#include "common/LoadingIndicator.hpp"
|
||||||
#include "resource/ImageResource.hpp"
|
|
||||||
#include "ReleaseListHelpers.hpp"
|
#include "ReleaseListHelpers.hpp"
|
||||||
#include "Filters.hpp"
|
#include "Filters.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|||||||
@@ -27,7 +27,6 @@
|
|||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
|
|
||||||
#include "resource/ImageResource.hpp"
|
|
||||||
#include "ArtistListHelpers.hpp"
|
#include "ArtistListHelpers.hpp"
|
||||||
#include "Filters.hpp"
|
#include "Filters.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
#include "resource/DownloadResource.hpp"
|
#include "resource/DownloadResource.hpp"
|
||||||
#include "resource/ImageResource.hpp"
|
#include "resource/CoverResource.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
#include "MediaPlayer.hpp"
|
#include "MediaPlayer.hpp"
|
||||||
#include "TrackPopup.hpp"
|
#include "TrackPopup.hpp"
|
||||||
@@ -71,9 +71,9 @@ namespace UserInterface::TrackListHelpers
|
|||||||
entry->setCondition("if-has-release", true);
|
entry->setCondition("if-has-release", true);
|
||||||
entry->bindWidget("release", LmsApplication::createReleaseAnchor(track->getRelease()));
|
entry->bindWidget("release", LmsApplication::createReleaseAnchor(track->getRelease()));
|
||||||
{
|
{
|
||||||
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
|
Wt::WAnchor* anchor {entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false))};
|
||||||
auto cover = std::make_unique<Wt::WImage>();
|
auto cover {std::make_unique<Wt::WImage>()};
|
||||||
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->setStyleClass("Lms-cover");
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
anchor->setImage(std::move(cover));
|
anchor->setImage(std::move(cover));
|
||||||
@@ -81,8 +81,8 @@ namespace UserInterface::TrackListHelpers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
auto cover = entry->bindNew<Wt::WImage>("cover");
|
auto* cover {entry->bindNew<Wt::WImage>("cover")};
|
||||||
cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(trackId, ImageResource::Size::Large));
|
cover->setImageLink(LmsApp->getCoverResource()->getTrackUrl(trackId, CoverResource::Size::Large));
|
||||||
cover->setStyleClass("Lms-cover");
|
cover->setStyleClass("Lms-cover");
|
||||||
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
#include "TrackPopup.hpp"
|
#include "TrackPopup.hpp"
|
||||||
|
|
||||||
|
#include <Wt/WPopupMenu.h>
|
||||||
|
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
#include "database/User.hpp"
|
#include "database/User.hpp"
|
||||||
|
|||||||
@@ -20,8 +20,9 @@
|
|||||||
#include "TracksView.hpp"
|
#include "TracksView.hpp"
|
||||||
|
|
||||||
#include <Wt/WAnchor.h>
|
#include <Wt/WAnchor.h>
|
||||||
#include <Wt/WMenu.h>
|
|
||||||
#include <Wt/WLineEdit.h>
|
#include <Wt/WLineEdit.h>
|
||||||
|
#include <Wt/WMenu.h>
|
||||||
|
#include <Wt/WPopupMenu.h>
|
||||||
#include <Wt/WText.h>
|
#include <Wt/WText.h>
|
||||||
|
|
||||||
#include "database/Artist.hpp"
|
#include "database/Artist.hpp"
|
||||||
@@ -34,7 +35,6 @@
|
|||||||
#include "utils/String.hpp"
|
#include "utils/String.hpp"
|
||||||
|
|
||||||
#include "common/LoadingIndicator.hpp"
|
#include "common/LoadingIndicator.hpp"
|
||||||
#include "resource/ImageResource.hpp"
|
|
||||||
#include "Filters.hpp"
|
#include "Filters.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
#include "MediaPlayer.hpp"
|
#include "MediaPlayer.hpp"
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ImageResource.hpp"
|
#include "CoverResource.hpp"
|
||||||
|
|
||||||
#include <Wt/WApplication.h>
|
#include <Wt/WApplication.h>
|
||||||
#include <Wt/Http/Response.h>
|
#include <Wt/Http/Response.h>
|
||||||
@@ -35,25 +35,34 @@
|
|||||||
|
|
||||||
namespace UserInterface {
|
namespace UserInterface {
|
||||||
|
|
||||||
ImageResource::~ImageResource()
|
CoverResource::CoverResource()
|
||||||
|
{
|
||||||
|
LmsApp->getScannerEvents().scanComplete.connect(this, [this](const Scanner::ScanStats& stats)
|
||||||
|
{
|
||||||
|
if (stats.nbChanges())
|
||||||
|
setChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CoverResource::~CoverResource()
|
||||||
{
|
{
|
||||||
beingDeleted();
|
beingDeleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
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<std::size_t>(size));
|
return url() + "&releaseid=" + std::to_string(releaseId) + "&size=" + std::to_string(static_cast<std::size_t>(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string
|
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<std::size_t>(size));
|
return url() + "&trackid=" + std::to_string(trackId) + "&size=" + std::to_string(static_cast<std::size_t>(size));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
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
|
// Retrieve parameters
|
||||||
const std::string *trackIdStr = request.getParameter("trackid");
|
const std::string *trackIdStr = request.getParameter("trackid");
|
||||||
@@ -25,12 +25,13 @@
|
|||||||
namespace UserInterface
|
namespace UserInterface
|
||||||
{
|
{
|
||||||
|
|
||||||
class ImageResource : public Wt::WResource
|
class CoverResource : public Wt::WResource
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static const std::size_t maxSize {512};
|
static const std::size_t maxSize {512};
|
||||||
|
|
||||||
~ImageResource();
|
CoverResource();
|
||||||
|
~CoverResource();
|
||||||
|
|
||||||
enum class Size : std::size_t
|
enum class Size : std::size_t
|
||||||
{
|
{
|
||||||
Reference in New Issue
Block a user