From 1cdb69e4eb38d2d185b396926d8e4231267caffd Mon Sep 17 00:00:00 2001 From: emeric Date: Fri, 1 Aug 2014 00:05:44 +0200 Subject: [PATCH] WIP. Added Account and Audio forms --- Makefile.am | 3 + database/DatabaseHandler.cpp | 9 +- database/User.cpp | 94 ++++++++ database/User.hpp | 21 +- ui/approot/templates.xml | 87 +++++++- ui/audio/AudioWidget.cpp | 17 +- ui/audio/AudioWidget.hpp | 2 +- ui/common/EmailValidator.hpp | 10 - ui/common/Validators.hpp | 23 ++ ui/settings/Settings.cpp | 13 +- ui/settings/SettingsAccountFormView.cpp | 266 +++++++++++++++++++++++ ui/settings/SettingsAccountFormView.hpp | 32 +++ ui/settings/SettingsAudioFormView.cpp | 178 +++++++++++++++ ui/settings/SettingsAudioFormView.hpp | 32 +++ ui/settings/SettingsDatabaseFormView.cpp | 33 +-- ui/settings/SettingsUserFormView.cpp | 161 +++++--------- ui/settings/SettingsUserFormView.hpp | 3 +- ui/settings/SettingsUsers.cpp | 6 +- 18 files changed, 830 insertions(+), 160 deletions(-) delete mode 100644 ui/common/EmailValidator.hpp create mode 100644 ui/common/Validators.hpp create mode 100644 ui/settings/SettingsAccountFormView.cpp create mode 100644 ui/settings/SettingsAccountFormView.hpp create mode 100644 ui/settings/SettingsAudioFormView.cpp create mode 100644 ui/settings/SettingsAudioFormView.hpp diff --git a/Makefile.am b/Makefile.am index c94131c4..61091eef 100644 --- a/Makefile.am +++ b/Makefile.am @@ -66,7 +66,10 @@ lms_SOURCES = \ $(top_srcdir)/ui/video/VideoMediaPlayerWidget.cpp \ $(top_srcdir)/ui/video/VideoParametersDialog.cpp \ $(top_srcdir)/ui/settings/Settings.cpp \ + $(top_srcdir)/ui/settings/SettingsAccountFormView.cpp \ + $(top_srcdir)/ui/settings/SettingsAudioFormView.cpp \ $(top_srcdir)/ui/settings/SettingsDatabaseFormView.cpp \ + $(top_srcdir)/ui/settings/SettingsUserFormView.cpp \ $(top_srcdir)/ui/settings/SettingsUsers.cpp diff --git a/database/DatabaseHandler.cpp b/database/DatabaseHandler.cpp index 693119af..d6bf283a 100644 --- a/database/DatabaseHandler.cpp +++ b/database/DatabaseHandler.cpp @@ -32,7 +32,14 @@ Handler::configureAuth(void) verifier->addHashFunction(new Wt::Auth::BCryptHashFunction(8)); passwordService.setVerifier(verifier); passwordService.setAttemptThrottlingEnabled(true); - passwordService.setStrengthValidator(new Wt::Auth::PasswordStrengthValidator()); + + Wt::Auth::PasswordStrengthValidator* strengthValidator = new Wt::Auth::PasswordStrengthValidator(); + // Reduce some constraints... + strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::TwoCharClass, 11); + strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::ThreeCharClass, 8 ); + strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::FourCharClass, 6 ); + + passwordService.setStrengthValidator(strengthValidator); } const Wt::Auth::AuthService& diff --git a/database/User.cpp b/database/User.cpp index a1697f8a..25f202ba 100644 --- a/database/User.cpp +++ b/database/User.cpp @@ -4,6 +4,33 @@ namespace Database { + +// must be ordered +const std::vector + User::audioBitrates = +{ + 64000, + 96000, + 128000, + 160000, + 192000, + 224000, + 256000, + 320000, + 512000 +}; + + +const std::vector + User::videoBitrates = +{ + 256000, + 512000, + 1024000, + 2048000, + 4096000, + 8192000 +}; User::User() : _maxAudioBitrate(maxAudioBitrate), _maxVideoBitrate(maxVideoBitrate), @@ -27,6 +54,73 @@ User::getById(Wt::Dbo::Session& session, std::string id) return session.find().where("id = ?").bind( id ); } +std::string +User::getId( pointer user) +{ + std::ostringstream oss; oss << user.id(); + return oss.str(); +} + +void +User::setAudioBitrate(std::size_t bitrate) +{ + _audioBitrate = std::min(bitrate, std::min(static_cast(_maxAudioBitrate), audioBitrates.back())); +} + +void +User::setVideoBitrate(std::size_t bitrate) +{ + _videoBitrate = std::min(bitrate, std::min(static_cast(_maxVideoBitrate), videoBitrates.back())); +} + +void +User::setMaxAudioBitrate(std::size_t bitrate) +{ + _maxAudioBitrate = std::min(bitrate, static_cast(_maxAudioBitrate)); +} + +void +User::setMaxVideoBitrate(std::size_t bitrate) +{ + _maxVideoBitrate = std::min(bitrate, static_cast(_maxVideoBitrate)); +} + +std::size_t +User::getAudioBitrate(void) const +{ + if (!isAdmin()) + return std::min(static_cast(_audioBitrate), std::min(static_cast(_maxAudioBitrate), audioBitrates.back())); + else + return std::min(static_cast(_audioBitrate), audioBitrates.back()); +} + +std::size_t +User::getVideoBitrate(void) const +{ + if (!isAdmin()) + return std::min(static_cast(_videoBitrate), std::min(static_cast(_maxVideoBitrate), videoBitrates.back())); + else + return std::min(static_cast(_videoBitrate), videoBitrates.back()); +} + +std::size_t +User::getMaxAudioBitrate(void) const +{ + if (!isAdmin()) + return std::min(static_cast(_maxAudioBitrate), audioBitrates.back()); + else + return audioBitrates.back(); +} + +std::size_t +User::getMaxVideoBitrate(void) const +{ + if (!isAdmin()) + return std::min(static_cast(_maxVideoBitrate), videoBitrates.back()); + else + return videoBitrates.back(); +} + } // namespace Database diff --git a/database/User.hpp b/database/User.hpp index 10a167bf..ab5a5e85 100644 --- a/database/User.hpp +++ b/database/User.hpp @@ -14,6 +14,10 @@ class User { public: static const std::size_t MaxNameLength = 15; + // list of commonly used bitrates + static const std::vector audioBitrates; + static const std::vector videoBitrates; + User(); typedef Wt::Dbo::ptr pointer; @@ -21,16 +25,21 @@ class User { // accessors static pointer getById(Wt::Dbo::Session& session, std::string id); static std::vector getAll(Wt::Dbo::Session& session); + static std::string getId(pointer user); // write void setAdmin(bool admin) { _isAdmin = admin; } - void setMaxAudioBitrate(std::size_t bitrate) { _maxAudioBitrate = bitrate; } - void setMaxVideoBitrate(std::size_t bitrate) { _maxVideoBitrate = bitrate; } + void setAudioBitrate(std::size_t bitrate); + void setVideoBitrate(std::size_t bitrate); + void setMaxAudioBitrate(std::size_t bitrate); + void setMaxVideoBitrate(std::size_t bitrate); // read bool isAdmin() const {return _isAdmin;} - std::size_t getMaxAudioBitrate() const { return _maxAudioBitrate; } - std::size_t getMaxVideoBitrate() const { return _maxVideoBitrate; } + std::size_t getAudioBitrate() const; + std::size_t getVideoBitrate() const; + std::size_t getMaxAudioBitrate() const; + std::size_t getMaxVideoBitrate() const; template void persist(Action& a) @@ -45,10 +54,10 @@ class User { private: static const std::size_t maxAudioBitrate = 320000; - static const std::size_t maxVideoBitrate = 7500000; + static const std::size_t maxVideoBitrate = 2048000; static const std::size_t defaultAudioBitrate = 128000; - static const std::size_t defaultVideoBitrate = 1500000; + static const std::size_t defaultVideoBitrate = 1024000; // Admin defined settings int _maxAudioBitrate; diff --git a/ui/approot/templates.xml b/ui/approot/templates.xml index 160ea844..96fd2262 100644 --- a/ui/approot/templates.xml +++ b/ui/approot/templates.xml @@ -50,7 +50,7 @@ - Access + ${access}
+ +
${save-button} ${cancel-button} @@ -100,6 +102,88 @@ + + ${title} +
+
+ +
+ ${name} +
+
+ ${name-info} +
+
+
+ +
+ ${email} +
+
+ ${email-info} +
+
+
+ +
+ ${password} +
+
+ ${password-info} +
+
+
+ +
+ ${password-confirm} +
+
+ ${password-confirm-info} +
+
+
+
+ ${save-button} ${cancel-button} +
+
+ ${apply-info} +
+
+ + + ${title} +
+
+ +
+
+ ${bitrate} + kbps +
+
+
+ ${bitrate-info} +
+
+
+
+ ${save-button} ${cancel-button} +
+
+ ${apply-info} +
+
+ ${title}
@@ -150,7 +234,6 @@ ${update-request-immediate-scan-info}
-
${apply-button} ${discard-button} diff --git a/ui/audio/AudioWidget.cpp b/ui/audio/AudioWidget.cpp index e717b91b..8e76aaff 100644 --- a/ui/audio/AudioWidget.cpp +++ b/ui/audio/AudioWidget.cpp @@ -6,7 +6,7 @@ namespace UserInterface { AudioWidget::AudioWidget(SessionData& sessionData, Wt::WContainerWidget* parent) : Wt::WContainerWidget(parent), -_sessionData(sessionData), +_db(sessionData.getDatabaseHandler()), _audioDbWidget(nullptr), _mediaPlayer(nullptr), _imgResource(nullptr), @@ -39,11 +39,22 @@ AudioWidget::playTrack(boost::filesystem::path p) { std::cout << "play track '" << p << "'" << std::endl; try { - // TODO get user's encoding preference + + std::size_t bitrate = 0; + + // Get user preferences + { + Wt::Dbo::Transaction transaction(_db.getSession()); + Database::User::pointer user = _db.getCurrentUser(); + if (user) + bitrate = user->getAudioBitrate(); + else + return; // TODO logout? + } Transcode::InputMediaFile inputFile(p); - Transcode::Parameters parameters(inputFile, Transcode::Format::get(Transcode::Format::OGA), 128000); + Transcode::Parameters parameters(inputFile, Transcode::Format::get(Transcode::Format::OGA), bitrate); _mediaPlayer->load( parameters ); diff --git a/ui/audio/AudioWidget.hpp b/ui/audio/AudioWidget.hpp index 6131f69c..f819b33b 100644 --- a/ui/audio/AudioWidget.hpp +++ b/ui/audio/AudioWidget.hpp @@ -28,7 +28,7 @@ class AudioWidget : public Wt::WContainerWidget void handleTrackEnded(void); - SessionData& _sessionData; + Database::Handler& _db; AudioDatabaseWidget* _audioDbWidget; diff --git a/ui/common/EmailValidator.hpp b/ui/common/EmailValidator.hpp deleted file mode 100644 index aaf92a22..00000000 --- a/ui/common/EmailValidator.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#include - -namespace UserInterface { - -static inline Wt::WValidator *createEmailValidator() -{ - return new Wt::WRegExpValidator("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}"); -} - -} // namespace UserInterface diff --git a/ui/common/Validators.hpp b/ui/common/Validators.hpp new file mode 100644 index 00000000..e656c034 --- /dev/null +++ b/ui/common/Validators.hpp @@ -0,0 +1,23 @@ +#include +#include + +#include "database/User.hpp" + +namespace UserInterface { + +static inline Wt::WValidator *createEmailValidator() +{ + Wt::WValidator *res = new Wt::WRegExpValidator("[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,4}"); + res->setMandatory(true); + return res; +} + +static inline Wt::WValidator *createNameValidator() { + Wt::WLengthValidator *v = new Wt::WLengthValidator(); + v->setMandatory(true); + v->setMinimumLength(3); + v->setMaximumLength(::Database::User::MaxNameLength); + return v; +} + +} // namespace UserInterface diff --git a/ui/settings/Settings.cpp b/ui/settings/Settings.cpp index fece783a..f1cd2a7c 100644 --- a/ui/settings/Settings.cpp +++ b/ui/settings/Settings.cpp @@ -2,6 +2,9 @@ #include #include +#include "SettingsAudioFormView.hpp" +#include "SettingsUserFormView.hpp" +#include "SettingsAccountFormView.hpp" #include "SettingsDatabaseFormView.hpp" #include "SettingsUsers.hpp" @@ -28,16 +31,16 @@ _sessionData(sessionData) // Must be logged in here assert(user); + menu->addItem("Audio", new AudioFormView(sessionData, Database::User::getId(user))); if (user->isAdmin()) { - // TODO Special admin settings menu->addItem("Database", new DatabaseFormView(sessionData)); menu->addItem("Users", new Users(sessionData)); } - - // User specifics settings - menu->addItem("Transcoding", new Wt::WTextArea("User's transcoding settings here!")); - menu->addItem("Personal", new Wt::WTextArea("User's password + mail here!")); + else + { + menu->addItem("Account", new AccountFormView(sessionData, Database::User::getId(user))); + } addWidget(contents); } diff --git a/ui/settings/SettingsAccountFormView.cpp b/ui/settings/SettingsAccountFormView.cpp new file mode 100644 index 00000000..bd34ed75 --- /dev/null +++ b/ui/settings/SettingsAccountFormView.cpp @@ -0,0 +1,266 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common/Validators.hpp" + +#include "SettingsAccountFormView.hpp" + +namespace UserInterface { +namespace Settings { + +class AccountFormModel : public Wt::WFormModel +{ + public: + + // Associate each field with a unique string literal. + static const Field NameField; + static const Field EmailField; + static const Field PasswordField; + static const Field PasswordConfirmField; + + AccountFormModel(SessionData& sessionData, std::string userId, Wt::WObject *parent = 0) + : Wt::WFormModel(parent), + _db(sessionData.getDatabaseHandler()), + _userId(userId) + { + addField(NameField); + addField(EmailField); + addField(PasswordField); + addField(PasswordConfirmField); + + setValidator(NameField, createNameValidator()); + setValidator(EmailField, createEmailValidator()); + + // populate the model with initial data + loadData(); + } + + void loadData() + { + Wt::Dbo::Transaction transaction(_db.getSession()); + + Wt::Auth::User authUser = _db.getUserDatabase().findWithId( _userId ); + Database::User::pointer user = _db.getUser(authUser); + + if (user && authUser.isValid()) + { + setValue(NameField, authUser.identity(Wt::Auth::Identity::LoginName)); + if (!authUser.email().empty()) + setValue(EmailField, authUser.email()); + else + setValue(EmailField, authUser.unverifiedEmail()); + } + setValue(PasswordField, Wt::WString()); + setValue(PasswordConfirmField, Wt::WString()); + } + + bool saveData(Wt::WString& error) + { + // DBO transaction active here + try { + Wt::Dbo::Transaction transaction(_db.getSession()); + + // Update user + Wt::Auth::User authUser = _db.getUserDatabase().findWithId(_userId); + Database::User::pointer user = _db.getUser( authUser ); + + // user may have been deleted by someone else + if (!authUser.isValid()) { + error = Wt::WString("User identity does not exist"); + return false; + } + else if(!user) + { + error = Wt::WString("User not found"); + return false; + } + + // Account + authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); + authUser.setEmail(valueText(EmailField).toUTF8()); + + // Password + if (!valueText(PasswordField).empty()) + Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); + + setValue(PasswordField, Wt::WString()); + setValue(PasswordConfirmField, Wt::WString()); + + } + catch(Wt::Dbo::Exception& exception) + { + std::cerr << "Dbo exception: " << exception.what() << std::endl; + return false; + } + + return true; + } + + bool validateField(Field field) + { + // DBO transaction active here + + Wt::WString error; + + if (field == NameField) + { + Wt::Dbo::Transaction transaction(_db.getSession()); + // Must be unique since used as LoginIdentity + Wt::Auth::User user = _db.getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(field)); + if (user.isValid() && user.id() != _userId) + error = "Already exists"; + else + return Wt::WFormModel::validateField(field); + } + else if (field == PasswordField) + { + // Password is mandatory if we create the user + if (!valueText(PasswordField).empty()) + { + // Evaluate the strength of the password + Wt::Auth::AbstractPasswordService::StrengthValidatorResult res + = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), + valueText(NameField), + valueText(EmailField).toUTF8()); + + if (!res.isValid()) + error = res.message(); + } + else + return Wt::WFormModel::validateField(field); + } + else if (field == PasswordConfirmField) + { + if (validation(PasswordField).state() == Wt::WValidator::Valid) + { + if (valueText(PasswordField) != valueText(PasswordConfirmField)) + error = Wt::WString::tr("Wt.Auth.passwords-dont-match"); + } + } + else + { + // Apply validators + return Wt::WFormModel::validateField(field); + } + + setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error)); + + return validation(field).state() == Wt::WValidator::Valid; + } + + + private: + + Database::Handler& _db; + std::string _userId; +}; + +const Wt::WFormModel::Field AccountFormModel::NameField = "name"; +const Wt::WFormModel::Field AccountFormModel::EmailField = "email"; +const Wt::WFormModel::Field AccountFormModel::PasswordField = "password"; +const Wt::WFormModel::Field AccountFormModel::PasswordConfirmField = "password-confirm"; + +AccountFormView::AccountFormView(SessionData& sessionData, std::string userId, Wt::WContainerWidget *parent) +: Wt::WTemplateFormView(parent) +{ + + _model = new AccountFormModel(sessionData, userId, this); + + setTemplateText(tr("userAccountForm-template")); + addFunction("id", &WTemplate::Functions::id); + addFunction("block", &WTemplate::Functions::id); + + _applyInfo = new Wt::WText(); + _applyInfo->setInline(false); + _applyInfo->hide(); + bindWidget("apply-info", _applyInfo); + + // Name + Wt::WLineEdit* accountEdit = new Wt::WLineEdit(); + setFormWidget(AccountFormModel::NameField, accountEdit); + accountEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); + + // Email + Wt::WLineEdit* emailEdit = new Wt::WLineEdit(); + setFormWidget(AccountFormModel::EmailField, emailEdit); + emailEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); + + // Password + Wt::WLineEdit* passwordEdit = new Wt::WLineEdit(); + setFormWidget(AccountFormModel::PasswordField, passwordEdit ); + passwordEdit->setEchoMode(Wt::WLineEdit::Password); + passwordEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); + + // Password confirmation + Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit(); + setFormWidget(AccountFormModel::PasswordConfirmField, passwordConfirmEdit); + passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password); + passwordConfirmEdit->changed().connect(_applyInfo, &Wt::WWidget::hide); + + // Title & Buttons + bindString("title", "Account settings"); + + Wt::WPushButton *saveButton = new Wt::WPushButton("Apply"); + saveButton->setStyleClass("btn-primary"); + bindWidget("save-button", saveButton); + saveButton->clicked().connect(this, &AccountFormView::processSave); + + Wt::WPushButton *cancelButton = new Wt::WPushButton("Discard"); + bindWidget("cancel-button", cancelButton); + cancelButton->clicked().connect(this, &AccountFormView::processCancel); + + updateView(_model); + +} + + void +AccountFormView::processCancel() +{ + _applyInfo->show(); + + _applyInfo->setText( Wt::WString::fromUTF8("Parameters reverted!")); + _applyInfo->setStyleClass("alert alert-info"); + _model->loadData(); + + _model->validate(); + updateView(_model); +} + +void +AccountFormView::processSave() +{ + updateModel(_model); + + _applyInfo->show(); + if (_model->validate()) + { + Wt::WString error; + // commit model into DB + if (_model->saveData(error) ) + { + _applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied!")); + _applyInfo->setStyleClass("alert alert-success"); + } + else + { + _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters: ") + error); + _applyInfo->setStyleClass("alert alert-danger"); + } + } + else + { + _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); + _applyInfo->setStyleClass("alert alert-danger"); + } + updateView(_model); +} + +} // namespace Settings +} // namespace UserInterface diff --git a/ui/settings/SettingsAccountFormView.hpp b/ui/settings/SettingsAccountFormView.hpp new file mode 100644 index 00000000..b20c380d --- /dev/null +++ b/ui/settings/SettingsAccountFormView.hpp @@ -0,0 +1,32 @@ +#ifndef UI_SETTINGS_ACCOUNT_FORM_VIEW_HPP +#define UI_SETTINGS_ACCOUNT_FORM_VIEW_HPP + +#include +#include +#include + +#include "common/SessionData.hpp" + +namespace UserInterface { +namespace Settings { + +class AccountFormModel; + +class AccountFormView : public Wt::WTemplateFormView +{ + public: + AccountFormView(SessionData& sessionData, std::string userId, Wt::WContainerWidget *parent = 0); + + private: + void processCancel(); // reload from DB + void processSave(); // commit into DB + + AccountFormModel* _model; + Wt::WText* _applyInfo; + +}; + +} // namespace Settings +} // namespace UserInterface + +#endif diff --git a/ui/settings/SettingsAudioFormView.cpp b/ui/settings/SettingsAudioFormView.cpp new file mode 100644 index 00000000..03688d10 --- /dev/null +++ b/ui/settings/SettingsAudioFormView.cpp @@ -0,0 +1,178 @@ +#include + +#include +#include +#include +#include + +#include "common/Validators.hpp" + +#include "SettingsAudioFormView.hpp" + +namespace UserInterface { +namespace Settings { + +class AudioFormModel : public Wt::WFormModel +{ + public: + + // Associate each field with a unique string literal. + static const Field BitrateField; + + AudioFormModel(SessionData& sessionData, std::string userId, Wt::WObject *parent = 0) + : Wt::WFormModel(parent), + _db(sessionData.getDatabaseHandler()), + _userId(userId) + { + initializeModels(); + + addField(BitrateField); + + setValidator(BitrateField, new Wt::WValidator(true)); // mandatory + + // populate the model with initial data + loadData(); + } + + Wt::WAbstractItemModel *bitrateModel() { return _bitrateModel; } + + void loadData() + { + Wt::Dbo::Transaction transaction(_db.getSession()); + + Database::User::pointer user = Database::User::getById( _db.getSession(), _userId); + + if (user) + setValue(BitrateField, std::min(user->getMaxAudioBitrate(), user->getAudioBitrate()) / 1000); // in kps + else + setValue(BitrateField, Wt::WString()); + } + + bool saveData(Wt::WString& error) + { + // DBO transaction active here + try { + Wt::Dbo::Transaction transaction(_db.getSession()); + + Database::User::pointer user = Database::User::getById( _db.getSession(), _userId); + // user may have been deleted by someone else + if (user) + { + user.modify()->setAudioBitrate( Wt::asNumber(value(BitrateField)) * 1000); // in kbps + } + + } + catch(Wt::Dbo::Exception& exception) + { + std::cerr << "Dbo exception: " << exception.what() << std::endl; + return false; + } + + return true; + } + + private: + void initializeModels() + { + Wt::Dbo::Transaction transaction(_db.getSession()); + + Database::User::pointer user = Database::User::getById(_db.getSession(), _userId); + + _bitrateModel = new Wt::WStringListModel(); + BOOST_FOREACH(std::size_t bitrate, Database::User::audioBitrates) + { + if (user && bitrate <= user->getMaxAudioBitrate()) + _bitrateModel->addString( Wt::WString("{1}").arg( bitrate / 1000 ) ); // in kbps + } + } + + Database::Handler& _db; + std::string _userId; + Wt::WStringListModel* _bitrateModel; +}; + +const Wt::WFormModel::Field AudioFormModel::BitrateField = "bitrate"; + +AudioFormView::AudioFormView(SessionData& sessionData, std::string userId, Wt::WContainerWidget *parent) +: Wt::WTemplateFormView(parent) +{ + + _model = new AudioFormModel(sessionData, userId, this); + + setTemplateText(tr("audioForm-template")); + addFunction("id", &WTemplate::Functions::id); + addFunction("block", &WTemplate::Functions::id); + + _applyInfo = new Wt::WText(); + _applyInfo->setInline(false); + _applyInfo->hide(); + bindWidget("apply-info", _applyInfo); + + // Bitrate + Wt::WComboBox *bitrateCB = new Wt::WComboBox(); + setFormWidget(AudioFormModel::BitrateField, bitrateCB); + bitrateCB->setStyleClass("span2"); + bitrateCB->setModel(_model->bitrateModel()); + bitrateCB->changed().connect(_applyInfo, &Wt::WWidget::hide); + + // Title & Buttons + bindString("title", "Audio settings"); + + Wt::WPushButton *saveButton = new Wt::WPushButton("Apply"); + saveButton->setStyleClass("btn-primary"); + bindWidget("save-button", saveButton); + saveButton->clicked().connect(this, &AudioFormView::processSave); + + Wt::WPushButton *cancelButton = new Wt::WPushButton("Discard"); + bindWidget("cancel-button", cancelButton); + cancelButton->clicked().connect(this, &AudioFormView::processCancel); + + + updateView(_model); + +} + +void +AudioFormView::processCancel() +{ + _applyInfo->show(); + + _applyInfo->setText( Wt::WString::fromUTF8("Parameters reverted!")); + _applyInfo->setStyleClass("alert alert-info"); + _model->loadData(); + + _model->validate(); + updateView(_model); +} + +void +AudioFormView::processSave() +{ + updateModel(_model); + + _applyInfo->show(); + if (_model->validate()) + { + Wt::WString error; + // commit model into DB + if (_model->saveData(error) ) + { + _applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied!")); + _applyInfo->setStyleClass("alert alert-success"); + } + else + { + _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters: ") + error); + _applyInfo->setStyleClass("alert alert-danger"); + } + } + else + { + _applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); + _applyInfo->setStyleClass("alert alert-danger"); + } + updateView(_model); +} + +} // namespace Settings +} // namespace UserInterface diff --git a/ui/settings/SettingsAudioFormView.hpp b/ui/settings/SettingsAudioFormView.hpp new file mode 100644 index 00000000..47815a1e --- /dev/null +++ b/ui/settings/SettingsAudioFormView.hpp @@ -0,0 +1,32 @@ +#ifndef UI_SETTINGS_AUDIO_FORM_VIEW_HPP +#define UI_SETTINGS_AUDIO_ACCOUNT_FORM_VIEW_HPP + +#include +#include +#include + +#include "common/SessionData.hpp" + +namespace UserInterface { +namespace Settings { + +class AudioFormModel; + +class AudioFormView : public Wt::WTemplateFormView +{ + public: + AudioFormView(SessionData& sessionData, std::string userId, Wt::WContainerWidget *parent = 0); + + private: + void processCancel(); // reload from DB + void processSave(); // commit into DB + + AudioFormModel* _model; + Wt::WText* _applyInfo; + +}; + +} // namespace Settings +} // namespace UserInterface + +#endif diff --git a/ui/settings/SettingsDatabaseFormView.cpp b/ui/settings/SettingsDatabaseFormView.cpp index f475e3f4..a8d9deef 100644 --- a/ui/settings/SettingsDatabaseFormView.cpp +++ b/ui/settings/SettingsDatabaseFormView.cpp @@ -250,25 +250,35 @@ DatabaseFormView::DatabaseFormView(SessionData& sessionData, Wt::WContainerWidge addFunction("id", &WTemplate::Functions::id); addFunction("block", &WTemplate::Functions::id); + applyInfo = new Wt::WText(); + applyInfo->setInline(false); + applyInfo->hide(); + bindWidget("apply-info", applyInfo); + // Path - setFormWidget(DatabaseFormModel::PathField, new Wt::WLineEdit()); + Wt::WLineEdit *pathEdit = new Wt::WLineEdit(); + setFormWidget(DatabaseFormModel::PathField, pathEdit); + pathEdit->changed().connect(applyInfo, &Wt::WWidget::hide); // Update Period Wt::WComboBox *updatePeriodCB = new Wt::WComboBox(); setFormWidget(DatabaseFormModel::UpdatePeriodField, updatePeriodCB); updatePeriodCB->setModel(model->updatePeriodModel()); + updatePeriodCB->changed().connect(applyInfo, &Wt::WWidget::hide); // Update Start Time Wt::WComboBox *updateStartTimeCB = new Wt::WComboBox(); setFormWidget(DatabaseFormModel::UpdateStartTimeField, updateStartTimeCB); updateStartTimeCB->setModel(model->updateStartTimeModel()); + updateStartTimeCB->changed().connect(applyInfo, &Wt::WWidget::hide); // Request Immediate scan - setFormWidget(DatabaseFormModel::UpdateRequestImmediateField, new Wt::WCheckBox()); + Wt::WCheckBox *immScan = new Wt::WCheckBox(); + setFormWidget(DatabaseFormModel::UpdateRequestImmediateField, immScan); + immScan->changed().connect(applyInfo, &Wt::WWidget::hide); // Title & Buttons - Wt::WString title = Wt::WString("Media directories settings"); - bindString("title", title); + bindString("title", "Media directories settings"); Wt::WPushButton *saveButton = new Wt::WPushButton("Apply"); bindWidget("apply-button", saveButton); @@ -277,11 +287,6 @@ DatabaseFormView::DatabaseFormView(SessionData& sessionData, Wt::WContainerWidge Wt::WPushButton *discardButton = new Wt::WPushButton("Discard"); bindWidget("discard-button", discardButton); - applyInfo = new Wt::WText(); - applyInfo->setInline(false); - applyInfo->hide(); - bindWidget("apply-info", applyInfo); - saveButton->clicked().connect(this, &DatabaseFormView::processSave); discardButton->clicked().connect(this, &DatabaseFormView::processDiscard); @@ -325,15 +330,13 @@ DatabaseFormView::processSave() applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied!")); applyInfo->setStyleClass("alert alert-success"); - - // Udate the view: Delete any validation message in the view, etc. - updateView(model); - - } else { + } + else { applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!")); applyInfo->setStyleClass("alert alert-danger"); - updateView(model); } + // Udate the view: Delete any validation message in the view, etc. + updateView(model); } diff --git a/ui/settings/SettingsUserFormView.cpp b/ui/settings/SettingsUserFormView.cpp index 221e9b7b..ddd7316b 100644 --- a/ui/settings/SettingsUserFormView.cpp +++ b/ui/settings/SettingsUserFormView.cpp @@ -2,20 +2,17 @@ #include #include -#include #include #include #include #include #include -#include -#include -#include -#include "common/EmailValidator.hpp" +#include "common/Validators.hpp" #include "SettingsUserFormView.hpp" + namespace UserInterface { namespace Settings { @@ -34,7 +31,7 @@ class UserFormModel : public Wt::WFormModel UserFormModel(SessionData& sessionData, std::string userId, Wt::WObject *parent = 0) : Wt::WFormModel(parent), - _sessionData(sessionData), + _db(sessionData.getDatabaseHandler()), _userId(userId) { initializeModels(); @@ -48,9 +45,7 @@ class UserFormModel : public Wt::WFormModel addField(VideoBitrateLimitField); setValidator(NameField, createNameValidator()); - Wt::WValidator *emailValidator = createEmailValidator(); - emailValidator->setMandatory(true); - setValidator(EmailField, emailValidator); + setValidator(EmailField, createEmailValidator()); // If creating a user, passwords are mandatory if (_userId.empty()) { @@ -72,67 +67,60 @@ class UserFormModel : public Wt::WFormModel if (!userId.empty()) { - Database::Handler& db = _sessionData.getDatabaseHandler(); + Wt::Dbo::Transaction transaction(_db.getSession()); - Wt::Dbo::Transaction transaction(db.getSession()); - - Wt::Auth::User authUser = db.getUserDatabase().findWithId( userId ); - Database::User::pointer user = db.getUser(authUser); - - Wt::Auth::User currentUser = _sessionData.getDatabaseHandler().getLogin().user(); - - if (user && user->isAdmin()) - { - setValue(AdminField, true); - - // We can cannot remove admin rights to ourselves - if (currentUser == authUser) - setReadOnly(AdminField, true); - - // if the user is admin, no need to limit it - setReadOnly(AudioBitrateLimitField, true); - setValidator(AudioBitrateLimitField, nullptr); - - setReadOnly(VideoBitrateLimitField, true); - setValidator(VideoBitrateLimitField, nullptr); - } + Wt::Auth::User authUser = _db.getUserDatabase().findWithId( userId ); + Database::User::pointer user = _db.getUser(authUser); + Wt::Auth::User currentUser = _db.getLogin().user(); if (user && authUser.isValid()) { + if (user->isAdmin()) + { + setValue(AdminField, true); + + // We can cannot remove admin rights to ourselves + if (currentUser == authUser) + setReadOnly(AdminField, true); + + // if the user is admin, no need to limit it + setReadOnly(AudioBitrateLimitField, true); + setValidator(AudioBitrateLimitField, nullptr); + + setReadOnly(VideoBitrateLimitField, true); + setValidator(VideoBitrateLimitField, nullptr); + } + else + { + setValue(AudioBitrateLimitField, user->getMaxAudioBitrate() / 1000); // in kbps + setValue(VideoBitrateLimitField, user->getMaxVideoBitrate() / 1000); // in kbps + } + setValue(NameField, authUser.identity(Wt::Auth::Identity::LoginName)); if (!authUser.email().empty()) setValue(EmailField, authUser.email()); else setValue(EmailField, authUser.unverifiedEmail()); } - - if (user) - { - setValue(AudioBitrateLimitField, user->getMaxAudioBitrate() / 1000); // in kbps - setValue(VideoBitrateLimitField, user->getMaxVideoBitrate() / 1000); // in kbps - } - } } bool saveData() { - // DBO transaction active here try { - Database::Handler& db = _sessionData.getDatabaseHandler(); - Wt::Dbo::Transaction transaction(db.getSession()); + Wt::Dbo::Transaction transaction(_db.getSession()); if (_userId.empty()) { // Create user - Wt::Auth::User authUser = db.getUserDatabase().registerNew(); - Database::User::pointer user = db.getUser(authUser); + Wt::Auth::User authUser = _db.getUserDatabase().registerNew(); + Database::User::pointer user = _db.getUser(authUser); // Account authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField)); authUser.setEmail(valueText(EmailField).toUTF8()); - db.getPasswordService().updatePassword(authUser, valueText(PasswordField)); + Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); // Access { @@ -153,10 +141,9 @@ class UserFormModel : public Wt::WFormModel } else { - // Update user - Wt::Auth::User authUser = db.getUserDatabase().findWithId(_userId); - Database::User::pointer user = db.getUser( authUser ); + Wt::Auth::User authUser = _db.getUserDatabase().findWithId(_userId); + Database::User::pointer user = _db.getUser( authUser ); // user may have been deleted by someone else if (!authUser.isValid()) { @@ -175,7 +162,7 @@ class UserFormModel : public Wt::WFormModel // Password if (!valueText(PasswordField).empty()) - db.getPasswordService().updatePassword(authUser, valueText(PasswordField)); + Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField)); // Access if (!isReadOnly(AdminField)) @@ -207,15 +194,13 @@ class UserFormModel : public Wt::WFormModel bool validateField(Field field) { - // DBO transaction active here - Wt::WString error; if (field == NameField) { - Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession()); + Wt::Dbo::Transaction transaction(_db.getSession()); // Must be unique since used as LoginIdentity - Wt::Auth::User user = _sessionData.getDatabaseHandler().getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(field)); + Wt::Auth::User user = _db.getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, valueText(field)); if (user.isValid() && user.id() != _userId) error = "Already exists"; else @@ -227,17 +212,10 @@ class UserFormModel : public Wt::WFormModel if (!valueText(PasswordField).empty()) { // Evaluate the strength of the password - Wt::Auth::PasswordStrengthValidator validator; - - // Reduce some constraints... - validator.setMinimumLength( Wt::Auth::PasswordStrengthValidator::TwoCharClass, 11); - validator.setMinimumLength( Wt::Auth::PasswordStrengthValidator::ThreeCharClass, 8 ); - validator.setMinimumLength( Wt::Auth::PasswordStrengthValidator::FourCharClass, 6 ); - Wt::Auth::AbstractPasswordService::StrengthValidatorResult res - = validator.evaluateStrength(valueText(PasswordField), - valueText(NameField), - valueText(EmailField).toUTF8()); + = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField), + valueText(NameField), + valueText(EmailField).toUTF8()); if (!res.isValid()) error = res.message(); @@ -267,58 +245,21 @@ class UserFormModel : public Wt::WFormModel private: - static Wt::WValidator *createNameValidator() { - Wt::WLengthValidator *v = new Wt::WLengthValidator(); - v->setMandatory(true); - v->setMinimumLength(3); - v->setMaximumLength(::Database::User::MaxNameLength); - return v; - } - void initializeModels() { - // AUDIO - // TODO move defaults somewhere else? - static const std::vector - audioBitrateLimits = - { - 64, - 96, - 128, - 160, - 192, - 224, - 256, - 320, - 512 - }; - _audioBitrateModel = new Wt::WStringListModel(); - for (std::size_t i = 0; i < audioBitrateLimits.size(); ++i) - _audioBitrateModel->addString( Wt::WString("{1}").arg( audioBitrateLimits[i] ) ); - + BOOST_FOREACH(std::size_t bitrate, Database::User::audioBitrates) + _audioBitrateModel->addString( Wt::WString("{1}").arg( bitrate / 1000 ) ); // in kbps // VIDEO - // TODO move defaults somewhere else? - static const std::vector - videoBitrateLimits = - { - 256, - 512, - 1024, - 2048, - 4096, - 8192 - }; - _videoBitrateModel = new Wt::WStringListModel(); - for (std::size_t i = 0; i < videoBitrateLimits.size(); ++i) - _videoBitrateModel->addString( Wt::WString("{1}").arg( videoBitrateLimits[i] ) ); + BOOST_FOREACH(std::size_t bitrate, Database::User::videoBitrates) + _videoBitrateModel->addString( Wt::WString("{1}").arg( bitrate / 1000 ) ); // in kbps } - SessionData& _sessionData; + Database::Handler& _db; std::string _userId; Wt::WStringListModel* _audioBitrateModel; Wt::WStringListModel* _videoBitrateModel; @@ -334,8 +275,7 @@ const Wt::WFormModel::Field UserFormModel::VideoBitrateLimitField = "video-bitra UserFormView::UserFormView(SessionData& sessionData, std::string userId, Wt::WContainerWidget *parent) -: Wt::WTemplateFormView(parent), - _sessionData(sessionData) +: Wt::WTemplateFormView(parent) { _model = new UserFormModel(sessionData, userId, this); @@ -360,9 +300,10 @@ UserFormView::UserFormView(SessionData& sessionData, std::string userId, Wt::WCo setFormWidget(UserFormModel::PasswordConfirmField, passwordConfirmEdit); passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password); + bindString("access", "Access"); + // Admin Field - Wt::WCheckBox *admin = new Wt::WCheckBox(); - setFormWidget(UserFormModel::AdminField, admin); + setFormWidget(UserFormModel::AdminField, new Wt::WCheckBox()); // AudioBitrate Wt::WComboBox *audioBitrateCB = new Wt::WComboBox(); @@ -418,7 +359,7 @@ UserFormView::UserFormView(SessionData& sessionData, std::string userId, Wt::WCo } -void + void UserFormView::processCancel() { // parent widget will delete this widget diff --git a/ui/settings/SettingsUserFormView.hpp b/ui/settings/SettingsUserFormView.hpp index c66d8bd5..27765a2a 100644 --- a/ui/settings/SettingsUserFormView.hpp +++ b/ui/settings/SettingsUserFormView.hpp @@ -15,6 +15,7 @@ class UserFormModel; class UserFormView : public Wt::WTemplateFormView { public: + UserFormView(SessionData& sessionData, std::string userId, Wt::WContainerWidget *parent = 0); // Signal emitted once the form is completed @@ -24,8 +25,6 @@ class UserFormView : public Wt::WTemplateFormView Wt::Signal _sigCompleted; - SessionData& _sessionData; - void processSave(); void processCancel(); diff --git a/ui/settings/SettingsUsers.cpp b/ui/settings/SettingsUsers.cpp index 4f252ecf..84e966ad 100644 --- a/ui/settings/SettingsUsers.cpp +++ b/ui/settings/SettingsUsers.cpp @@ -67,11 +67,7 @@ Users::refresh(void) for (std::size_t i = 0; i < users.size(); ++i) { - std::string userId; - { - std::ostringstream oss; oss << users[i].id(); - userId = oss.str(); - } + std::string userId = Database::User::getId(users[i]); Wt::Auth::User authUser;