Added a scan report button (Fixes #4), also fixed the regression about files being always rescanned even if no change has been made
This commit is contained in:
@@ -19,14 +19,10 @@
|
||||
|
||||
#include "DatabaseSettingsView.hpp"
|
||||
|
||||
#include <ostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <Wt/WComboBox.h>
|
||||
#include <Wt/WFormModel.h>
|
||||
#include <Wt/WLineEdit.h>
|
||||
#include <Wt/WPushButton.h>
|
||||
#include <Wt/WServer.h>
|
||||
#include <Wt/WString.h>
|
||||
#include <Wt/WTemplateFormView.h>
|
||||
|
||||
@@ -38,6 +34,7 @@
|
||||
|
||||
#include "common/Validators.hpp"
|
||||
#include "common/ValueStringModel.hpp"
|
||||
#include "DatabaseStatus.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
@@ -178,85 +175,6 @@ const Wt::WFormModel::Field DatabaseSettingsModel::UpdateStartTimeField = "upda
|
||||
const Wt::WFormModel::Field DatabaseSettingsModel::SimilarityEngineTypeField = "similarity-engine-type";
|
||||
const Wt::WFormModel::Field DatabaseSettingsModel::TagsField = "tags";
|
||||
|
||||
static
|
||||
std::string durationToString(const Wt::WDateTime& begin, const Wt::WDateTime& end)
|
||||
{
|
||||
auto secs {std::chrono::duration_cast<std::chrono::seconds>(end.toTimePoint() - begin.toTimePoint()).count()};
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
if (secs >= 3600)
|
||||
oss << secs/3600 << "h";
|
||||
if (secs >= 60)
|
||||
oss << std::setw(2) << std::setfill('0') << (secs % 3600) / 60 << "m";
|
||||
oss << std::setw(2) << std::setfill('0') << (secs % 60) << "s";
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
class DatabaseStatus : public Wt::WTemplate
|
||||
{
|
||||
public:
|
||||
|
||||
DatabaseStatus(): WTemplate {Wt::WString::tr("Lms.Admin.Database.Status.template")}
|
||||
{
|
||||
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||
|
||||
using namespace Scanner;
|
||||
|
||||
auto onDbEvent = [&]() { refreshContents(); };
|
||||
|
||||
LmsApp->getEvents().dbScanned.connect(this, onDbEvent);
|
||||
LmsApp->getEvents().dbScanInProgress.connect(this, onDbEvent);
|
||||
LmsApp->getEvents().dbScanScheduled.connect(this, onDbEvent);
|
||||
|
||||
refreshContents();
|
||||
}
|
||||
|
||||
private:
|
||||
void refreshContents()
|
||||
{
|
||||
using namespace Scanner;
|
||||
|
||||
MediaScanner::Status status {getService<MediaScanner>()->getStatus()};
|
||||
|
||||
if (status.lastScanStats)
|
||||
{
|
||||
bindString("last-scan", Wt::WString::tr("Lms.Admin.Database.Status.last-scan-status")
|
||||
.arg(status.lastScanStats->totalFiles)
|
||||
.arg(durationToString(status.lastScanStats->startTime, status.lastScanStats->stopTime))
|
||||
.arg(status.lastScanStats->stopTime.toString())
|
||||
.arg(status.lastScanStats->nbErrors())
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
bindString("last-scan", Wt::WString::tr("Lms.Admin.Database.Status.last-scan-not-available"));
|
||||
}
|
||||
|
||||
switch (status.currentState)
|
||||
{
|
||||
case MediaScanner::State::NotScheduled:
|
||||
bindString("status", Wt::WString::tr("Lms.Admin.Database.Status.status-not-scheduled"));
|
||||
break;
|
||||
case MediaScanner::State::Scheduled:
|
||||
bindString("status", Wt::WString::tr("Lms.Admin.Database.Status.status-scheduled")
|
||||
.arg(status.nextScheduledScan.toString()));
|
||||
break;
|
||||
case MediaScanner::State::InProgress:
|
||||
{
|
||||
std::ostringstream oss;
|
||||
bindString("status", Wt::WString::tr("Lms.Admin.Database.Status.status-in-progress")
|
||||
.arg(status.inProgressStats->nbFiles())
|
||||
.arg(status.inProgressStats->totalFiles)
|
||||
.arg(static_cast<int>(status.inProgressStats->nbFiles() / static_cast<float>(status.inProgressStats->totalFiles ? status.inProgressStats->totalFiles : 1) * 100)));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
private:
|
||||
};
|
||||
|
||||
DatabaseSettingsView::DatabaseSettingsView()
|
||||
{
|
||||
wApp->internalPathChanged().connect(std::bind([=]
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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/>.
|
||||
*/
|
||||
|
||||
#include "DatabaseStatus.hpp"
|
||||
|
||||
#include <iomanip>
|
||||
|
||||
#include <Wt/Http/Response.h>
|
||||
#include <Wt/WDateTime.h>
|
||||
#include <Wt/WPushButton.h>
|
||||
#include <Wt/WResource.h>
|
||||
|
||||
#include "main/Service.hpp"
|
||||
#include "scanner/MediaScanner.hpp"
|
||||
#include "LmsApplication.hpp"
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
static
|
||||
std::string
|
||||
durationToString(const Wt::WDateTime& begin, const Wt::WDateTime& end)
|
||||
{
|
||||
const auto secs {std::chrono::duration_cast<std::chrono::seconds>(end.toTimePoint() - begin.toTimePoint()).count()};
|
||||
|
||||
std::ostringstream oss;
|
||||
|
||||
if (secs >= 3600)
|
||||
oss << secs/3600 << "h";
|
||||
if (secs >= 60)
|
||||
oss << std::setw(2) << std::setfill('0') << (secs % 3600) / 60 << "m";
|
||||
oss << std::setw(2) << std::setfill('0') << (secs % 60) << "s";
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
|
||||
class ReportResource : public Wt::WResource
|
||||
{
|
||||
public:
|
||||
|
||||
ReportResource(const Scanner::ScanStats& stats)
|
||||
: _stats {stats}
|
||||
{
|
||||
suggestFileName("report.txt");
|
||||
}
|
||||
|
||||
~ReportResource()
|
||||
{
|
||||
beingDeleted();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
for (const auto& error : _stats.errors)
|
||||
{
|
||||
response.out() << error.file.string() << " - " << errorTypeToWString(error.error).toUTF8();
|
||||
if (!error.systemError.empty())
|
||||
response.out() << ": " << error.systemError;
|
||||
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;
|
||||
|
||||
for (const auto& duplicate : _stats.duplicates)
|
||||
response.out() << duplicate.file.string() << " - " << duplicateReasonToWString(duplicate.reason).toUTF8() << std::endl;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
static Wt::WString errorTypeToWString(Scanner::ScanErrorType error)
|
||||
{
|
||||
switch (error)
|
||||
{
|
||||
case Scanner::ScanErrorType::CannotReadFile: return Wt::WString::tr("Lms.Admin.Database.Status.cannot-read-file");
|
||||
case Scanner::ScanErrorType::CannotParseFile: return Wt::WString::tr("Lms.Admin.Database.Status.cannot-parse-file");
|
||||
case Scanner::ScanErrorType::NoAudioTrack: return Wt::WString::tr("Lms.Admin.Database.Status.no-audio-track");
|
||||
case Scanner::ScanErrorType::BadDuration: return Wt::WString::tr("Lms.Admin.Database.Status.bad-duration");
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
static Wt::WString duplicateReasonToWString(Scanner::DuplicateReason reason)
|
||||
{
|
||||
switch (reason)
|
||||
{
|
||||
case Scanner::DuplicateReason::SameHash: return Wt::WString::tr("Lms.Admin.Database.Status.same-hash");
|
||||
case Scanner::DuplicateReason::SameMBID: return Wt::WString::tr("Lms.Admin.Database.Status.same-mbid");
|
||||
}
|
||||
return "?";
|
||||
}
|
||||
|
||||
Scanner::ScanStats _stats;
|
||||
};
|
||||
|
||||
|
||||
DatabaseStatus::DatabaseStatus()
|
||||
: WTemplate {Wt::WString::tr("Lms.Admin.Database.Status.template")}
|
||||
{
|
||||
addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||
|
||||
using namespace Scanner;
|
||||
|
||||
auto onDbEvent = [&]() { refreshContents(); };
|
||||
|
||||
LmsApp->getEvents().dbScanned.connect(this, onDbEvent);
|
||||
LmsApp->getEvents().dbScanInProgress.connect(this, onDbEvent);
|
||||
LmsApp->getEvents().dbScanScheduled.connect(this, onDbEvent);
|
||||
|
||||
refreshContents();
|
||||
}
|
||||
|
||||
void
|
||||
DatabaseStatus::refreshContents()
|
||||
{
|
||||
using namespace Scanner;
|
||||
|
||||
Wt::WPushButton* reportBtn {bindNew<Wt::WPushButton>("btn-report", Wt::WString::tr("Lms.Admin.Database.Status.get-report"))};
|
||||
|
||||
const MediaScanner::Status status {getService<MediaScanner>()->getStatus()};
|
||||
if (status.lastCompleteScanStats)
|
||||
{
|
||||
bindString("last-scan", Wt::WString::tr("Lms.Admin.Database.Status.last-scan-status")
|
||||
.arg(status.lastCompleteScanStats->nbFiles())
|
||||
.arg(durationToString(status.lastCompleteScanStats->startTime, status.lastCompleteScanStats->stopTime))
|
||||
.arg(status.lastCompleteScanStats->stopTime.toString())
|
||||
.arg(status.lastCompleteScanStats->errors.size())
|
||||
.arg(status.lastCompleteScanStats->duplicates.size())
|
||||
);
|
||||
|
||||
Wt::WLink link {std::make_shared<ReportResource>(*status.lastCompleteScanStats)};
|
||||
link.setTarget(Wt::LinkTarget::NewWindow);
|
||||
reportBtn->setLink(link);
|
||||
}
|
||||
else
|
||||
{
|
||||
bindString("last-scan", Wt::WString::tr("Lms.Admin.Database.Status.last-scan-not-available"));
|
||||
reportBtn->setEnabled(false);
|
||||
}
|
||||
|
||||
|
||||
switch (status.currentState)
|
||||
{
|
||||
case MediaScanner::State::NotScheduled:
|
||||
bindString("status", Wt::WString::tr("Lms.Admin.Database.Status.status-not-scheduled"));
|
||||
break;
|
||||
case MediaScanner::State::Scheduled:
|
||||
bindString("status", Wt::WString::tr("Lms.Admin.Database.Status.status-scheduled")
|
||||
.arg(status.nextScheduledScan.toString()));
|
||||
break;
|
||||
case MediaScanner::State::InProgress:
|
||||
{
|
||||
std::ostringstream oss;
|
||||
bindString("status", Wt::WString::tr("Lms.Admin.Database.Status.status-in-progress")
|
||||
.arg(status.inProgressScanStats->processedFiles)
|
||||
.arg(status.inProgressScanStats->filesToScan)
|
||||
.arg(status.inProgressScanStats->progress()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // namespace UserInterface
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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/WTemplate.h>
|
||||
|
||||
namespace UserInterface {
|
||||
|
||||
class DatabaseStatus : public Wt::WTemplate
|
||||
{
|
||||
public:
|
||||
DatabaseStatus();
|
||||
|
||||
private:
|
||||
void refreshContents();
|
||||
};
|
||||
|
||||
} // namespace DatabaseStatus
|
||||
|
||||
Reference in New Issue
Block a user