Can now interrupt/relaunch scan + scan force from the web interface. ref #32

This commit is contained in:
emeric
2020-07-25 14:58:02 +02:00
parent 36ccd62a84
commit d0826daa41
13 changed files with 197 additions and 154 deletions
+2 -37
View File
@@ -60,7 +60,7 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-lg-offset-3 col-lg-9"> <div class="col-lg-offset-3 col-lg-9">
${apply-btn class="btn-primary"} ${discard-btn} ${immediate-scan-btn class="btn-info"} ${apply-btn class="btn-primary"} ${discard-btn}
</div> </div>
</div> </div>
</form> </form>
@@ -69,42 +69,7 @@
<br/> <br/>
<div class="row"> <div class="row">
<div class="col-lg-8"> <div class="col-lg-8">
<div class="panel panel-info"> ${scanner-controller}
<div class="panel-heading">${tr:Lms.Admin.Database.Status.status}</div>
<div class="panel-body">
${status}
</div>
</div>
</div>
</div>
</message>
<message id="Lms.Admin.Database.Status.template">
<div class="form-horizontal">
<div class="form-group">
<label class="col-lg-3 control-label">
${tr:Lms.Admin.Database.Status.status}
</label>
<div class="col-lg-9">
<div class="well well-sm">
${status}
</div>
</div>
</div>
<div class="form-group">
<label class="col-lg-3 control-label">
${tr:Lms.Admin.Database.Status.last-scan}
</label>
<div class="col-lg-9">
<div class="well well-sm">
${last-scan}
</div>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-3 col-lg-9">
${btn-report class="btn"}
</div>
</div> </div>
</div> </div>
</message> </message>
+44
View File
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8" ?>
<messages xmlns:if="Wt.WTemplate.conditions">
<message id="Lms.Admin.ScannerController.template">
<div class="panel panel-info">
<div class="panel-heading">${tr:Lms.Admin.ScannerController.scanner}</div>
<div class="panel-body">
<div class="form-horizontal">
<div class="form-group">
<label class="col-lg-3 control-label">
${tr:Lms.Admin.ScannerController.status}
</label>
<div class="col-lg-9">
<div class="well well-sm">
${status}
</div>
</div>
</div>
<div class="form-group">
<label class="col-lg-3 control-label">
${tr:Lms.Admin.ScannerController.last-scan}
</label>
<div class="col-lg-9">
<div class="well well-sm">
${last-scan}
</div>
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-3 col-lg-9">
${btn-report class="btn btn-xs"}
</div>
</div>
<div class="form-group">
<div class="col-lg-offset-3 col-lg-9">
${btn-action}
</div>
</div>
</div>
</div>
</div>
</message>
</messages>
+19 -16
View File
@@ -54,22 +54,25 @@
<message id="Lms.Admin.Database.update-start-time">Update start time</message> <message id="Lms.Admin.Database.update-start-time">Update start time</message>
<message id="Lms.Admin.Database.weekly">Weekly</message> <message id="Lms.Admin.Database.weekly">Weekly</message>
<message id="Lms.Admin.Database.Status.bad-duration">Cannot get track duration</message> <message id="Lms.Admin.ScannerController.bad-duration">Cannot get track duration</message>
<message id="Lms.Admin.Database.Status.cannot-parse-file">Cannot parse file</message> <message id="Lms.Admin.ScannerController.cannot-parse-file">Cannot parse file</message>
<message id="Lms.Admin.Database.Status.cannot-read-file">Cannot read file</message> <message id="Lms.Admin.ScannerController.cannot-read-file">Cannot read file</message>
<message id="Lms.Admin.Database.Status.duplicates-header">{1} duplicate files:</message> <message id="Lms.Admin.ScannerController.duplicates-header">{1} duplicate files:</message>
<message id="Lms.Admin.Database.Status.errors-header">{1} errors:</message> <message id="Lms.Admin.ScannerController.errors-header">{1} errors:</message>
<message id="Lms.Admin.Database.Status.get-report">Get report</message> <message id="Lms.Admin.ScannerController.force-scan-now">Force scan now</message>
<message id="Lms.Admin.Database.Status.last-scan">Last scan</message> <message id="Lms.Admin.ScannerController.get-report">Get report</message>
<message id="Lms.Admin.Database.Status.last-scan-not-available">Not available</message> <message id="Lms.Admin.ScannerController.last-scan">Last scan</message>
<message id="Lms.Admin.Database.Status.last-scan-status">Scanned {1} files in {2} on {3} ({4} errors, {5} duplicates)</message> <message id="Lms.Admin.ScannerController.last-scan-not-available">Not available</message>
<message id="Lms.Admin.Database.Status.no-audio-track">No audio track</message> <message id="Lms.Admin.ScannerController.last-scan-status">Scanned {1} files in {2} on {3} ({4} errors, {5} duplicates)</message>
<message id="Lms.Admin.Database.Status.same-hash">Duplicated file hash</message> <message id="Lms.Admin.ScannerController.no-audio-track">No audio track</message>
<message id="Lms.Admin.Database.Status.same-mbid">Duplicated MBID</message> <message id="Lms.Admin.ScannerController.same-hash">Duplicated file hash</message>
<message id="Lms.Admin.Database.Status.status">Status</message> <message id="Lms.Admin.ScannerController.same-mbid">Duplicated MBID</message>
<message id="Lms.Admin.Database.Status.status-not-scheduled">Not scheduled</message> <message id="Lms.Admin.ScannerController.scan-now">Scan now</message>
<message id="Lms.Admin.Database.Status.status-scheduled">Scheduled on {1}</message> <message id="Lms.Admin.ScannerController.scanner">Scanner</message>
<message id="Lms.Admin.Database.Status.status-in-progress">Scanning {1} of {2} files ({3} %)</message> <message id="Lms.Admin.ScannerController.status">Status</message>
<message id="Lms.Admin.ScannerController.status-not-scheduled">Not scheduled</message>
<message id="Lms.Admin.ScannerController.status-scheduled">Scheduled on {1}</message>
<message id="Lms.Admin.ScannerController.status-in-progress">Scanning {1} of {2} files ({3} %)</message>
<!--Users--> <!--Users-->
<message id="Lms.Admin.Users.add">New user</message> <message id="Lms.Admin.Users.add">New user</message>
+20 -17
View File
@@ -54,22 +54,25 @@
<message id="Lms.Admin.Database.update-start-time">Heure de départ de la mise à jour</message> <message id="Lms.Admin.Database.update-start-time">Heure de départ de la mise à jour</message>
<message id="Lms.Admin.Database.weekly">Toutes les semaines</message> <message id="Lms.Admin.Database.weekly">Toutes les semaines</message>
<message id="Lms.Admin.Database.Status.bad-duration">Impossible de récupérer la durée de la piste</message> <message id="Lms.Admin.ScannerController.bad-duration">Impossible de récupérer la durée de la piste</message>
<message id="Lms.Admin.Database.Status.cannot-parse-file">Impossible d'analyser le fichier</message> <message id="Lms.Admin.ScannerController.cannot-parse-file">Impossible d'analyser le fichier</message>
<message id="Lms.Admin.Database.Status.cannot-read-file">Impossible de lire le fichier</message> <message id="Lms.Admin.ScannerController.cannot-read-file">Impossible de lire le fichier</message>
<message id="Lms.Admin.Database.Status.duplicates-header">{1} fichiers dupliqués :</message> <message id="Lms.Admin.ScannerController.duplicates-header">{1} fichiers dupliqués :</message>
<message id="Lms.Admin.Database.Status.errors-header">{1} erreurs :</message> <message id="Lms.Admin.ScannerController.errors-header">{1} erreurs :</message>
<message id="Lms.Admin.Database.Status.get-report">Rapport</message> <message id="Lms.Admin.ScannerController.force-scan-now">Lancer un scan forcé</message>
<message id="Lms.Admin.Database.Status.last-scan">Dernier scan</message> <message id="Lms.Admin.ScannerController.get-report">Rapport</message>
<message id="Lms.Admin.Database.Status.last-scan-not-available">Non disponible</message> <message id="Lms.Admin.ScannerController.last-scan">Dernier scan</message>
<message id="Lms.Admin.Database.Status.last-scan-status">{1} fichiers scannés en {2} le {3} ({4} erreurs, {5} duplicatas)</message> <message id="Lms.Admin.ScannerController.last-scan-not-available">Non disponible</message>
<message id="Lms.Admin.Database.Status.no-audio-track">Pas de piste audio</message> <message id="Lms.Admin.ScannerController.last-scan-status">{1} fichiers scannés en {2} le {3} ({4} erreurs, {5} duplicatas)</message>
<message id="Lms.Admin.Database.Status.same-hash">Hash dupliqué</message> <message id="Lms.Admin.ScannerController.no-audio-track">Pas de piste audio</message>
<message id="Lms.Admin.Database.Status.same-mbid">MBID dupliqué</message> <message id="Lms.Admin.ScannerController.same-hash">Hash dupliqué</message>
<message id="Lms.Admin.Database.Status.status">Statut</message> <message id="Lms.Admin.ScannerController.same-mbid">MBID dupliqué</message>
<message id="Lms.Admin.Database.Status.status-not-scheduled">Non planifié</message> <message id="Lms.Admin.ScannerController.scan-now">Lancer un scan</message>
<message id="Lms.Admin.Database.Status.status-scheduled">Planifié le {1}</message> <message id="Lms.Admin.ScannerController.scanner">Scanner</message>
<message id="Lms.Admin.Database.Status.status-in-progress">Scan de {1} fichiers sur {2} ({3} %)</message> <message id="Lms.Admin.ScannerController.status">Statut</message>
<message id="Lms.Admin.ScannerController.status-not-scheduled">Non planifié</message>
<message id="Lms.Admin.ScannerController.status-scheduled">Planifié le {1}</message>
<message id="Lms.Admin.ScannerController.status-in-progress">Scan de {1} fichiers sur {2} ({3} %)</message>
<!--Users--> <!--Users-->
<message id="Lms.Admin.Users.add">Ajouter</message> <message id="Lms.Admin.Users.add">Ajouter</message>
@@ -132,7 +135,7 @@
<message id="Lms.Explore.Artists.linktype-releaseartist">Artistes d'album</message> <message id="Lms.Explore.Artists.linktype-releaseartist">Artistes d'album</message>
<!--Explore:Release--> <!--Explore:Release-->
<message id="Lms.Explore.ReleaseInfo.similar-releases">Albums similaires</message> <message id="Lms.Explore.Release.similar-releases">Albums similaires</message>
<message id="Lms.Explore.Release.copyright">Copyright</message> <message id="Lms.Explore.Release.copyright">Copyright</message>
<message id="Lms.Explore.Release.disc">Disque {1}</message> <message id="Lms.Explore.Release.disc">Disque {1}</message>
+41 -36
View File
@@ -257,49 +257,51 @@ MediaScanner::MediaScanner(Database::Db& db)
} }
MediaScanner::~MediaScanner() MediaScanner::~MediaScanner()
{
if (_running)
stop();
}
void
MediaScanner::restart(void)
{ {
stop(); stop();
start();
} }
void void
MediaScanner::start(void) MediaScanner::start()
{ {
_running = true;
scheduleNextScan(); scheduleNextScan();
_ioService.start(); _ioService.start();
} }
void void
MediaScanner::stop(void) MediaScanner::stop()
{ {
_running = false; _abortScan = true;
_scheduleTimer.cancel(); _scheduleTimer.cancel();
_ioService.stop(); _ioService.stop();
} }
void void
MediaScanner::requestImmediateScan() MediaScanner::abortScan()
{ {
_abortScan = true;
_scheduleTimer.cancel();
_ioService.stop();
_abortScan = false;
_ioService.start();
}
void
MediaScanner::requestImmediateScan(bool force)
{
abortScan();
_ioService.post([=]() _ioService.post([=]()
{ {
scheduleScan(); scheduleScan(force);
}); });
} }
void void
MediaScanner::requestReschedule() MediaScanner::requestReload()
{ {
_ioService.post([=]() _ioService.post([=]()
{ {
@@ -308,7 +310,7 @@ MediaScanner::requestReschedule()
} }
MediaScanner::Status MediaScanner::Status
MediaScanner::getStatus() MediaScanner::getStatus() const
{ {
Status res; Status res;
@@ -365,7 +367,7 @@ MediaScanner::scheduleNextScan()
if (nextScanDate.isValid()) if (nextScanDate.isValid())
{ {
nextScanDateTime = Wt::WDateTime {nextScanDate, _startTime}; nextScanDateTime = Wt::WDateTime {nextScanDate, _startTime};
scheduleScan(nextScanDateTime); scheduleScan(false, nextScanDateTime);
} }
{ {
@@ -384,7 +386,7 @@ MediaScanner::countAllFiles(ScanStats& stats)
exploreFilesRecursive(_mediaDirectory, [&](std::error_code ec, const std::filesystem::path& path) exploreFilesRecursive(_mediaDirectory, [&](std::error_code ec, const std::filesystem::path& path)
{ {
if (!_running) if (_abortScan)
return false; return false;
if (!ec && isFileSupported(path, _fileExtensions)) if (!ec && isFileSupported(path, _fileExtensions))
@@ -398,13 +400,21 @@ MediaScanner::countAllFiles(ScanStats& stats)
} }
void void
MediaScanner::scheduleScan(const Wt::WDateTime& dateTime) MediaScanner::scheduleScan(bool force, const Wt::WDateTime& dateTime)
{ {
auto cb {[=](boost::system::error_code ec)
{
if (ec)
return;
scan(force);
}};
if (dateTime.isNull()) if (dateTime.isNull())
{ {
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan right now"; LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan right now";
_scheduleTimer.expires_from_now(std::chrono::seconds(0)); _scheduleTimer.expires_from_now(std::chrono::seconds {0});
_scheduleTimer.async_wait(std::bind(&MediaScanner::scan, this, std::placeholders::_1)); _scheduleTimer.async_wait(cb);
} }
else else
{ {
@@ -413,16 +423,13 @@ MediaScanner::scheduleScan(const Wt::WDateTime& dateTime)
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan at " << std::string(std::ctime(&t)); LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan at " << std::string(std::ctime(&t));
_scheduleTimer.expires_at(timePoint); _scheduleTimer.expires_at(timePoint);
_scheduleTimer.async_wait(std::bind(&MediaScanner::scan, this, std::placeholders::_1)); _scheduleTimer.async_wait(cb);
} }
} }
void void
MediaScanner::scan(boost::system::error_code err) MediaScanner::scan(bool forceScan)
{ {
if (err)
return;
scanStarted().emit(); scanStarted().emit();
{ {
@@ -438,8 +445,6 @@ MediaScanner::scan(boost::system::error_code err)
refreshScanSettings(); refreshScanSettings();
bool forceScan {false};
LMS_LOG(DBUPDATER, DEBUG) << "Counting files in media directory '" << _mediaDirectory.string() << "'..."; LMS_LOG(DBUPDATER, DEBUG) << "Counting files in media directory '" << _mediaDirectory.string() << "'...";
countAllFiles(stats); countAllFiles(stats);
LMS_LOG(DBUPDATER, DEBUG) << "-> Nb files = " << stats.filesToScan; LMS_LOG(DBUPDATER, DEBUG) << "-> Nb files = " << stats.filesToScan;
@@ -454,17 +459,17 @@ MediaScanner::scan(boost::system::error_code err)
removeOrphanEntries(); removeOrphanEntries();
if (_running) if (!_abortScan)
checkDuplicatedAudioFiles(stats); checkDuplicatedAudioFiles(stats);
// Now update all the track features if needed // Now update all the track features if needed
fetchTrackFeatures(stats); fetchTrackFeatures(stats);
LMS_LOG(DBUPDATER, INFO) << "Scan " << (_running ? "complete" : "aborted") << ". Changes = " << stats.nbChanges() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.errors.size() << "), features fetched = " << stats.featuresFetched << "/" << stats.featuresToFetch <<", duplicates = " << stats.duplicates.size(); LMS_LOG(DBUPDATER, INFO) << "Scan " << (_abortScan ? "aborted" : "complete") << ". Changes = " << stats.nbChanges() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.errors.size() << "), features fetched = " << stats.featuresFetched << "/" << stats.featuresToFetch <<", duplicates = " << stats.duplicates.size();
_dbSession.optimize(); _dbSession.optimize();
if (_running) if (!_abortScan)
{ {
stats.stopTime = Wt::WLocalDateTime::currentDateTime().toUTC(); stats.stopTime = Wt::WLocalDateTime::currentDateTime().toUTC();
{ {
@@ -546,7 +551,7 @@ MediaScanner::fetchTrackFeatures(ScanStats& stats)
for (const TrackInfo& trackToFetch : tracksToFetch) for (const TrackInfo& trackToFetch : tracksToFetch)
{ {
if (!_running) if (_abortScan)
return; return;
if (fetchTrackFeatures(trackToFetch.id, trackToFetch.mbid)) if (fetchTrackFeatures(trackToFetch.id, trackToFetch.mbid))
@@ -774,7 +779,7 @@ MediaScanner::scanMediaDirectory(const std::filesystem::path& mediaDirectory, bo
{ {
exploreFilesRecursive(mediaDirectory, [&](std::error_code ec, const std::filesystem::path& path) exploreFilesRecursive(mediaDirectory, [&](std::error_code ec, const std::filesystem::path& path)
{ {
if (!_running) if (_abortScan)
return false; return false;
if (ec) if (ec)
@@ -861,7 +866,7 @@ MediaScanner::removeMissingTracks(ScanStats& stats)
for (const auto& [trackId, trackPath] : trackPaths) for (const auto& [trackId, trackPath] : trackPaths)
{ {
if (!_running) if (_abortScan)
return; return;
if (!checkFile(trackPath, _mediaDirectory, _fileExtensions)) if (!checkFile(trackPath, _mediaDirectory, _fileExtensions))
+11 -9
View File
@@ -52,12 +52,10 @@ class MediaScanner : public IMediaScanner
void start() override; void start() override;
void stop() override; void stop() override;
void restart() override; void requestReload() override;
void requestImmediateScan(bool force) override;
void requestImmediateScan() override; Status getStatus() const override;
void requestReschedule() override ;
Status getStatus() override;
Wt::Signal<>& scanStarted() override { return _sigScanStarted; } Wt::Signal<>& scanStarted() override { return _sigScanStarted; }
Wt::Signal<>& scanComplete() override { return _sigScanComplete; } Wt::Signal<>& scanComplete() override { return _sigScanComplete; }
@@ -68,10 +66,12 @@ class MediaScanner : public IMediaScanner
// Job handling // Job handling
void scheduleNextScan(); void scheduleNextScan();
void scheduleScan(const Wt::WDateTime& dateTime = {}); void scheduleScan(bool force, const Wt::WDateTime& dateTime = {});
void abortScan();
// Update database (scheduled callback) // Update database (scheduled callback)
void scan(boost::system::error_code ec); void scan(bool force);
void scanMediaDirectory( const std::filesystem::path& mediaDirectory, bool forceScan, ScanStats& stats); void scanMediaDirectory( const std::filesystem::path& mediaDirectory, bool forceScan, ScanStats& stats);
bool fetchTrackFeatures(Database::IdType trackId, const UUID& MBID); bool fetchTrackFeatures(Database::IdType trackId, const UUID& MBID);
@@ -89,7 +89,9 @@ class MediaScanner : public IMediaScanner
void notifyInProgressIfNeeded(const ScanStats& stats); void notifyInProgressIfNeeded(const ScanStats& stats);
void notifyInProgress(const ScanStats& stats); void notifyInProgress(const ScanStats& stats);
bool _running {}; std::mutex _scanInProgress {};
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; Wt::Signal<> _sigScanStarted;
@@ -100,7 +102,7 @@ class MediaScanner : public IMediaScanner
Database::Session _dbSession; Database::Session _dbSession;
std::unique_ptr<MetaData::IParser> _metadataParser; std::unique_ptr<MetaData::IParser> _metadataParser;
std::shared_mutex _statusMutex; mutable std::shared_mutex _statusMutex;
State _curState {State::NotScheduled}; State _curState {State::NotScheduled};
std::optional<ScanStats> _lastCompleteScanStats; std::optional<ScanStats> _lastCompleteScanStats;
std::optional<ScanProgressStats> _inProgressScanStats; std::optional<ScanProgressStats> _inProgressScanStats;
@@ -40,11 +40,10 @@ class IMediaScanner
virtual void start() = 0; virtual void start() = 0;
virtual void stop() = 0; virtual void stop() = 0;
virtual void restart() = 0;
// Async requests // Async requests
virtual void requestImmediateScan() = 0; virtual void requestReload() = 0;
virtual void requestReschedule() = 0; virtual void requestImmediateScan(bool force) = 0;
enum class State enum class State
@@ -56,13 +55,13 @@ class IMediaScanner
struct Status struct Status
{ {
State currentState {State::NotScheduled}; State currentState {State::NotScheduled};
Wt::WDateTime nextScheduledScan; Wt::WDateTime nextScheduledScan;
std::optional<ScanStats> lastCompleteScanStats; std::optional<ScanStats> lastCompleteScanStats;
std::optional<ScanProgressStats> inProgressScanStats; std::optional<ScanProgressStats> inProgressScanStats;
}; };
virtual Status getStatus() = 0; virtual Status getStatus() const = 0;
// Called just after scan start // Called just after scan start
virtual Wt::Signal<>& scanStarted() = 0; virtual Wt::Signal<>& scanStarted() = 0;
+1 -1
View File
@@ -54,7 +54,7 @@ namespace API::Subsonic::Scan
Response Response
handleStartScan(RequestContext& context) handleStartScan(RequestContext& context)
{ {
ServiceProvider<IMediaScanner>::get()->requestImmediateScan(); ServiceProvider<IMediaScanner>::get()->requestImmediateScan(false);
Response response {Response::createOkResponse(context)}; Response response {Response::createOkResponse(context)};
response.addNode("scanStatus", createStatusResponseNode()); response.addNode("scanStatus", createStatusResponseNode());
+1 -1
View File
@@ -10,7 +10,7 @@ add_executable(lms
ui/SettingsView.cpp ui/SettingsView.cpp
ui/TrackStringUtils.cpp ui/TrackStringUtils.cpp
ui/admin/DatabaseSettingsView.cpp ui/admin/DatabaseSettingsView.cpp
ui/admin/DatabaseStatus.cpp ui/admin/ScannerController.cpp
ui/admin/InitWizardView.cpp ui/admin/InitWizardView.cpp
ui/admin/UserView.cpp ui/admin/UserView.cpp
ui/admin/UsersView.cpp ui/admin/UsersView.cpp
+1
View File
@@ -123,6 +123,7 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env,
// Add a resource bundle // Add a resource bundle
messageResourceBundle().use(appRoot() + "admin-database"); messageResourceBundle().use(appRoot() + "admin-database");
messageResourceBundle().use(appRoot() + "admin-initwizard"); messageResourceBundle().use(appRoot() + "admin-initwizard");
messageResourceBundle().use(appRoot() + "admin-scannercontroller");
messageResourceBundle().use(appRoot() + "admin-user"); messageResourceBundle().use(appRoot() + "admin-user");
messageResourceBundle().use(appRoot() + "admin-users"); messageResourceBundle().use(appRoot() + "admin-users");
messageResourceBundle().use(appRoot() + "artist"); messageResourceBundle().use(appRoot() + "artist");
+4 -4
View File
@@ -34,7 +34,7 @@
#include "common/Validators.hpp" #include "common/Validators.hpp"
#include "common/ValueStringModel.hpp" #include "common/ValueStringModel.hpp"
#include "DatabaseStatus.hpp" #include "ScannerController.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
namespace UserInterface { namespace UserInterface {
@@ -220,7 +220,7 @@ DatabaseSettingsView::refreshView()
Wt::WPushButton *discardBtn = t->bindWidget("discard-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.discard"))); Wt::WPushButton *discardBtn = t->bindWidget("discard-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.discard")));
Wt::WPushButton *immScanBtn = t->bindWidget("immediate-scan-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.Admin.Database.immediate-scan"))); Wt::WPushButton *immScanBtn = t->bindWidget("immediate-scan-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.Admin.Database.immediate-scan")));
t->bindNew<DatabaseStatus>("status"); t->bindNew<ScannerController>("scanner-controller");
saveBtn->clicked().connect([=] () saveBtn->clicked().connect([=] ()
{ {
@@ -230,7 +230,7 @@ DatabaseSettingsView::refreshView()
{ {
model->saveData(); model->saveData();
ServiceProvider<Scanner::IMediaScanner>::get()->requestReschedule(); ServiceProvider<Scanner::IMediaScanner>::get()->requestReload();
LmsApp->notifyMsg(MsgType::Success, Wt::WString::tr("Lms.Admin.Database.settings-saved")); LmsApp->notifyMsg(MsgType::Success, Wt::WString::tr("Lms.Admin.Database.settings-saved"));
} }
@@ -247,7 +247,7 @@ DatabaseSettingsView::refreshView()
immScanBtn->clicked().connect([=] () immScanBtn->clicked().connect([=] ()
{ {
ServiceProvider<Scanner::IMediaScanner>::get()->requestImmediateScan(); ServiceProvider<Scanner::IMediaScanner>::get()->requestImmediateScan(false);
}); });
t->updateView(model.get()); t->updateView(model.get());
@@ -17,14 +17,16 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>. * along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "DatabaseStatus.hpp" #include "ScannerController.hpp"
#include <iomanip> #include <iomanip>
#include <Wt/Http/Response.h> #include <Wt/Http/Response.h>
#include <Wt/WDateTime.h> #include <Wt/WDateTime.h>
#include <Wt/WPopupMenu.h>
#include <Wt/WPushButton.h> #include <Wt/WPushButton.h>
#include <Wt/WResource.h> #include <Wt/WResource.h>
#include <Wt/WSplitButton.h>
#include "utils/Service.hpp" #include "utils/Service.hpp"
#include "LmsApplication.hpp" #include "LmsApplication.hpp"
@@ -66,7 +68,7 @@ class ReportResource : public Wt::WResource
void handleRequest(const Wt::Http::Request&, Wt::Http::Response& response) void handleRequest(const Wt::Http::Request&, Wt::Http::Response& response)
{ {
response.out() << Wt::WString::tr("Lms.Admin.Database.Status.errors-header").arg(_stats.errors.size()).toUTF8() << std::endl; response.out() << Wt::WString::tr("Lms.Admin.ScannerController.errors-header").arg(_stats.errors.size()).toUTF8() << std::endl;
for (const auto& error : _stats.errors) for (const auto& error : _stats.errors)
{ {
@@ -78,7 +80,7 @@ class ReportResource : public Wt::WResource
response.out() << std::endl; response.out() << std::endl;
response.out() << Wt::WString::tr("Lms.Admin.Database.Status.duplicates-header").arg(_stats.duplicates.size()).toUTF8() << std::endl; response.out() << Wt::WString::tr("Lms.Admin.ScannerController.duplicates-header").arg(_stats.duplicates.size()).toUTF8() << std::endl;
for (const auto& duplicate : _stats.duplicates) for (const auto& duplicate : _stats.duplicates)
response.out() << duplicate.file.string() << " - " << duplicateReasonToWString(duplicate.reason).toUTF8() << std::endl; response.out() << duplicate.file.string() << " - " << duplicateReasonToWString(duplicate.reason).toUTF8() << std::endl;
@@ -90,10 +92,10 @@ class ReportResource : public Wt::WResource
{ {
switch (error) switch (error)
{ {
case Scanner::ScanErrorType::CannotReadFile: return Wt::WString::tr("Lms.Admin.Database.Status.cannot-read-file"); case Scanner::ScanErrorType::CannotReadFile: return Wt::WString::tr("Lms.Admin.ScannerController.cannot-read-file");
case Scanner::ScanErrorType::CannotParseFile: return Wt::WString::tr("Lms.Admin.Database.Status.cannot-parse-file"); case Scanner::ScanErrorType::CannotParseFile: return Wt::WString::tr("Lms.Admin.ScannerController.cannot-parse-file");
case Scanner::ScanErrorType::NoAudioTrack: return Wt::WString::tr("Lms.Admin.Database.Status.no-audio-track"); case Scanner::ScanErrorType::NoAudioTrack: return Wt::WString::tr("Lms.Admin.ScannerController.no-audio-track");
case Scanner::ScanErrorType::BadDuration: return Wt::WString::tr("Lms.Admin.Database.Status.bad-duration"); case Scanner::ScanErrorType::BadDuration: return Wt::WString::tr("Lms.Admin.ScannerController.bad-duration");
} }
return "?"; return "?";
} }
@@ -102,8 +104,8 @@ class ReportResource : public Wt::WResource
{ {
switch (reason) switch (reason)
{ {
case Scanner::DuplicateReason::SameHash: return Wt::WString::tr("Lms.Admin.Database.Status.same-hash"); case Scanner::DuplicateReason::SameHash: return Wt::WString::tr("Lms.Admin.ScannerController.same-hash");
case Scanner::DuplicateReason::SameMBID: return Wt::WString::tr("Lms.Admin.Database.Status.same-mbid"); case Scanner::DuplicateReason::SameMBID: return Wt::WString::tr("Lms.Admin.ScannerController.same-mbid");
} }
return "?"; return "?";
} }
@@ -112,8 +114,8 @@ class ReportResource : public Wt::WResource
}; };
DatabaseStatus::DatabaseStatus() ScannerController::ScannerController()
: WTemplate {Wt::WString::tr("Lms.Admin.Database.Status.template")} : WTemplate {Wt::WString::tr("Lms.Admin.ScannerController.template")}
{ {
addFunction("tr", &Wt::WTemplate::Functions::tr); addFunction("tr", &Wt::WTemplate::Functions::tr);
@@ -133,16 +135,34 @@ DatabaseStatus::DatabaseStatus()
} }
void void
DatabaseStatus::refreshContents() ScannerController::refreshContents()
{ {
using namespace Scanner; using namespace Scanner;
Wt::WPushButton* reportBtn {bindNew<Wt::WPushButton>("btn-report", Wt::WString::tr("Lms.Admin.Database.Status.get-report"))}; Wt::WPushButton* reportBtn {bindNew<Wt::WPushButton>("btn-report", Wt::WString::tr("Lms.Admin.ScannerController.get-report"))};
Wt::WSplitButton* actionBtn {bindNew<Wt::WSplitButton>("btn-action")};
actionBtn->actionButton()->addStyleClass("btn-primary");
actionBtn->actionButton()->setText(Wt::WString::tr("Lms.Admin.ScannerController.scan-now"));
actionBtn->actionButton()->clicked().connect([]
{
ServiceProvider<Scanner::IMediaScanner>::get()->requestImmediateScan(false);
});
auto popup = std::make_unique<Wt::WPopupMenu>();
popup->addItem(Wt::WString::tr("Lms.Admin.ScannerController.force-scan-now"));
popup->itemSelected().connect([]
{
ServiceProvider<Scanner::IMediaScanner>::get()->requestImmediateScan(true);
});
actionBtn->dropDownButton()->setMenu(std::move(popup));
actionBtn->dropDownButton()->addStyleClass("btn-primary");
const IMediaScanner::Status status {ServiceProvider<IMediaScanner>::get()->getStatus()}; const IMediaScanner::Status status {ServiceProvider<IMediaScanner>::get()->getStatus()};
if (status.lastCompleteScanStats) if (status.lastCompleteScanStats)
{ {
bindString("last-scan", Wt::WString::tr("Lms.Admin.Database.Status.last-scan-status") bindString("last-scan", Wt::WString::tr("Lms.Admin.ScannerController.last-scan-status")
.arg(status.lastCompleteScanStats->nbFiles()) .arg(status.lastCompleteScanStats->nbFiles())
.arg(durationToString(status.lastCompleteScanStats->startTime, status.lastCompleteScanStats->stopTime)) .arg(durationToString(status.lastCompleteScanStats->startTime, status.lastCompleteScanStats->stopTime))
.arg(status.lastCompleteScanStats->stopTime.toString()) .arg(status.lastCompleteScanStats->stopTime.toString())
@@ -156,7 +176,7 @@ DatabaseStatus::refreshContents()
} }
else else
{ {
bindString("last-scan", Wt::WString::tr("Lms.Admin.Database.Status.last-scan-not-available")); bindString("last-scan", Wt::WString::tr("Lms.Admin.ScannerController.last-scan-not-available"));
reportBtn->setEnabled(false); reportBtn->setEnabled(false);
} }
@@ -164,16 +184,16 @@ DatabaseStatus::refreshContents()
switch (status.currentState) switch (status.currentState)
{ {
case IMediaScanner::State::NotScheduled: case IMediaScanner::State::NotScheduled:
bindString("status", Wt::WString::tr("Lms.Admin.Database.Status.status-not-scheduled")); bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-not-scheduled"));
break; break;
case IMediaScanner::State::Scheduled: case IMediaScanner::State::Scheduled:
bindString("status", Wt::WString::tr("Lms.Admin.Database.Status.status-scheduled") bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-scheduled")
.arg(status.nextScheduledScan.toString())); .arg(status.nextScheduledScan.toString()));
break; break;
case IMediaScanner::State::InProgress: case IMediaScanner::State::InProgress:
{ {
std::ostringstream oss; std::ostringstream oss;
bindString("status", Wt::WString::tr("Lms.Admin.Database.Status.status-in-progress") bindString("status", Wt::WString::tr("Lms.Admin.ScannerController.status-in-progress")
.arg(status.inProgressScanStats->processedFiles) .arg(status.inProgressScanStats->processedFiles)
.arg(status.inProgressScanStats->filesToScan) .arg(status.inProgressScanStats->filesToScan)
.arg(status.inProgressScanStats->progress())); .arg(status.inProgressScanStats->progress()));
@@ -21,16 +21,17 @@
#include <Wt/WTemplate.h> #include <Wt/WTemplate.h>
namespace UserInterface { namespace UserInterface
class DatabaseStatus : public Wt::WTemplate
{ {
public:
DatabaseStatus();
private: class ScannerController : public Wt::WTemplate
void refreshContents(); {
}; public:
ScannerController();
private:
void refreshContents();
};
} // namespace DatabaseStatus } // namespace DatabaseStatus