Files
lms/src/lms/ui/SettingsView.cpp
T

686 lines
37 KiB
C++

/*
* Copyright (C) 2018 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 "SettingsView.hpp"
#include <Wt/WCheckBox.h>
#include <Wt/WComboBox.h>
#include <Wt/WDoubleSpinBox.h>
#include <Wt/WFormModel.h>
#include <Wt/WLineEdit.h>
#include <Wt/WPushButton.h>
#include <Wt/WString.h>
#include <Wt/WTemplateFormView.h>
#include "core/IConfig.hpp"
#include "core/ILogger.hpp"
#include "core/Service.hpp"
#include "database/Session.hpp"
#include "database/User.hpp"
#include "services/auth/IAuthTokenService.hpp"
#include "services/auth/IPasswordService.hpp"
#include "LmsApplication.hpp"
#include "MediaPlayer.hpp"
#include "Tooltip.hpp"
#include "common/DoubleValidator.hpp"
#include "common/MandatoryValidator.hpp"
#include "common/PasswordValidator.hpp"
#include "common/UUIDValidator.hpp"
#include "common/ValueStringModel.hpp"
namespace lms::ui
{
using namespace db;
class SettingsModel : public Wt::WFormModel
{
public:
// Associate each field with a unique string literal.
static inline const Field ArtistReleaseSortMethodField{ "artist-release-sort-method" };
static inline const Field TranscodingModeField{ "transcoding-mode" };
static inline const Field TranscodeFormatField{ "transcoding-output-format" };
static inline const Field TranscodeBitrateField{ "transcoding-output-bitrate" };
static inline const Field ReplayGainModeField{ "replaygain-mode" };
static inline const Field ReplayGainPreAmpGainField{ "replaygain-preamp" };
static inline const Field ReplayGainPreAmpGainIfNoInfoField{ "replaygain-preamp-no-rg-info" };
static inline const Field SubsonicTokenField{ "subsonic-token" };
static inline const Field SubsonicEnableTranscodingByDefault{ "subsonic-enable-transcoding-by-default" };
static inline const Field SubsonicArtistListModeField{ "subsonic-artist-list-mode" };
static inline const Field SubsonicTranscodingOutputFormatField{ "subsonic-transcoding-output-format" };
static inline const Field SubsonicTranscodingOutputBitrateField{ "subsonic-transcoding-output-bitrate" };
static inline const Field FeedbackBackendField{ "feedback-backend" };
static inline const Field ScrobblingBackendField{ "scrobbling-backend" };
static inline const Field ListenBrainzTokenField{ "listenbrainz-token" };
static inline const Field PasswordOldField{ "password-old" };
static inline const Field PasswordField{ "password" };
static inline const Field PasswordConfirmField{ "password-confirm" };
using ArtistReleaseSortMethodModel = ValueStringModel<db::ReleaseSortMethod>;
using TranscodingModeModel = ValueStringModel<MediaPlayer::Settings::Transcoding::Mode>;
using ReplayGainModeModel = ValueStringModel<MediaPlayer::Settings::ReplayGain::Mode>;
using FeedbackBackendModel = ValueStringModel<db::FeedbackBackend>;
using ScrobblingBackendModel = ValueStringModel<db::ScrobblingBackend>;
SettingsModel(auth::IPasswordService* authPasswordService, bool withOldPassword, auth::IAuthTokenService& authTokenService)
: _authPasswordService{ authPasswordService }
, _withOldPassword{ withOldPassword }
, _authTokenService{ authTokenService }
{
initializeModels();
addField(ArtistReleaseSortMethodField);
addField(TranscodingModeField);
addField(TranscodeBitrateField);
addField(TranscodeFormatField);
addField(ReplayGainModeField);
addField(ReplayGainPreAmpGainField);
addField(ReplayGainPreAmpGainIfNoInfoField);
addField(SubsonicTokenField);
addField(SubsonicEnableTranscodingByDefault);
addField(SubsonicTranscodingOutputBitrateField);
addField(SubsonicTranscodingOutputFormatField);
addField(FeedbackBackendField);
addField(ScrobblingBackendField);
addField(ListenBrainzTokenField);
setValidator(SubsonicTokenField, createUUIDValidator());
setValidator(ListenBrainzTokenField, createUUIDValidator());
if (_authPasswordService)
{
if (_withOldPassword)
{
addField(PasswordOldField);
setValidator(PasswordOldField, createPasswordCheckValidator(*_authPasswordService));
}
addField(PasswordField);
setValidator(PasswordField, createPasswordStrengthValidator(*authPasswordService, [] { return auth::PasswordValidationContext{ .loginName = std::string{ LmsApp->getUserLoginName() }, .userType = LmsApp->getUserType() }; }));
addField(PasswordConfirmField);
}
setValidator(ArtistReleaseSortMethodField, createMandatoryValidator());
setValidator(TranscodingModeField, createMandatoryValidator());
setValidator(TranscodeBitrateField, createMandatoryValidator());
setValidator(TranscodeFormatField, createMandatoryValidator());
setValidator(ReplayGainModeField, createMandatoryValidator());
auto createPreAmpValidator{ [] {
return createDoubleValidator(MediaPlayer::Settings::ReplayGain::minPreAmpGain, MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
} };
setValidator(ReplayGainPreAmpGainField, createPreAmpValidator());
setValidator(ReplayGainPreAmpGainIfNoInfoField, createPreAmpValidator());
setValidator(SubsonicTranscodingOutputBitrateField, createMandatoryValidator());
setValidator(SubsonicTranscodingOutputFormatField, createMandatoryValidator());
loadData();
}
std::shared_ptr<ArtistReleaseSortMethodModel> getArtistReleaseSortMethodModel() { return _artistReleaseSortMethodModel; }
std::shared_ptr<TranscodingModeModel> getTranscodingModeModel() { return _transcodingModeModeModel; }
std::shared_ptr<Wt::WAbstractItemModel> getTranscodingOutputBitrateModel() { return _transcodingOutputBitrateModel; }
std::shared_ptr<Wt::WAbstractItemModel> getTranscodingOutputFormatModel() { return _transcodingOutputFormatModel; }
std::shared_ptr<ReplayGainModeModel> getReplayGainModeModel() { return _replayGainModeModel; }
std::shared_ptr<Wt::WAbstractItemModel> getSubsonicArtistListModeModel() { return _subsonicArtistListModeModel; }
std::shared_ptr<FeedbackBackendModel> getFeedbackBackendModel() { return _feedbackBackendModel; }
std::shared_ptr<ScrobblingBackendModel> getScrobblingBackendModel() { return _scrobblingBackendModel; }
void saveData()
{
auto transaction{ LmsApp->getDbSession().createWriteTransaction() };
User::pointer user{ LmsApp->getUser() };
{
auto artistReleaseSortMethodRow{ _artistReleaseSortMethodModel->getRowFromString(valueText(ArtistReleaseSortMethodField)) };
if (artistReleaseSortMethodRow)
user.modify()->setUIArtistReleaseSortMethod(_artistReleaseSortMethodModel->getValue(*artistReleaseSortMethodRow));
}
{
MediaPlayer::Settings settings;
auto transcodingModeRow{ _transcodingModeModeModel->getRowFromString(valueText(TranscodingModeField)) };
if (transcodingModeRow)
settings.transcoding.mode = _transcodingModeModeModel->getValue(*transcodingModeRow);
auto transcodingOutputFormatRow{ _transcodingOutputFormatModel->getRowFromString(valueText(TranscodeFormatField)) };
if (transcodingOutputFormatRow)
settings.transcoding.format = _transcodingOutputFormatModel->getValue(*transcodingOutputFormatRow);
auto transcodingOutputBitrateRow{ _transcodingOutputBitrateModel->getRowFromString(valueText(TranscodeBitrateField)) };
if (transcodingOutputBitrateRow)
settings.transcoding.bitrate = _transcodingOutputBitrateModel->getValue(*transcodingOutputBitrateRow);
auto replayGainModeRow{ _replayGainModeModel->getRowFromString(valueText(ReplayGainModeField)) };
if (replayGainModeRow)
settings.replayGain.mode = _replayGainModeModel->getValue(*replayGainModeRow);
settings.replayGain.preAmpGain = Wt::asNumber(value(ReplayGainPreAmpGainField));
settings.replayGain.preAmpGainIfNoInfo = Wt::asNumber(value(ReplayGainPreAmpGainIfNoInfoField));
LmsApp->getMediaPlayer().setSettings(settings);
}
// Subsonic API
{
const std::string token{ Wt::asString(value(SubsonicTokenField)).toUTF8() };
if (token.empty())
{
_authTokenService.clearAuthTokens("subsonic", user->getId());
}
else
{
// Consider there must be only one token
bool hasNonMatchingToken{ false };
bool hasMatchingToken{ false };
_authTokenService.visitAuthTokens("subsonic", user->getId(), [&](const auth::IAuthTokenService::AuthTokenInfo&, std::string_view storedToken) {
if (storedToken == token)
hasMatchingToken = true;
else
hasNonMatchingToken = true;
});
if (!hasMatchingToken || hasNonMatchingToken)
{
_authTokenService.clearAuthTokens("subsonic", user->getId());
_authTokenService.createAuthToken("subsonic", user->getId(), token);
}
}
bool subsonicEnableTranscodingByDefault{ Wt::asNumber(value(SubsonicEnableTranscodingByDefault)) != 0 };
user.modify()->setSubsonicEnableTranscodingByDefault(subsonicEnableTranscodingByDefault);
auto subsonicTranscodingOutputBitrateRow{ _transcodingOutputBitrateModel->getRowFromString(valueText(SubsonicTranscodingOutputBitrateField)) };
if (subsonicTranscodingOutputBitrateRow)
user.modify()->setSubsonicDefaultTranscodingOutputBitrate(_transcodingOutputBitrateModel->getValue(*subsonicTranscodingOutputBitrateRow));
auto subsonicTranscodingOutputFormatRow{ _transcodingOutputFormatModel->getRowFromString(valueText(SubsonicTranscodingOutputFormatField)) };
if (subsonicTranscodingOutputFormatRow)
user.modify()->setSubsonicDefaultTranscodintOutputFormat(_transcodingOutputFormatModel->getValue(*subsonicTranscodingOutputFormatRow));
auto subsonicArtistListModeRow{ _subsonicArtistListModeModel->getRowFromString(valueText(SubsonicArtistListModeField)) };
if (subsonicArtistListModeRow)
user.modify()->setSubsonicArtistListMode(_subsonicArtistListModeModel->getValue(*subsonicArtistListModeRow));
}
{
if (auto feedbackBackendRow{ _feedbackBackendModel->getRowFromString(valueText(FeedbackBackendField)) })
user.modify()->setFeedbackBackend(_feedbackBackendModel->getValue(*feedbackBackendRow));
user.modify()->setListenBrainzToken(core::UUID::fromString(Wt::asString(value(ListenBrainzTokenField)).toUTF8()));
}
{
if (auto scrobblingBackendRow{ _scrobblingBackendModel->getRowFromString(valueText(ScrobblingBackendField)) })
user.modify()->setScrobblingBackend(_scrobblingBackendModel->getValue(*scrobblingBackendRow));
user.modify()->setListenBrainzToken(core::UUID::fromString(Wt::asString(value(ListenBrainzTokenField)).toUTF8()));
}
if (_authPasswordService && !valueText(PasswordField).empty())
{
_authPasswordService->setPassword(user->getId(), valueText(PasswordField).toUTF8());
_authTokenService.clearAuthTokens("ui", user->getId());
}
}
void loadData()
{
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
const User::pointer user{ LmsApp->getUser() };
{
auto artistReleaseSortMethodRow{ _artistReleaseSortMethodModel->getRowFromValue(user->getUIArtistReleaseSortMethod()) };
if (artistReleaseSortMethodRow)
setValue(ArtistReleaseSortMethodField, _artistReleaseSortMethodModel->getString(*artistReleaseSortMethodRow));
}
{
const auto settings{ *LmsApp->getMediaPlayer().getSettings() };
auto transcodingModeRow{ _transcodingModeModeModel->getRowFromValue(settings.transcoding.mode) };
if (transcodingModeRow)
setValue(TranscodingModeField, _transcodingModeModeModel->getString(*transcodingModeRow));
auto transcodingOutputFormatRow{ _transcodingOutputFormatModel->getRowFromValue(settings.transcoding.format) };
if (transcodingOutputFormatRow)
setValue(TranscodeFormatField, _transcodingOutputFormatModel->getString(*transcodingOutputFormatRow));
auto transcodingOutputBitrateRow{ _transcodingOutputBitrateModel->getRowFromValue(settings.transcoding.bitrate) };
if (transcodingOutputBitrateRow)
setValue(TranscodeBitrateField, _transcodingOutputBitrateModel->getString(*transcodingOutputBitrateRow));
{
const bool useTranscoding{ settings.transcoding.mode != MediaPlayer::Settings::Transcoding::Mode::Never };
setReadOnly(SettingsModel::TranscodeFormatField, !useTranscoding);
setReadOnly(SettingsModel::TranscodeBitrateField, !useTranscoding);
}
auto replayGainModeRow{ _replayGainModeModel->getRowFromValue(settings.replayGain.mode) };
if (replayGainModeRow)
setValue(ReplayGainModeField, _replayGainModeModel->getString(*replayGainModeRow));
setValue(ReplayGainPreAmpGainField, settings.replayGain.preAmpGain);
setValue(ReplayGainPreAmpGainIfNoInfoField, settings.replayGain.preAmpGainIfNoInfo);
}
// Subsonic
{
// Consider there is only one auth token
_authTokenService.visitAuthTokens("subsonic", user->getId(), [&](const auth::IAuthTokenService::AuthTokenInfo&, std::string_view storedToken) {
if (Wt::asString(value(SubsonicTokenField)).empty())
setValue(SubsonicTokenField, Wt::WString::fromUTF8(std::string{ storedToken }));
});
setValue(SubsonicEnableTranscodingByDefault, user->getSubsonicEnableTranscodingByDefault());
auto subsonicTranscodingOutputBitrateRow{ _transcodingOutputBitrateModel->getRowFromValue(user->getSubsonicDefaultTranscodingOutputBitrate()) };
if (subsonicTranscodingOutputBitrateRow)
setValue(SubsonicTranscodingOutputBitrateField, _transcodingOutputBitrateModel->getString(*subsonicTranscodingOutputBitrateRow));
auto subsonicTranscodingOutputFormatRow{ _transcodingOutputFormatModel->getRowFromValue(user->getSubsonicDefaultTranscodingOutputFormat()) };
if (subsonicTranscodingOutputFormatRow)
setValue(SubsonicTranscodingOutputFormatField, _transcodingOutputFormatModel->getString(*subsonicTranscodingOutputFormatRow));
auto subsonicArtistListModeRow{ _subsonicArtistListModeModel->getRowFromValue(user->getSubsonicArtistListMode()) };
if (subsonicArtistListModeRow)
setValue(SubsonicArtistListModeField, _subsonicArtistListModeModel->getString(*subsonicArtistListModeRow));
}
{
if (auto feedbackBackendRow{ _feedbackBackendModel->getRowFromValue(user->getFeedbackBackend()) })
setValue(FeedbackBackendField, _feedbackBackendModel->getString(*feedbackBackendRow));
if (auto scrobblingBackendRow{ _scrobblingBackendModel->getRowFromValue(user->getScrobblingBackend()) })
setValue(ScrobblingBackendField, _scrobblingBackendModel->getString(*scrobblingBackendRow));
if (auto listenBrainzToken{ user->getListenBrainzToken() })
setValue(ListenBrainzTokenField, Wt::WString::fromUTF8(std::string{ listenBrainzToken->getAsString() }));
{
const bool usesListenBrainz{ user->getScrobblingBackend() == ScrobblingBackend::ListenBrainz || user->getFeedbackBackend() == FeedbackBackend::ListenBrainz };
setReadOnly(SettingsModel::ListenBrainzTokenField, !usesListenBrainz);
validator(SettingsModel::ListenBrainzTokenField)->setMandatory(usesListenBrainz);
}
}
if (_authPasswordService)
{
if (_withOldPassword)
setValue(PasswordOldField, "");
setValue(PasswordField, "");
setValue(PasswordConfirmField, "");
}
}
private:
bool validateField(Field field)
{
Wt::WString error;
if (field == PasswordOldField)
{
if (valueText(PasswordOldField).empty() && !valueText(PasswordField).empty())
error = Wt::WString::tr("Lms.Settings.password-must-fill-old-password");
else
return Wt::WFormModel::validateField(field);
}
else if (field == PasswordField)
{
if (!valueText(PasswordOldField).empty() && valueText(PasswordField).empty())
error = Wt::WString::tr("Wt.WValidator.Invalid");
else
return Wt::WFormModel::validateField(field);
}
else if (field == PasswordConfirmField)
{
if (validation(PasswordField).state() == Wt::ValidationState::Valid)
{
if (valueText(PasswordField) != valueText(PasswordConfirmField))
error = Wt::WString::tr("Lms.passwords-dont-match");
}
}
else
{
return Wt::WFormModel::validateField(field);
}
setValidation(field, Wt::WValidator::Result(error.empty() ? Wt::ValidationState::Valid : Wt::ValidationState::Invalid, error));
return (validation(field).state() == Wt::ValidationState::Valid);
}
private:
void initializeModels()
{
_artistReleaseSortMethodModel = std::make_shared<ArtistReleaseSortMethodModel>();
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.date-asc"), db::ReleaseSortMethod::DateAsc);
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.date-desc"), db::ReleaseSortMethod::DateDesc);
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.original-date-asc"), db::ReleaseSortMethod::OriginalDate);
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.original-date-desc"), db::ReleaseSortMethod::OriginalDateDesc);
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.name"), db::ReleaseSortMethod::Name);
_transcodingModeModeModel = std::make_shared<TranscodingModeModel>();
_transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcoding-mode.always"), MediaPlayer::Settings::Transcoding::Mode::Always);
_transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcoding-mode.never"), MediaPlayer::Settings::Transcoding::Mode::Never);
_transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcoding-mode.if-format-not-supported"), MediaPlayer::Settings::Transcoding::Mode::IfFormatNotSupported);
_transcodingOutputBitrateModel = std::make_shared<ValueStringModel<Bitrate>>();
visitAllowedAudioBitrates([&](const Bitrate bitrate) {
_transcodingOutputBitrateModel->add(Wt::WString::fromUTF8(std::to_string(bitrate / 1000)), bitrate);
});
_transcodingOutputFormatModel = std::make_shared<ValueStringModel<TranscodingOutputFormat>>();
_transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.mp3"), TranscodingOutputFormat::MP3);
_transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.ogg_opus"), TranscodingOutputFormat::OGG_OPUS);
_transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.matroska_opus"), TranscodingOutputFormat::MATROSKA_OPUS);
_transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.ogg_vorbis"), TranscodingOutputFormat::OGG_VORBIS);
_transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.webm_vorbis"), TranscodingOutputFormat::WEBM_VORBIS);
_replayGainModeModel = std::make_shared<ReplayGainModeModel>();
_replayGainModeModel->add(Wt::WString::tr("Lms.Settings.replaygain-mode.none"), MediaPlayer::Settings::ReplayGain::Mode::None);
_replayGainModeModel->add(Wt::WString::tr("Lms.Settings.replaygain-mode.auto"), MediaPlayer::Settings::ReplayGain::Mode::Auto);
_replayGainModeModel->add(Wt::WString::tr("Lms.Settings.replaygain-mode.track"), MediaPlayer::Settings::ReplayGain::Mode::Track);
_replayGainModeModel->add(Wt::WString::tr("Lms.Settings.replaygain-mode.release"), MediaPlayer::Settings::ReplayGain::Mode::Release);
_subsonicArtistListModeModel = std::make_shared<ValueStringModel<SubsonicArtistListMode>>();
_subsonicArtistListModeModel->add(Wt::WString::tr("Lms.Settings.subsonic-artist-list-mode.all-artists"), SubsonicArtistListMode::AllArtists);
_subsonicArtistListModeModel->add(Wt::WString::tr("Lms.Settings.subsonic-artist-list-mode.release-artists"), SubsonicArtistListMode::ReleaseArtists);
_subsonicArtistListModeModel->add(Wt::WString::tr("Lms.Settings.subsonic-artist-list-mode.track-artists"), SubsonicArtistListMode::TrackArtists);
_feedbackBackendModel = std::make_shared<ValueStringModel<FeedbackBackend>>();
_feedbackBackendModel->add(Wt::WString::tr("Lms.Settings.backend.internal"), FeedbackBackend::Internal);
_feedbackBackendModel->add(Wt::WString::tr("Lms.Settings.backend.listenbrainz"), FeedbackBackend::ListenBrainz);
_scrobblingBackendModel = std::make_shared<ValueStringModel<ScrobblingBackend>>();
_scrobblingBackendModel->add(Wt::WString::tr("Lms.Settings.backend.internal"), ScrobblingBackend::Internal);
_scrobblingBackendModel->add(Wt::WString::tr("Lms.Settings.backend.listenbrainz"), ScrobblingBackend::ListenBrainz);
}
auth::IPasswordService* _authPasswordService{};
bool _withOldPassword{};
auth::IAuthTokenService& _authTokenService;
std::shared_ptr<ArtistReleaseSortMethodModel> _artistReleaseSortMethodModel;
std::shared_ptr<TranscodingModeModel> _transcodingModeModeModel;
std::shared_ptr<ValueStringModel<Bitrate>> _transcodingOutputBitrateModel;
std::shared_ptr<ValueStringModel<TranscodingOutputFormat>> _transcodingOutputFormatModel;
std::shared_ptr<ReplayGainModeModel> _replayGainModeModel;
std::shared_ptr<ValueStringModel<SubsonicArtistListMode>> _subsonicArtistListModeModel;
std::shared_ptr<FeedbackBackendModel> _feedbackBackendModel;
std::shared_ptr<ScrobblingBackendModel> _scrobblingBackendModel;
};
SettingsView::SettingsView()
{
wApp->internalPathChanged().connect(this, [this] {
refreshView();
});
LmsApp->getMediaPlayer().settingsLoaded.connect([this] {
refreshView();
});
refreshView();
}
void SettingsView::refreshView()
{
if (!wApp->internalPathMatches("/settings"))
return;
clear();
// Hack to wait for the audio player know the settings applied
if (!LmsApp->getMediaPlayer().getSettings())
return;
auto t{ addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Settings.template")) };
auth::IPasswordService* authPasswordService{};
if (LmsApp->getAuthBackend() == AuthenticationBackend::Internal)
{
authPasswordService = core::Service<auth::IPasswordService>::get();
assert(authPasswordService->canSetPasswords());
}
auto model{ std::make_shared<SettingsModel>(authPasswordService, !LmsApp->isUserAuthStrong(), *core::Service<auth::IAuthTokenService>::get()) };
if (authPasswordService)
{
t->setCondition("if-has-change-password", true);
// Old password
if (!LmsApp->isUserAuthStrong())
{
t->setCondition("if-has-old-password", true);
auto oldPassword{ std::make_unique<Wt::WLineEdit>() };
oldPassword->setEchoMode(Wt::EchoMode::Password);
oldPassword->setAttributeValue("autocomplete", "current-password");
t->setFormWidget(SettingsModel::PasswordOldField, std::move(oldPassword));
}
// Password
auto password{ std::make_unique<Wt::WLineEdit>() };
password->setEchoMode(Wt::EchoMode::Password);
password->setAttributeValue("autocomplete", "new-password");
t->setFormWidget(SettingsModel::PasswordField, std::move(password));
// Password confirm
auto passwordConfirm{ std::make_unique<Wt::WLineEdit>() };
passwordConfirm->setEchoMode(Wt::EchoMode::Password);
passwordConfirm->setAttributeValue("autocomplete", "new-password");
t->setFormWidget(SettingsModel::PasswordConfirmField, std::move(passwordConfirm));
}
// User interface
{
auto artistReleaseSortMethod{ std::make_unique<Wt::WComboBox>() };
artistReleaseSortMethod->setModel(model->getArtistReleaseSortMethodModel());
t->setFormWidget(SettingsModel::ArtistReleaseSortMethodField, std::move(artistReleaseSortMethod));
}
// Audio
{
// Transcode
auto transcodingMode{ std::make_unique<Wt::WComboBox>() };
transcodingMode->setModel(model->getTranscodingModeModel());
transcodingMode->activated().connect([=](int row) {
const bool enable{ model->getTranscodingModeModel()->getValue(row) != MediaPlayer::Settings::Transcoding::Mode::Never };
model->setReadOnly(SettingsModel::TranscodeFormatField, !enable);
model->setReadOnly(SettingsModel::TranscodeBitrateField, !enable);
t->updateModel(model.get());
t->updateView(model.get());
});
t->setFormWidget(SettingsModel::TranscodingModeField, std::move(transcodingMode));
// Format
auto transcodingOutputFormat{ std::make_unique<Wt::WComboBox>() };
transcodingOutputFormat->setModel(model->getTranscodingOutputFormatModel());
t->setFormWidget(SettingsModel::TranscodeFormatField, std::move(transcodingOutputFormat));
// Bitrate
auto transcodingOutputBitrate{ std::make_unique<Wt::WComboBox>() };
transcodingOutputBitrate->setModel(model->getTranscodingOutputBitrateModel());
t->setFormWidget(SettingsModel::TranscodeBitrateField, std::move(transcodingOutputBitrate));
// Replay gain mode
auto replayGainMode{ std::make_unique<Wt::WComboBox>() };
replayGainMode->activated().connect([=](int row) {
const bool enable{ model->getReplayGainModeModel()->getValue(row) != MediaPlayer::Settings::ReplayGain::Mode::None };
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, !enable);
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainIfNoInfoField, !enable);
t->updateModel(model.get());
t->updateView(model.get());
});
replayGainMode->setModel(model->getReplayGainModeModel());
t->setFormWidget(SettingsModel::ReplayGainModeField, std::move(replayGainMode));
// Replay gain preampGain
auto replayGainPreampGain{ std::make_unique<Wt::WDoubleSpinBox>() };
replayGainPreampGain->setRange(MediaPlayer::Settings::ReplayGain::minPreAmpGain, MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
t->setFormWidget(SettingsModel::ReplayGainPreAmpGainField, std::move(replayGainPreampGain));
// Replay gain preampGain if no info
auto replayGainPreampGainIfNoInfo{ std::make_unique<Wt::WDoubleSpinBox>() };
replayGainPreampGainIfNoInfo->setRange(MediaPlayer::Settings::ReplayGain::minPreAmpGain, MediaPlayer::Settings::ReplayGain::maxPreAmpGain);
t->setFormWidget(SettingsModel::ReplayGainPreAmpGainIfNoInfoField, std::move(replayGainPreampGainIfNoInfo));
if (LmsApp->getMediaPlayer().getSettings()->replayGain.mode == MediaPlayer::Settings::ReplayGain::Mode::None)
{
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, true);
model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainIfNoInfoField, true);
}
}
// Subsonic
{
t->setCondition("if-has-subsonic-api", core::Service<core::IConfig>::get()->getBool("api-subsonic", true));
t->setCondition("if-has-subsonic-token-usage", core::Service<core::IConfig>::get()->getBool("api-subsonic-support-user-password-auth", true));
auto subsonicToken{ std::make_unique<Wt::WLineEdit>() };
Wt::WLineEdit* subsonicTokenPtr{ subsonicToken.get() };
subsonicTokenPtr->setEchoMode(Wt::EchoMode::Password);
subsonicTokenPtr->setReadOnly(true);
t->setFormWidget(SettingsModel::SubsonicTokenField, std::move(subsonicToken));
auto subsonicTokenRegenBtn{ std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.Settings.regen-token")) };
subsonicTokenRegenBtn->clicked().connect(this, [subsonicTokenPtr] {
subsonicTokenPtr->setValueText(Wt::WString::fromUTF8(std::string{ core::UUID::generate().getAsString() }));
});
t->bindWidget("subsonic-token-regen-btn", std::move(subsonicTokenRegenBtn));
auto subsonicTokenVisibilityBtn{ std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.template.toggle-visibility-btn"), Wt::TextFormat::XHTML) };
subsonicTokenVisibilityBtn->clicked().connect(this, [subsonicTokenPtr] {
subsonicTokenPtr->setEchoMode(subsonicTokenPtr->echoMode() == Wt::EchoMode::Password ? Wt::EchoMode::Normal : Wt::EchoMode::Password);
});
t->bindWidget("subsonic-token-visibility-btn", std::move(subsonicTokenVisibilityBtn));
auto subsonicTokenDelBtn{ std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.template.trash-btn"), Wt::TextFormat::XHTML) };
subsonicTokenDelBtn->clicked().connect(this, [subsonicTokenPtr] {
subsonicTokenPtr->setValueText("");
});
t->bindWidget("subsonic-token-del-btn", std::move(subsonicTokenDelBtn));
// Enable transcoding by default
t->setFormWidget(SettingsModel::SubsonicEnableTranscodingByDefault, std::make_unique<Wt::WCheckBox>());
// Format
auto transcodingOutputFormat{ std::make_unique<Wt::WComboBox>() };
transcodingOutputFormat->setModel(model->getTranscodingOutputFormatModel());
t->setFormWidget(SettingsModel::SubsonicTranscodingOutputFormatField, std::move(transcodingOutputFormat));
// Bitrate
auto transcodingOutputBitrate{ std::make_unique<Wt::WComboBox>() };
transcodingOutputBitrate->setModel(model->getTranscodingOutputBitrateModel());
t->setFormWidget(SettingsModel::SubsonicTranscodingOutputBitrateField, std::move(transcodingOutputBitrate));
// Artist list mode
auto artistListMode{ std::make_unique<Wt::WComboBox>() };
artistListMode->setModel(model->getSubsonicArtistListModeModel());
t->setFormWidget(SettingsModel::SubsonicArtistListModeField, std::move(artistListMode));
}
// Feedback
Wt::WComboBox* feedbackBackendRaw{};
{
auto feedbackBackend{ std::make_unique<Wt::WComboBox>() };
feedbackBackend->setModel(model->getFeedbackBackendModel());
feedbackBackendRaw = feedbackBackend.get();
t->setFormWidget(SettingsModel::FeedbackBackendField, std::move(feedbackBackend));
}
// Scrobbling
Wt::WComboBox* scrobblingBackendRaw;
{
auto scrobblingBackend{ std::make_unique<Wt::WComboBox>() };
scrobblingBackend->setModel(model->getScrobblingBackendModel());
scrobblingBackendRaw = scrobblingBackend.get();
t->setFormWidget(SettingsModel::ScrobblingBackendField, std::move(scrobblingBackend));
}
// Backend settings
{
auto listenbrainzToken{ std::make_unique<Wt::WLineEdit>() };
Wt::WLineEdit* listenbrainzTokenPtr{ listenbrainzToken.get() };
listenbrainzTokenPtr->setEchoMode(Wt::EchoMode::Password);
t->setFormWidget(SettingsModel::ListenBrainzTokenField, std::move(listenbrainzToken));
auto listenbrainzTokenVisibilityBtn{ std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.template.toggle-visibility-btn"), Wt::TextFormat::XHTML) };
listenbrainzTokenVisibilityBtn->clicked().connect(this, [listenbrainzTokenPtr] {
listenbrainzTokenPtr->setEchoMode(listenbrainzTokenPtr->echoMode() == Wt::EchoMode::Password ? Wt::EchoMode::Normal : Wt::EchoMode::Password);
});
t->bindWidget("listenbrainz-token-visibility-btn", std::move(listenbrainzTokenVisibilityBtn));
}
auto updateListenBrainzTokenField{ [=] {
const bool enable{ model->getFeedbackBackendModel()->getValue(feedbackBackendRaw->currentIndex()) == FeedbackBackend::ListenBrainz
|| model->getScrobblingBackendModel()->getValue(scrobblingBackendRaw->currentIndex()) == ScrobblingBackend::ListenBrainz };
model->setReadOnly(SettingsModel::ListenBrainzTokenField, !enable);
model->validator(SettingsModel::ListenBrainzTokenField)->setMandatory(enable);
t->updateModel(model.get());
t->updateView(model.get());
} };
feedbackBackendRaw->activated().connect([=] { updateListenBrainzTokenField(); });
scrobblingBackendRaw->activated().connect([=] { updateListenBrainzTokenField(); });
// Buttons
Wt::WPushButton* saveBtn{ t->bindWidget("save-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.save"))) };
Wt::WPushButton* discardBtn{ t->bindWidget("discard-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.discard"))) };
saveBtn->clicked().connect([=] {
{
if (LmsApp->getUserType() == db::UserType::DEMO)
{
LmsApp->notifyMsg(Notification::Type::Warning, Wt::WString::tr("Lms.Settings.settings"), Wt::WString::tr("Lms.Settings.demo-cannot-save"));
return;
}
}
t->updateModel(model.get());
if (model->validate())
{
model->saveData();
LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Settings.settings"), Wt::WString::tr("Lms.Settings.settings-saved"));
}
// Udate the view: Delete any validation message in the view, etc.
t->updateView(model.get());
});
discardBtn->clicked().connect([=] {
model->loadData();
model->validate();
t->updateView(model.get());
});
t->updateView(model.get());
initTooltipsForWidgetTree(*t);
}
} // namespace lms::ui