Reworked the project layout

This commit is contained in:
emeric
2014-10-10 20:41:34 +02:00
parent 92a176c899
commit 49ff050539
146 changed files with 156 additions and 162 deletions
+130
View File
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WBootstrapTheme>
#include "auth/LmsAuth.hpp"
#include "LmsHome.hpp"
#include "settings/SettingsFirstConnectionFormView.hpp"
#include "LmsApplication.hpp"
namespace skeletons {
extern const char *AuthStrings_xml1;
}
namespace UserInterface {
Wt::WApplication*
LmsApplication::create(const Wt::WEnvironment& env, boost::filesystem::path dbPath)
{
/*
* You could read information from the environment to decide whether
* the user has permission to start a new application
*/
return new LmsApplication(env, dbPath);
}
/*
* The env argument contains information about the new session, and
* the initial request. It must be passed to the Wt::WApplication
* constructor so it is typically also an argument for your custom
* application constructor.
*/
LmsApplication::LmsApplication(const Wt::WEnvironment& env, boost::filesystem::path dbPath)
: Wt::WApplication(env),
_sessionData(dbPath),
_home(nullptr)
{
Wt::WBootstrapTheme *bootstrapTheme = new Wt::WBootstrapTheme(this);
bootstrapTheme->setVersion(Wt::WBootstrapTheme::Version3);
bootstrapTheme->setResponsive(true);
setTheme(bootstrapTheme);
// Add a resource bundle
messageResourceBundle().use(appRoot() + "templates");
setTitle("LMS"); // application title
bool firstConnection;
{
Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession());
firstConnection = (Database::User::getAll(_sessionData.getDatabaseHandler().getSession()).size() == 0);
}
// If here is no account in the database, launch the first connection wizard
if (firstConnection)
{
// Hack, use the auth widget builtin strings
builtinLocalizedStrings().useBuiltin(skeletons::AuthStrings_xml1);
root()->addWidget( new Settings::FirstConnectionFormView(_sessionData));
}
else
{
_sessionData.getDatabaseHandler().getLogin().changed().connect(this, &LmsApplication::handleAuthEvent);
LmsAuth *authWidget = new LmsAuth(_sessionData.getDatabaseHandler());
authWidget->model()->addPasswordAuth(&Database::Handler::getPasswordService());
authWidget->setRegistrationEnabled(false);
authWidget->processEnvironment();
root()->addWidget(authWidget);
}
}
void
LmsApplication::handleAuthEvent(void)
{
if (_sessionData.getDatabaseHandler().getLogin().loggedIn())
{
if (_home == nullptr) {
_home = new LmsHome(_sessionData, root() );
}
else
std::cerr << "Already logged in??" << std::endl;
}
else
{
std::cerr << "user log out" << std::endl;
if (_home != nullptr) {
delete _home;
_home = nullptr;
// Hack: quit/redirect in order to avoid 'signal not exposed' problems
// TODO, investigate/remove?
quit();
redirect("/");
}
else
std::cerr << "Already logged out??" << std::endl;
}
}
} // namespace UserInterface
+54
View File
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMS_APPLICATION_HPP
#define LMS_APPLICATION_HPP
#include <Wt/WApplication>
#include "common/SessionData.hpp"
#include "LmsHome.hpp"
namespace UserInterface {
class LmsApplication : public Wt::WApplication
{
public:
static Wt::WApplication *create(const Wt::WEnvironment& env, boost::filesystem::path dbPath);
LmsApplication(const Wt::WEnvironment& env, boost::filesystem::path dbPath);
private:
void handleAuthEvent(void);
SessionData _sessionData;
LmsHome* _home;
};
} // namespace UserInterface
#endif
+99
View File
@@ -0,0 +1,99 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WStackedWidget>
#include <Wt/WMenu>
#include <Wt/WNavigationBar>
#include <Wt/WPopupMenu>
#include <Wt/WPopupMenuItem>
#include <Wt/Auth/Identity>
#include "settings/Settings.hpp"
#include "LmsHome.hpp"
namespace UserInterface {
LmsHome::LmsHome(SessionData& sessionData, Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent),
_sessionData(sessionData)
{
const Wt::Auth::User& user = sessionData.getDatabaseHandler().getLogin().user();
// Create a navigation bar with a link to a web page.
Wt::WNavigationBar *navigation = new Wt::WNavigationBar(this);
navigation->setTitle("LMS");
navigation->setResponsive(true);
navigation->addStyleClass("main-nav");
Wt::WStackedWidget *contentsStack = new Wt::WStackedWidget(this);
// Setup a Left-aligned menu.
Wt::WMenu *leftMenu = new Wt::WMenu(contentsStack);
navigation->addMenu(leftMenu);
_audioWidget = new AudioWidget(_sessionData);
_videoWidget = new VideoWidget(_sessionData);
leftMenu->addItem("Audio", _audioWidget);
leftMenu->addItem("Video", _videoWidget);
leftMenu->addItem("Settings", new Settings::Settings(_sessionData));
// Setup a Right-aligned menu.
Wt::WMenu *rightMenu = new Wt::WMenu();
navigation->addMenu(rightMenu, Wt::AlignRight);
Wt::WPopupMenu *popup = new Wt::WPopupMenu();
popup->addItem("Logout");
popup->itemSelected().connect(this, &LmsHome::handleUserMenuSelected);
Wt::WMenuItem *item = new Wt::WMenuItem( user.identity(Wt::Auth::Identity::LoginName) );
item->setMenu(popup);
rightMenu->addItem(item);
// Add a Search control.
_searchEdit = new Wt::WLineEdit();
_searchEdit->setEmptyText("Search...");
_searchEdit->enterPressed().connect(this, &LmsHome::handleSearch);
navigation->addSearch(_searchEdit, Wt::AlignLeft);
addWidget(contentsStack);
}
void
LmsHome::handleUserMenuSelected( Wt::WMenuItem* item)
{
if (item && item->text() == "Logout") {
_sessionData.getDatabaseHandler().getLogin().logout();
}
}
void
LmsHome::handleSearch(void)
{
// TODO Check currently selected menu item and search it
_audioWidget->search( _searchEdit->text().toUTF8() );
}
} // namespace UserInterface
+54
View File
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LMS_HOME_HPP
#define LMS_HOME_HPP
#include <Wt/WContainerWidget>
#include <Wt/WLineEdit>
#include "common/SessionData.hpp"
#include "audio/AudioWidget.hpp"
#include "video/VideoWidget.hpp"
namespace UserInterface {
class LmsHome : public Wt::WContainerWidget
{
public:
LmsHome(SessionData& sessionData, Wt::WContainerWidget* parent = 0);
private:
void handleSearch(void);
void handleUserMenuSelected( Wt::WMenuItem* item );
SessionData& _sessionData;
Wt::WLineEdit* _searchEdit;
AudioWidget* _audioWidget;
VideoWidget* _videoWidget;
};
} // namespace UserInterface
#endif
+313
View File
@@ -0,0 +1,313 @@
<?xml version="1.0" encoding="UTF-8" ?>
<messages xmlns:if="Wt.WTemplate.conditions">
<!--FORMS message blocks-->
<message id="firstConnectionForm-template">
<legend>${title}</legend>
<div class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="${id:name}">
Name
</label>
<div class="col-sm-5">
${name}
</div>
<div class="help-block col-sm-5">
${name-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:email}">
e-Mail
</label>
<div class="col-sm-5">
${email}
</div>
<div class="help-block col-sm-5">
${email-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:password}">
Password
</label>
<div class="col-sm-5">
${password}
</div>
${password-info}
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:password-confirm}">
Confirm password
</label>
<div class="col-sm-5">
${password-confirm}
</div>
<div class="help-block col-sm-5">
${password-confirm-info}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
${save-button}
</div>
</div>
${apply-info}
</div>
</message>
<message id="userForm-template">
<legend>${title}</legend>
<div class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="${id:name}">
Name
</label>
<div class="col-sm-5">
${name}
</div>
<div class="help-block col-sm-5">
${name-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:email}">
e-Mail
</label>
<div class="col-sm-5">
${email}
</div>
<div class="help-block col-sm-5">
${email-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:password}">
Password
</label>
<div class="col-sm-5">
${password}
</div>
<div class="help-block col-sm-5">
${password-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:password-confirm}">
Confirm password
</label>
<div class="col-sm-5">
${password-confirm}
</div>
<div class="help-block col-sm-5">
${password-confirm-info}
</div>
</div>
</div>
<legend>${access}</legend>
<div class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="${id:admin}">
Admin
</label>
<div class="col-sm-5">
${admin}
</div>
<div class="help-block col-sm-5">
${admin-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:audio-bitrate-limit}">
Audio Bitrate Limit
</label>
<div class="col-sm-5">
<div class="input-group">
${audio-bitrate-limit}
<span class="input-group-addon">kbps</span>
</div>
</div>
<div class="help-block col-sm-5">
${audio-bitrate-limit-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:video-bitrate-limit}">
Video Bitrate Limit
</label>
<div class="col-sm-5">
<div class="input-group">
${video-bitrate-limit}
<span class="input-group-addon">kbps</span>
</div>
</div>
<div class="help-block col-sm-5">
${video-bitrate-limit-info}
</div>
</div>
</div>
<div class="form-horizontal">
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
${save-button} ${cancel-button}
</div>
</div>
</div>
</message>
<message id="userAccountForm-template">
<legend>${title}</legend>
<div class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="${id:name}">
Name
</label>
<div class="col-sm-5">
${name}
</div>
<div class="help-block col-sm-5">
${name-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:email}">
e-Mail
</label>
<div class="col-sm-5">
${email}
</div>
<div class="help-block col-sm-5">
${email-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:password}">
Password
</label>
<div class="col-sm-5">
${password}
</div>
<div class="help-block col-sm-5">
${password-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:password-confirm}">
Confirm password
</label>
<div class="col-sm-5">
${password-confirm}
</div>
<div class="help-block col-sm-5">
${password-confirm-info}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
${save-button} ${cancel-button}
</div>
</div>
${apply-info}
</div>
</message>
<message id="audioForm-template">
<legend>${title}</legend>
<div class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="${id:bitrate}">
Audio bitrate
</label>
<div class="col-sm-5">
<div class="input-group">
${bitrate}
<span class="input-group-addon">kbps</span>
</div>
</div>
<div class="help-block col-sm-5">
${bitrate-info}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
${save-button} ${cancel-button}
</div>
</div>
${apply-info}
</div>
</message>
<message id="mediaDirectoryForm-template">
<legend>${title}</legend>
<div class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="${id:path}">
Path
</label>
<div class="col-sm-5">
${path}
</div>
<div class="help-block col-sm-5">
${path-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:type}">
Type
</label>
<div class="col-sm-5">
${type}
</div>
<div class="help-block col-sm-5">
${type-info}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
${save-button} ${cancel-button}
</div>
</div>
${apply-info}
</div>
</message>
<message id="databaseForm-template">
<legend>${title}</legend>
<div class="form-horizontal">
<div class="form-group">
<label class="control-label col-sm-2" for="${id:update-period}">
Update period
</label>
<div class="col-sm-5">
${update-period}
</div>
<div class="help-block col-sm-5">
${update-period-info}
</div>
</div>
<div class="form-group">
<label class="control-label col-sm-2" for="${id:update-start-time}">
Update start time
</label>
<div class="col-sm-5">
${update-start-time}
</div>
<div class="help-block col-sm-5">
${update-start-time-info}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
${apply-button} ${discard-button} ${immediate-scan-button}
</div>
</div>
${apply-info}
</div>
</message>
</messages>
+126
View File
@@ -0,0 +1,126 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include <Wt/WTable> // TODO
#include <Wt/WBreak> // TODO
#include "AudioDatabaseWidget.hpp"
#include "TableFilterWidget.hpp"
#include "SearchFilterWidget.hpp"
#include "TrackWidget.hpp"
namespace UserInterface {
AudioDatabaseWidget::AudioDatabaseWidget( Database::Handler& db, Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_refreshingFilters(false)
{
std::size_t idFilter (0);
{
SearchFilterWidget* search = new SearchFilterWidget(this);
_filters.push_back( search );
search->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
}
Wt::WTable* table = new Wt::WTable(this);
{
TableFilterWidget* filterTable = new TableFilterWidget(db, "genre", "name", table->elementAt(0,0));
_filters.push_back( filterTable );
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
}
{
TableFilterWidget* filterTable = new TableFilterWidget(db, "artist", "name", table->elementAt(0,1));
_filters.push_back( filterTable );
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
}
{
TableFilterWidget* filterTable = new TableFilterWidget(db, "release", "name", table->elementAt(0,2));
_filters.push_back( filterTable );
filterTable->update().connect( boost::bind(&AudioDatabaseWidget::handleFilterUpdated, this, idFilter++) );
}
{
TrackWidget* track = new TrackWidget(db, this);
_filters.push_back( track );
track->trackSelected().connect(this, &AudioDatabaseWidget::handleTrackSelected);
}
}
void
AudioDatabaseWidget::search(const std::string& text)
{
SearchFilterWidget* searchWidget ( dynamic_cast<SearchFilterWidget*>(_filters.front() ) );
searchWidget->setText(text);
}
void
AudioDatabaseWidget::handleTrackSelected(boost::filesystem::path p)
{
_trackSelected.emit(p);
}
void
AudioDatabaseWidget::handleFilterUpdated(std::size_t idFilterUpdated)
{
// TODO disconnect from event!
if (_refreshingFilters)
return;
_refreshingFilters = true;
FilterWidget::Constraint currentConstraint;
currentConstraint.where.And( WhereClause( "track.artist_id = artist.id and track.release_id = release.id and track_genre.track_id = track.id and genre.id = track_genre.genre_id"));
for (std::size_t idFilter = 0; idFilter < _filters.size(); ++idFilter)
{
FilterWidget* filter = _filters.at(idFilter);
// Apply contraints created by previous filters
if (idFilter > idFilterUpdated) {
filter->refresh(currentConstraint);
}
// Get constraints generated by this filter
// (Note: adding accross successive calls)
filter->getConstraint(currentConstraint);
}
_refreshingFilters = false;
}
void
AudioDatabaseWidget::selectNextTrack(void)
{
TrackWidget* trackWidget ( dynamic_cast<TrackWidget*>(_filters.back() ) );
trackWidget->selectNextTrack();
}
} // namespace UserInterface
+62
View File
@@ -0,0 +1,62 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AUDIO_DB_WIDGET_HPP
#define AUDIO_DB_WIDGET_HPP
#include <boost/filesystem/path.hpp>
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
#include "common/SessionData.hpp"
#include "FilterWidget.hpp"
namespace UserInterface {
class AudioDatabaseWidget : public Wt::WContainerWidget
{
public:
AudioDatabaseWidget( Database::Handler& db, Wt::WContainerWidget *parent = 0);
void search(const std::string& text);
void selectNextTrack(void); // Will later emit the next selected track
// Signals
Wt::Signal< boost::filesystem::path >& trackSelected() { return _trackSelected; }
private:
Wt::Signal< boost::filesystem::path > _trackSelected;
void handleTrackSelected(boost::filesystem::path p);
void handleFilterUpdated(std::size_t idFilter);
std::vector<FilterWidget*> _filters; // Free Search, Genre, Artist, Release, etc.
bool _refreshingFilters;
};
} // namespace UserInterface
#endif
+173
View File
@@ -0,0 +1,173 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WMediaPlayer>
#include <Wt/WProgressBar>
#include "AudioMediaPlayerWidget.hpp"
namespace UserInterface {
AudioMediaPlayerWidget::AudioMediaPlayerWidget( Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_mediaResource(nullptr)
{
_mediaPlayer = new Wt::WMediaPlayer( Wt::WMediaPlayer::Audio, this );
// _mediaPlayer->setAlternativeContent (new Wt::WText("You don't have HTML5 audio support!"));
// _mediaPlayer->setOptions( Wt::WMediaPlayer::Autoplay );
_mediaPlayer->addSource( Wt::WMediaPlayer::OGA, "" );
_mediaPlayer->ended().connect(this, &AudioMediaPlayerWidget::handleTrackEnded);
{
Wt::WContainerWidget *container = new Wt::WContainerWidget(this);
_prevBtn = new Wt::WPushButton("<<", container );
_playBtn = new Wt::WPushButton("Play", container );
_pauseBtn = new Wt::WPushButton("Pause", container );
_nextBtn = new Wt::WPushButton(">>", container );
_curTime = new Wt::WText(container);
_timeSlider = new Wt::WSlider( container );
_duration = new Wt::WText(container);
_volumeSlider = new Wt::WSlider( container );
_volumeSlider->setRange(0,100);
_volumeSlider->setValue(_mediaPlayer->volume() * 100);
_mediaPlayer->setControlsWidget( container );
_mediaPlayer->setButton(Wt::WMediaPlayer::Play, _playBtn);
_mediaPlayer->setButton(Wt::WMediaPlayer::Pause, _pauseBtn);
_mediaPlayer->setText( Wt::WMediaPlayer::CurrentTime, _curTime);
_mediaPlayer->setText( Wt::WMediaPlayer::Duration, _duration);
_mediaPlayer->timeUpdated().connect(this, &AudioMediaPlayerWidget::handleTimeUpdated);
}
_timeSlider->valueChanged().connect(this, &AudioMediaPlayerWidget::handlePlayOffset);
_timeSlider->sliderMoved().connect(this, &AudioMediaPlayerWidget::handleSliderMoved);
_timeSlider->setDisabled(true);
_volumeSlider->sliderMoved().connect(this, &AudioMediaPlayerWidget::handleVolumeSliderMoved);
_nextBtn->clicked().connect(this, &AudioMediaPlayerWidget::handlePlayNext);
_prevBtn->clicked().connect(this, &AudioMediaPlayerWidget::handlePlayPrev);
}
void
AudioMediaPlayerWidget::loadPlayer(void)
{
_mediaPlayer->clearSources();
_mediaInternalLink.setResource( nullptr );
if (_mediaResource)
delete _mediaResource;
assert( _currentParameters );
_mediaResource = new AvConvTranscodeStreamResource( *_currentParameters, this );
_mediaInternalLink.setResource( _mediaResource );
_mediaPlayer->addSource( Wt::WMediaPlayer::OGA, _mediaInternalLink );
}
void
AudioMediaPlayerWidget::load(const Transcode::Parameters& parameters)
{
_timeSlider->setDisabled(false);
_currentParameters = std::make_shared<Transcode::Parameters>( parameters );
loadPlayer();
_timeSlider->setRange(0, parameters.getInputMediaFile().getDuration().total_seconds() );
_timeSlider->setValue(0);
_duration->setText( boost::posix_time::to_simple_string( parameters.getInputMediaFile().getDuration() ));
_mediaPlayer->play();
}
void
AudioMediaPlayerWidget::handlePlayOffset(int offsetSecs)
{
if (!_currentParameters)
return;
_currentParameters->setOffset( boost::posix_time::seconds(offsetSecs) );
loadPlayer();
_mediaPlayer->play();
}
void
AudioMediaPlayerWidget::handlePlayNext(void)
{
// TODO
}
void
AudioMediaPlayerWidget::handlePlayPrev(void)
{
// TODO
}
void
AudioMediaPlayerWidget::handleTrackEnded(void)
{
_playbackEnded.emit();
}
void
AudioMediaPlayerWidget::handleValueChanged(double value)
{
// TODO
}
void
AudioMediaPlayerWidget::handleSliderMoved(int value)
{
;
}
void
AudioMediaPlayerWidget::handleTimeUpdated(void)
{
if (!_currentParameters)
return;
boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters->getOffset().total_seconds()));
_timeSlider->setValue( currentTime.total_seconds() );
_curTime->setText( boost::posix_time::to_simple_string( currentTime) );
}
void
AudioMediaPlayerWidget::handleVolumeSliderMoved(int value)
{
_mediaPlayer->setVolume( value / 100. );
}
} // namespace UserInterface
+89
View File
@@ -0,0 +1,89 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __MEDIA_PLAYER_WIDGET_HPP
#define __MEDIA_PLAYER_WIDGET_HPP
#include <memory>
#include <Wt/WSlider>
#include <Wt/WPushButton>
#include <Wt/WContainerWidget>
#include <Wt/WMediaPlayer>
#include <Wt/WLink>
#include <Wt/WText>
#include "transcode/Parameters.hpp"
#include "resource/AvConvTranscodeStreamResource.hpp"
namespace UserInterface {
class AudioMediaPlayerWidget : public Wt::WContainerWidget
{
public:
AudioMediaPlayerWidget( Wt::WContainerWidget *parent = 0);
void load(const Transcode::Parameters& parameters);
// Signal slot Next
Wt::Signal<void>& playbackEnded() {return _playbackEnded;}
// Signal Slot Previous
// Signal slot Ended
private:
void handlePlayOffset(int offsetSecs);
void handlePlayNext(void);
void handlePlayPrev(void);
void handleTrackEnded(void);
void handleValueChanged(double);
void handleTimeUpdated(void);
void handleSliderMoved(int value);
void handleVolumeSliderMoved(int value);
void loadPlayer(void);
// Signals
Wt::Signal<void> _playbackEnded;
// Core
Wt::WMediaPlayer* _mediaPlayer;
AvConvTranscodeStreamResource* _mediaResource;
Wt::WLink _mediaInternalLink;
// Controls
std::shared_ptr<Transcode::Parameters> _currentParameters;
Wt::WPushButton* _playBtn;
Wt::WPushButton* _pauseBtn;
Wt::WPushButton* _nextBtn;
Wt::WPushButton* _prevBtn;
Wt::WSlider* _timeSlider;
Wt::WSlider* _volumeSlider;
Wt::WText* _curTime;
Wt::WText* _duration;
};
} // namespace UserInterface
#endif
+122
View File
@@ -0,0 +1,122 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WBreak>
#include "logger/Logger.hpp"
#include "AudioWidget.hpp"
namespace UserInterface {
AudioWidget::AudioWidget(SessionData& sessionData, Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent),
_db(sessionData.getDatabaseHandler()),
_audioDbWidget(nullptr),
_mediaPlayer(nullptr),
_imgResource(nullptr),
_img(nullptr)
{
_audioDbWidget = new AudioDatabaseWidget(sessionData.getDatabaseHandler(), this);
_audioDbWidget->trackSelected().connect(this, &AudioWidget::playTrack);
_mediaPlayer = new AudioMediaPlayerWidget(this);
_mediaPlayer->playbackEnded().connect(this, &AudioWidget::handleTrackEnded);
this->addWidget(new Wt::WBreak());
// Image
_imgResource = new Wt::WMemoryResource(this);
_imgLink.setResource( _imgResource);
_img = new Wt::WImage(_imgLink, this);
}
void
AudioWidget::search(const std::string& searchText)
{
_audioDbWidget->search(searchText);
}
void
AudioWidget::playTrack(boost::filesystem::path p)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "play track '" << p << "'";
try {
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
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Can't play video: user does not exists!";
return; // TODO logout?
}
}
Transcode::InputMediaFile inputFile(p);
Transcode::Parameters parameters(inputFile, Transcode::Format::get(Transcode::Format::OGA));
parameters.setBitrate(Transcode::Stream::Audio, bitrate);
_mediaPlayer->load( parameters );
// Refresh cover
{
std::vector<CoverArt::CoverArt> covers = inputFile.getCovers();
if (!covers.empty())
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "Cover found!";
if (!covers.front().scale(256)) // TODO
LMS_LOG(MOD_UI, SEV_ERROR) << "Cannot resize!";
//_imgResource->setMimeType(covers.front().getMimeType());
_imgResource->setData(covers.front().getData());
}
else {
LMS_LOG(MOD_UI, SEV_DEBUG) << "No cover found!";
_imgResource->setData( std::vector<unsigned char>());
}
_imgResource->setChanged();
}
}
catch( std::exception &e)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Caught exception while loading '" << p << "': " << e.what();
}
}
void
AudioWidget::handleTrackEnded(void)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "Track playback ended!";
_audioDbWidget->selectNextTrack();
}
} // namespace UserInterface
+68
View File
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AUDIO_WIDGET_HPP
#define AUDIO_WIDGET_HPP
#include <string>
#include <Wt/WLink>
#include <Wt/WImage>
#include <Wt/WContainerWidget>
#include <Wt/WMemoryResource>
#include "audio/AudioMediaPlayerWidget.hpp"
#include "audio/AudioDatabaseWidget.hpp"
namespace UserInterface {
class AudioWidget : public Wt::WContainerWidget
{
public:
AudioWidget(SessionData& sessionData, Wt::WContainerWidget* parent = 0);
void search(const std::string& searchText);
private:
void playTrack(boost::filesystem::path p);
void handleTrackEnded(void);
Database::Handler& _db;
AudioDatabaseWidget* _audioDbWidget;
AudioMediaPlayerWidget* _mediaPlayer;
// Image
Wt::WMemoryResource *_imgResource;
Wt::WLink _imgLink;
Wt::WImage *_img;
};
} // namespace UserInterface
#endif
+65
View File
@@ -0,0 +1,65 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FILTER_WIDGET_HPP
#define FILTER_WIDGET_HPP
#include <boost/foreach.hpp>
#include <string>
#include <list>
#include <Wt/WSignal>
#include <Wt/WContainerWidget>
#include "database/SqlQuery.hpp"
namespace UserInterface {
class FilterWidget : public Wt::WContainerWidget {
public:
struct Constraint {
WhereClause where; // WHERE SQL clause
};
FilterWidget(Wt::WContainerWidget* parent = 0) : Wt::WContainerWidget(parent) {}
virtual ~FilterWidget() {}
// Refresh filter Widget using constraints created by parent filters
virtual void refresh(const Constraint& constraint) = 0;
// Update constraints for child filters
virtual void getConstraint(Constraint& constraint) = 0;
// Emitted when a constraint has changed
Wt::Signal<void>& update() { return _update; };
protected:
void emitUpdate() { _update.emit(); }
private:
Wt::Signal<void> _update;
};
} // namespace UserInterface
#endif
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WLabel>
#include "SearchFilterWidget.hpp"
namespace UserInterface {
SearchFilterWidget::SearchFilterWidget(Wt::WContainerWidget* parent)
: FilterWidget( parent )
{
}
void
SearchFilterWidget::setText(const std::string& text)
{
_lastEmittedText = text;
emitUpdate();
}
// Get constraints created by this filter
void
SearchFilterWidget::getConstraint(Constraint& constraint)
{
// No active search means no constaint!
if (!_lastEmittedText.empty()) {
const std::string bindText ("%%" + _lastEmittedText + "%%");
constraint.where.And( WhereClause("(track.name like ? or release.name like ? or artist.name like ?)").bind(bindText).bind(bindText).bind(bindText));
}
// else no constraint!
}
} // namespace UserInterface
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SEARCH_FILTER_WIDGET_PP
#define SEARCH_FILTER_WIDGET_PP
#include <Wt/WLineEdit>
#include "FilterWidget.hpp"
namespace UserInterface {
class SearchFilterWidget : public FilterWidget {
public:
SearchFilterWidget(Wt::WContainerWidget* parent = 0);
void setText(const std::string& text);
// Set constraint on this filter
void refresh(const Constraint& constraint) {}
// Get constraints created by this filter
void getConstraint(Constraint& constraint);
private:
void handleKeyWentUp(void);
std::string _lastEmittedText;
};
} // namespace UserInterface
#endif
+114
View File
@@ -0,0 +1,114 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include "logger/Logger.hpp"
#include "TableFilterWidget.hpp"
namespace UserInterface {
TableFilterWidget::TableFilterWidget(Database::Handler& db, std::string table, std::string field, Wt::WContainerWidget* parent)
: FilterWidget( parent ),
_db(db),
_table(table),
_field(field),
_tableView(nullptr)
{
_queryModel.setQuery( _db.getSession().query< ResultType >("select " + _table + "." + _field + ",count(DISTINCT track.id),0 as ORDERBY from track,artist,release,genre,track_genre WHERE track.artist_id = artist.id and track.release_id = release.id and track_genre.track_id = track.id and genre.id = track_genre.genre_id GROUP BY " + _table + "." + _field + " UNION select '<All>',0,1 AS ORDERBY").orderBy("ORDERBY DESC," + _table + "." + _field));
_queryModel.addColumn( _table + "." + _field, table);
_queryModel.addColumn( "count(DISTINCT track.id)", "Tracks");
_tableView = new Wt::WTableView( this );
_tableView->resize(250, 200); // TODO
_tableView->setSelectionMode(Wt::ExtendedSelection);
_tableView->setSortingEnabled(false);
_tableView->setAlternatingRowColors(true);
_tableView->setModel(&_queryModel);
_tableView->selectionChanged().connect(this, &TableFilterWidget::emitUpdate);
_queryModel.setBatchSize(100);
}
// Set constraints on this filter
void
TableFilterWidget::refresh(const Constraint& constraint)
{
SqlQuery sqlQuery;
sqlQuery.select(_table + "." + _field + ",count(DISTINCT track.id),0 as ORDERBY");
sqlQuery.from().And( FromClause("artist,release,track,genre,track_genre")) ;
sqlQuery.where().And(constraint.where); // Add constraint made by other filters
sqlQuery.groupBy().And( _table + "." + _field); // Add constraint made by other filters
SqlQuery AllSqlQuery;
AllSqlQuery.select("'<All>',0,1 AS ORDERBY");
LMS_LOG(MOD_UI, SEV_DEBUG) << _table << ", generated query = '" << sqlQuery.get() + " UNION " + AllSqlQuery.get() << "'";
Wt::Dbo::Query<ResultType> query = _db.getSession().query<ResultType>( sqlQuery.get() + " UNION " + AllSqlQuery.get() );
query.orderBy("ORDERBY DESC," + _table + "." + _field);
BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Binding value '" << bindArg << "'";
query.bind(bindArg);
}
_queryModel.setQuery( query, true /* Keep columns */);
LMS_LOG(MOD_UI, SEV_DEBUG) << "Finish !";
}
// Get constraint created by this filter
void
TableFilterWidget::getConstraint(Constraint& constraint)
{
Wt::WModelIndexSet indexSet = _tableView->selectedIndexes();
// WHERE statement
WhereClause clause;
BOOST_FOREACH(Wt::WModelIndex index, indexSet) {
if (!index.isValid())
continue;
ResultType result = _queryModel.resultRow( index.row() );
// Get the track part
std::string name( result.get<0>() );
bool isAll( result.get<2>() ) ;
if (isAll) {
// no constraint, just return
return;
}
clause.Or(_table + "." + _field + " = ?").bind(name);
}
// Adding our WHERE clause
constraint.where.And( clause );
}
} // namespace UserInterface
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TABLE_VIEW_FILTER_WIDGET_HPP
#define TABLE_VIEW_FILTER_WIDGET_HPP
#include <Wt/Dbo/QueryModel>
#include <Wt/WTableView>
#include "FilterWidget.hpp"
#include "database/DatabaseHandler.hpp"
namespace UserInterface {
class TableFilterWidget : public FilterWidget
{
public:
TableFilterWidget(Database::Handler& db, std::string table, std::string field, Wt::WContainerWidget* parent = 0);
// Set constraints on this filter
virtual void refresh(const Constraint& constraint);
// Get constraints created by this filter
virtual void getConstraint(Constraint& constraint);
protected:
Database::Handler& _db;
const std::string _table;
const std::string _field;
// Name, track count, special value that means 'all' if set to 1
typedef boost::tuple<std::string, int, int> ResultType;
Wt::Dbo::QueryModel< ResultType > _queryModel;
Wt::WTableView* _tableView;
};
} // namespace UserInterface
#endif
+159
View File
@@ -0,0 +1,159 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include <Wt/WItemDelegate>
#include <Wt/WBreak>
#include "logger/Logger.hpp"
#include "TrackWidget.hpp"
namespace UserInterface {
TrackWidget::TrackWidget( Database::Handler& db, Wt::WContainerWidget* parent)
: FilterWidget( parent ),
_db(db),
_tableView(nullptr),
_trackStats(nullptr)
{
// ----- TRACK -----
_queryModel.setQuery(_db.getSession().query<ResultType>("select track,release,artist from track,release,artist where track.release_id = release.id and track.artist_id = artist.id" ).orderBy("artist.name,release.name,track.disc_number,track.track_number"));
_queryModel.addColumn( "artist.name", "Artist" );
_queryModel.addColumn( "release.name", "Album" );
_queryModel.addColumn( "track.disc_number", "Disc #" );
_queryModel.addColumn( "track.track_number", "Track #" );
_queryModel.addColumn( "track.name", "Track" );
_queryModel.addColumn( "track.duration", "Duration" );
_queryModel.addColumn( "track.creation_time", "Date" );
_queryModel.addColumn( "track.genre_list", "Genres" );
_queryModel.setBatchSize(1000);
_tableView = new Wt::WTableView( this );
_tableView->resize(Wt::WLength::Auto, 400);
_tableView->setSortingEnabled(true);
_tableView->setSelectionMode(Wt::SingleSelection);
_tableView->setAlternatingRowColors(true);
_tableView->setModel(&_queryModel);
{
Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this);
delegate->setTextFormat("yyyy");
_tableView->setItemDelegateForColumn(6, delegate);
}
{
// TODO better handle 1 hour+ files!
Wt::WItemDelegate *delegate = new Wt::WItemDelegate(this);
delegate->setTextFormat("mm:ss");
_tableView->setItemDelegateForColumn(5, delegate);
}
// TODO other event!
_tableView->selectionChanged().connect(this, &TrackWidget::handleTrackSelected);
new Wt::WBreak(this);
_trackStats = new Wt::WText(this);
updateStats();
}
void
TrackWidget::updateStats(void)
{
std::ostringstream oss; oss << "Files :" << _tableView->model()->rowCount();
// TODO from UTF-8 ?
_trackStats->setText(oss.str());
}
// Set constraints created by parent filters
void
TrackWidget::refresh(const Constraint& constraint)
{
SqlQuery sqlQuery;
sqlQuery.select( "track,release,artist" );
sqlQuery.from().And( FromClause("artist,release,track,genre,track_genre"));
sqlQuery.where().And(constraint.where);
LMS_LOG(MOD_UI, SEV_DEBUG) << "TRACK REQ = '" << sqlQuery.get() << "'";
Wt::Dbo::Query<ResultType> query = _db.getSession().query<ResultType>( sqlQuery.get() );
query.groupBy("track").orderBy("artist.name,track.creation_time,release.name,track.disc_number,track.track_number");
BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) {
LMS_LOG(MOD_UI, SEV_DEBUG) << "Binding value '" << bindArg << "'";
query.bind(bindArg);
}
_queryModel.setQuery( query, true );
updateStats();
}
void
TrackWidget::handleTrackSelected(void)
{
Wt::WModelIndexSet indexSet = _tableView->selectedIndexes();
if (!indexSet.empty()) {
Wt::WModelIndex currentIndex( *indexSet.begin() );
// Check there are remainin tracks!
if (currentIndex.isValid())
{
ResultType result = _queryModel.resultRow( currentIndex.row());
// Get the track part
Wt::Dbo::ptr<Database::Track> track ( result.get<0>() );
_trackSelected.emit( track->getPath() );
}
}
}
void
TrackWidget::selectNextTrack(void)
{
Wt::WModelIndexSet indexSet = _tableView->selectedIndexes();
if (!indexSet.empty()) {
Wt::WModelIndex currentIndex( *indexSet.begin() );
// Check there are remainin tracks!
if (currentIndex.isValid() && _tableView->model()->rowCount() > currentIndex.row() + 1)
{
_tableView->select( _tableView->model()->index( currentIndex.row() + 1, currentIndex.column()));
ResultType result = _queryModel.resultRow( currentIndex.row() + 1 );
// Get the track part
Wt::Dbo::ptr<Database::Track> track ( result.get<0>() );
_trackSelected.emit( track->getPath() );
}
}
}
} // namespace UserInterface
+71
View File
@@ -0,0 +1,71 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TRACK_WIDGET_HPP
#define TRACK_WIDGET_HPP
#include <Wt/WTableView>
#include <Wt/Dbo/QueryModel>
#include "database/DatabaseHandler.hpp"
#include "database/AudioTypes.hpp"
#include "FilterWidget.hpp"
namespace UserInterface {
class TrackWidget : public FilterWidget
{
public:
TrackWidget( Database::Handler& db, Wt::WContainerWidget* parent = 0);
// Set constraints created by parent filters
void refresh(const Constraint& constraint);
// Create constraints for child filters (N/A)
void getConstraint(Constraint& constraint) {}
void selectNextTrack(void); // Emit a trackSelected() if success
// Signals
Wt::Signal< boost::filesystem::path >& trackSelected() { return _trackSelected; }
private:
void handleTrackSelected();
Wt::Signal< boost::filesystem::path > _trackSelected;
Database::Handler& _db;
typedef boost::tuple<Database::Track::pointer, Database::Release::pointer, Database::Artist::pointer> ResultType;
Wt::Dbo::QueryModel< ResultType > _queryModel;
Wt::WTableView* _tableView;
void updateStats(void);
Wt::WText* _trackStats;
};
} // namespace UserInterface
#endif
+46
View File
@@ -0,0 +1,46 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "LmsAuth.hpp"
namespace UserInterface {
LmsAuth::LmsAuth(Database::Handler& db)
: Wt::Auth::AuthWidget(db.getAuthService(),
db.getUserDatabase(),
db.getLogin())
{
}
void
LmsAuth::createLoggedInView()
{
hide();
}
void
LmsAuth::createLoginView()
{
show();
Wt::Auth::AuthWidget::createLoginView();
}
} // namespace UserInterface
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_AUTH_HPP
#define UI_AUTH_HPP
#include <Wt/Auth/AuthService>
#include <Wt/Auth/AbstractUserDatabase>
#include <Wt/Auth/Login>
#include <Wt/Auth/AuthWidget>
#include <Wt/WContainerWidget>
#include "database/DatabaseHandler.hpp"
namespace UserInterface {
class LmsAuth : public Wt::Auth::AuthWidget
{
public:
LmsAuth(Database::Handler& db);
// LoggedInView is delegated to LmsHome
void createLoggedInView () ;
void createLoginView ();
private:
};
} // namespace UserInterface
#endif
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/filesystem.hpp>
#include "DirectoryValidator.hpp"
namespace UserInterface {
DirectoryValidator::DirectoryValidator(Wt::WObject *parent)
: Wt::WValidator(parent)
{
}
Wt::WValidator::Result
DirectoryValidator::validate(const Wt::WString& input) const
{
if (input.empty())
return Wt::WValidator::validate(input);
boost::filesystem::path p(input.toUTF8());
boost::system::error_code ec;
bool res = boost::filesystem::is_directory(p, ec);
if (ec)
return Wt::WValidator::Result(Wt::WValidator::Invalid, ec.message());
else if (res)
return Wt::WValidator::Result(Wt::WValidator::Valid);
else
return Wt::WValidator::Result(Wt::WValidator::Invalid, "Not a directory");
}
} // namespace UserInterface
+33
View File
@@ -0,0 +1,33 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WValidator>
namespace UserInterface {
class DirectoryValidator : public Wt::WValidator
{
public:
DirectoryValidator(Wt::WObject *parent = 0);
Wt::WValidator::Result validate(const Wt::WString& input) const;
};
} // namespace UserInterface
+30
View File
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "SessionData.hpp"
namespace UserInterface {
SessionData::SessionData(boost::filesystem::path dbPath)
: _db(dbPath)
{
}
} // namespace UserInterface
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_SESSION_DATA_HPP
#define UI_SESSION_DATA_HPP
#include <boost/filesystem.hpp>
#include "database/DatabaseHandler.hpp"
namespace UserInterface {
class SessionData
{
public:
SessionData(boost::filesystem::path dbPath);
Database::Handler& getDatabaseHandler() { return _db;}
const Database::Handler& getDatabaseHandler() const { return _db;}
private:
Database::Handler _db;
};
} // namespace UserInterface
#endif
+42
View File
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WRegExpValidator>
#include <Wt/WLengthValidator>
#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
@@ -0,0 +1,100 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/Http/Request>
#include <Wt/Http/Response>
#include "logger/Logger.hpp"
#include "AvConvTranscodeStreamResource.hpp"
namespace UserInterface {
AvConvTranscodeStreamResource::AvConvTranscodeStreamResource(const Transcode::Parameters& parameters, Wt::WObject *parent)
: Wt::WResource(parent),
_parameters( parameters )
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "CONSTRUCTING RESOURCE";
}
AvConvTranscodeStreamResource::~AvConvTranscodeStreamResource()
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "DESTRUCTING RESOURCE";
beingDeleted();
}
void
AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request,
Wt::Http::Response& response)
{
// see if this request is for a continuation:
Wt::Http::ResponseContinuation *continuation = request.continuation();
std::shared_ptr<Transcode::AvConvTranscoder> transcoder;
if (continuation)
transcoder = boost::any_cast<std::shared_ptr<Transcode::AvConvTranscoder> >(continuation->data());
if (!transcoder)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "Launching transcoder";
transcoder = std::make_shared<Transcode::AvConvTranscoder>( _parameters);
response.setMimeType(_parameters.getOutputFormat().getMimeType());
}
Transcode::AvConvTranscoder::data_type& data = transcoder->getOutputData();
while (!transcoder->isComplete() && data.size() < _bufferSize)
transcoder->process();
// Give the client all the output data
Transcode::AvConvTranscoder::data_type::const_iterator it = data.begin();
bool copySuccess = true;
std::size_t copiedSize = 0;
for (Transcode::AvConvTranscoder::data_type::const_iterator it = data.begin(); it != data.end(); ++it)
{
if (!response.out().put(*it)) {
copySuccess = false;
break;
}
else
copiedSize++;
}
LMS_LOG(MOD_UI, SEV_DEBUG) << "Wrote " << copiedSize << " bytes";
if (!copySuccess)
LMS_LOG(MOD_UI, SEV_ERROR) << "** Write failed!";
// Consume copied bytes
data.erase(data.begin(), data.begin() + copiedSize);
if (copySuccess && !transcoder->isComplete()) {
continuation = response.createContinuation();
continuation->setData(transcoder);
LMS_LOG(MOD_UI, SEV_DEBUG) << "Continuation set!";
}
else
LMS_LOG(MOD_UI, SEV_DEBUG) << "No more data!";
}
} // namespace UserInterface
@@ -0,0 +1,52 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef AVCONV_TRANSCODE_STREAM_RESOURCE_HPP
#define AVCONV_TRANSCODE_STREAM_RESOURCE_HPP
#include <iostream>
#include <memory>
#include <Wt/WResource>
#include "transcode/AvConvTranscoder.hpp"
#include "transcode/Parameters.hpp"
namespace UserInterface {
class AvConvTranscodeStreamResource : public Wt::WResource
{
public:
AvConvTranscodeStreamResource(const Transcode::Parameters& parameters, Wt::WObject *parent = 0);
~AvConvTranscodeStreamResource();
void handleRequest(const Wt::Http::Request& request,
Wt::Http::Response& response);
private:
Transcode::Parameters _parameters;
std::shared_ptr<Transcode::AvConvTranscoder> _transcoder;
static const std::size_t _bufferSize = 8192*16;
};
} // namespace UserInterface
#endif
+113
View File
@@ -0,0 +1,113 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WMenu>
#include <Wt/WStackedWidget>
#include <Wt/WTextArea>
#include <Wt/WHBoxLayout>
#include "SettingsAudioFormView.hpp"
#include "SettingsUserFormView.hpp"
#include "SettingsAccountFormView.hpp"
#include "SettingsDatabaseFormView.hpp"
#include "SettingsMediaDirectories.hpp"
#include "SettingsUsers.hpp"
#include "logger/Logger.hpp"
#include "service/ServiceManager.hpp"
#include "service/DatabaseUpdateService.hpp"
#include "Settings.hpp"
namespace UserInterface {
namespace Settings {
Settings::Settings(SessionData& sessionData, Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent),
_sessionData(sessionData)
{
Wt::WHBoxLayout* hLayout = new Wt::WHBoxLayout(this);
// Create a stack where the contents will be located.
Wt::WStackedWidget *contents = new Wt::WStackedWidget();
contents->setStyleClass("contents");
contents->setOverflow(WContainerWidget::OverflowHidden);
Wt::WMenu *menu = new Wt::WMenu(contents, Wt::Vertical);
menu->setStyleClass("nav nav-pills nav-stacked submenu");
menu->setWidth(150);
hLayout->addWidget(menu);
hLayout->addWidget(contents, 1);
Wt::Dbo::Transaction transaction( sessionData.getDatabaseHandler().getSession());
::Database::User::pointer user = sessionData.getDatabaseHandler().getCurrentUser();
// Must be logged in here
assert(user);
menu->addItem("Audio", new AudioFormView(sessionData, Database::User::getId(user)));
if (user->isAdmin())
{
MediaDirectories* mediaDirectory = new MediaDirectories(sessionData);
mediaDirectory->changed().connect(this, &Settings::handleDatabaseDirectoriesChanged);
menu->addItem("Media Folders", mediaDirectory);
DatabaseFormView* databaseFormView = new DatabaseFormView(sessionData);
databaseFormView->changed().connect(this, &Settings::restartDatabaseUpdateService);
menu->addItem("Database Update", databaseFormView);
menu->addItem("Users", new Users(sessionData));
}
else
{
menu->addItem("Account", new AccountFormView(sessionData, Database::User::getId(user)));
}
}
void
Settings::handleDatabaseDirectoriesChanged()
{
LMS_LOG(MOD_UI, SEV_NOTICE) << "Media directories have changed: requesting imediate scan";
// On directory add or delete, request an immediate scan
{
Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession());
Database::MediaDirectorySettings::get(_sessionData.getDatabaseHandler().getSession()).modify()->setManualScanRequested(true);
}
restartDatabaseUpdateService();
}
void
Settings::restartDatabaseUpdateService()
{
// Restarting the update service
boost::lock_guard<boost::mutex> serviceLock (Service::ServiceManager::instance().mutex());
Service::DatabaseUpdateService::pointer service = Service::ServiceManager::instance().getService<Service::DatabaseUpdateService>();
if (service)
service->restart();
}
} // namespace Settings
} // namespace UserInterface
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WContainerWidget>
#include "common/SessionData.hpp"
namespace UserInterface {
namespace Settings {
class Settings : public Wt::WContainerWidget
{
public:
Settings(SessionData& sessionData, Wt::WContainerWidget* parent = 0);
private:
void handleDatabaseDirectoriesChanged();
void restartDatabaseUpdateService(void);
SessionData& _sessionData;
};
} // namespace Settings
} // namespace UserInterface
+287
View File
@@ -0,0 +1,287 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <vector>
#include <Wt/WStringListModel>
#include <Wt/WFormModel>
#include <Wt/WLineEdit>
#include <Wt/WCheckBox>
#include <Wt/WComboBox>
#include <Wt/WPushButton>
#include <Wt/Auth/Identity>
#include "logger/Logger.hpp"
#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)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
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
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_SETTINGS_ACCOUNT_FORM_VIEW_HPP
#define UI_SETTINGS_ACCOUNT_FORM_VIEW_HPP
#include <Wt/WContainerWidget>
#include <Wt/WTemplateFormView>
#include <Wt/WText>
#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
+199
View File
@@ -0,0 +1,199 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <vector>
#include <Wt/WStringListModel>
#include <Wt/WFormModel>
#include <Wt/WComboBox>
#include <Wt/WPushButton>
#include "logger/Logger.hpp"
#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)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
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
+51
View File
@@ -0,0 +1,51 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_SETTINGS_AUDIO_FORM_VIEW_HPP
#define UI_SETTINGS_AUDIO_ACCOUNT_FORM_VIEW_HPP
#include <Wt/WContainerWidget>
#include <Wt/WTemplateFormView>
#include <Wt/WText>
#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
@@ -0,0 +1,354 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WString>
#include <Wt/WPushButton>
#include <Wt/WCheckBox>
#include <Wt/WComboBox>
#include <Wt/WMessageBox>
#include <Wt/WFormModel>
#include <Wt/WStringListModel>
#include "logger/Logger.hpp"
#include "database/MediaDirectory.hpp"
#include "common/DirectoryValidator.hpp"
#include "SettingsDatabaseFormView.hpp"
namespace UserInterface {
namespace Settings {
class DatabaseFormModel : public Wt::WFormModel
{
public:
// Associate each field with a unique string literal.
static const Field UpdatePeriodField;
static const Field UpdateStartTimeField;
DatabaseFormModel(SessionData& sessionData, Wt::WObject *parent = 0)
: Wt::WFormModel(parent),
_sessionData(sessionData)
{
initializeModels();
addField(UpdatePeriodField);
addField(UpdateStartTimeField);
setValidator(UpdatePeriodField, createUpdatePeriodValidator());
setValidator(UpdateStartTimeField, createStartTimeValidator());
// populate the model with initial data
loadData();
}
Wt::WAbstractItemModel *updatePeriodModel() { return _updatePeriodModel; }
Wt::WAbstractItemModel *updateStartTimeModel() { return _updateStartTimeModel; }
void loadData()
{
Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession());
// Get refresh settings
::Database::MediaDirectorySettings::pointer settings = ::Database::MediaDirectorySettings::get(_sessionData.getDatabaseHandler().getSession());
int periodRow = getUpdatePeriodModelRow( settings->getUpdatePeriod() );
if (periodRow != -1)
setValue(UpdatePeriodField, updatePeriod(periodRow));
int startTimeRow = getUpdateStartTimeModelRow( settings->getUpdateStartTime() );
if (startTimeRow != -1)
setValue(UpdateStartTimeField, updateStartTime( startTimeRow ) );
}
void saveData()
{
Wt::Dbo::Session& session( _sessionData.getDatabaseHandler().getSession());
Wt::Dbo::Transaction transaction(session);
::Database::MediaDirectorySettings::pointer settings = ::Database::MediaDirectorySettings::get(_sessionData.getDatabaseHandler().getSession() );
int periodRow = getUpdatePeriodModelRow( boost::any_cast<Wt::WString>(value(UpdatePeriodField)));
assert(periodRow != -1);
settings.modify()->setUpdatePeriod( updatePeriodDuration( periodRow ) );
int startTimeRow = getUpdateStartTimeModelRow( boost::any_cast<Wt::WString>(value(UpdateStartTimeField)));
assert(startTimeRow != -1);
settings.modify()->setUpdateStartTime( updateStartTimeDuration( startTimeRow ) );
}
bool setImmediateScan(Wt::WString& error)
{
try {
Wt::Dbo::Session& session( _sessionData.getDatabaseHandler().getSession());
Wt::Dbo::Transaction transaction(session);
::Database::MediaDirectorySettings::pointer settings = ::Database::MediaDirectorySettings::get(_sessionData.getDatabaseHandler().getSession() );
settings.modify()->setManualScanRequested( true );
}
catch(Wt::Dbo::Exception& exception)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
return false;
}
return true;
}
int getUpdatePeriodModelRow(Wt::WString value)
{
for (int i = 0; i < _updatePeriodModel->rowCount(); ++i)
{
if (updatePeriod(i) == value)
return i;
}
return -1;
}
int getUpdatePeriodModelRow(Database::MediaDirectorySettings::UpdatePeriod duration)
{
for (int i = 0; i < _updatePeriodModel->rowCount(); ++i)
{
if (updatePeriodDuration(i) == duration)
return i;
}
return -1;
}
Database::MediaDirectorySettings::UpdatePeriod updatePeriodDuration(int row) {
return boost::any_cast<Database::MediaDirectorySettings::UpdatePeriod>
(_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::UserRole));
}
Wt::WString updatePeriod(int row) {
return boost::any_cast<Wt::WString>
(_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::DisplayRole));
}
int getUpdateStartTimeModelRow(Wt::WString value)
{
for (int i = 0; i < _updateStartTimeModel->rowCount(); ++i)
{
if (updateStartTime(i) == value)
return i;
}
return -1;
}
int getUpdateStartTimeModelRow(boost::posix_time::time_duration duration)
{
for (int i = 0; i < _updateStartTimeModel->rowCount(); ++i)
{
if (updateStartTimeDuration(i) == duration)
return i;
}
return -1;
}
boost::posix_time::time_duration updateStartTimeDuration(int row) {
return boost::any_cast<boost::posix_time::time_duration>
(_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::UserRole));
}
Wt::WString updateStartTime(int row) {
return boost::any_cast<Wt::WString>
(_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::DisplayRole));
}
private:
void initializeModels() {
_updatePeriodModel = new Wt::WStringListModel(this);
_updatePeriodModel->addString("Never");
_updatePeriodModel->setData(0, 0, Database::MediaDirectorySettings::Never, Wt::UserRole);
_updatePeriodModel->addString("Daily");
_updatePeriodModel->setData(1, 0, Database::MediaDirectorySettings::Daily, Wt::UserRole);
_updatePeriodModel->addString("Weekly");
_updatePeriodModel->setData(2, 0, Database::MediaDirectorySettings::Weekly, Wt::UserRole);
_updatePeriodModel->addString("Monthly");
_updatePeriodModel->setData(3, 0, Database::MediaDirectorySettings::Monthly, Wt::UserRole);
_updateStartTimeModel = new Wt::WStringListModel(this);
for (std::size_t i = 0; i < 24; ++i)
{
boost::posix_time::time_duration dur = boost::posix_time::hours(i);
boost::posix_time::time_facet* facet = new boost::posix_time::time_facet("%H:%M");
facet->time_duration_format("%H:%M");
std::ostringstream oss;
oss.imbue(std::locale(oss.getloc(), facet));
oss << dur;
_updateStartTimeModel->addString( oss.str() );
_updateStartTimeModel->setData(i, 0, dur, Wt::UserRole);
}
}
Wt::WValidator *createUpdatePeriodValidator() {
Wt::WValidator* v = new Wt::WValidator();
v->setMandatory(true);
return v;
}
Wt::WValidator *createStartTimeValidator() {
Wt::WValidator* v = new Wt::WValidator();
v->setMandatory(true);
return v;
}
SessionData& _sessionData;
Wt::WStringListModel* _updatePeriodModel;
Wt::WStringListModel* _updateStartTimeModel;
};
const Wt::WFormModel::Field DatabaseFormModel::UpdatePeriodField = "update-period";
const Wt::WFormModel::Field DatabaseFormModel::UpdateStartTimeField = "update-start-time";
DatabaseFormView::DatabaseFormView(SessionData& sessionData, Wt::WContainerWidget *parent)
: Wt::WTemplateFormView(parent)
{
_model = new DatabaseFormModel(sessionData, this);
setTemplateText(tr("databaseForm-template"));
addFunction("id", &WTemplate::Functions::id);
addFunction("block", &WTemplate::Functions::id);
_applyInfo = new Wt::WText();
_applyInfo->setInline(false);
_applyInfo->hide();
bindWidget("apply-info", _applyInfo);
// 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);
// Title & Buttons
bindString("title", "Media folder settings");
Wt::WPushButton *saveButton = new Wt::WPushButton("Apply");
bindWidget("apply-button", saveButton);
saveButton->setStyleClass("btn-primary");
Wt::WPushButton *discardButton = new Wt::WPushButton("Discard");
bindWidget("discard-button", discardButton);
saveButton->clicked().connect(this, &DatabaseFormView::processSave);
discardButton->clicked().connect(this, &DatabaseFormView::processDiscard);
Wt::WPushButton *immediateScanButton = new Wt::WPushButton("Immediate scan");
immediateScanButton->setStyleClass("btn-warning");
bindWidget("immediate-scan-button", immediateScanButton);
immediateScanButton->clicked().connect(this, &DatabaseFormView::processImmediateScan);
updateView(_model);
}
void
DatabaseFormView::processImmediateScan()
{
Wt::WString error;
_applyInfo->show();
if (_model->setImmediateScan(error))
{
_applyInfo->setText( Wt::WString::fromUTF8("Media folder scan has been started!" ) );
_applyInfo->setStyleClass("alert alert-warning");
_sigChanged.emit();
}
else
{
_applyInfo->setText( error );
_applyInfo->setStyleClass("alert alert-danger");
}
}
void
DatabaseFormView::processDiscard()
{
_applyInfo->show();
_applyInfo->setText( Wt::WString::fromUTF8("Parameters reverted!"));
_applyInfo->setStyleClass("alert alert-info");
_model->loadData();
_model->validate();
updateView(_model);
}
void
DatabaseFormView::processSave()
{
updateModel(_model);
_applyInfo->show();
if (_model->validate()) {
// Make the model to commit data into DB
_model->saveData();
_sigChanged.emit();
_applyInfo->setText( Wt::WString::fromUTF8("New parameters successfully applied!"));
_applyInfo->setStyleClass("alert alert-success");
}
else {
_applyInfo->setText( Wt::WString::fromUTF8("Cannot apply new parameters!"));
_applyInfo->setStyleClass("alert alert-danger");
}
// Udate the view: Delete any validation message in the view, etc.
updateView(_model);
}
} // namespace Settings
} // namespace UserInterface
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_SETTINGS_DB_FORM_VIEW_HPP
#define UI_SETTINGS_DB_FORM_VIEW_HPP
#include <Wt/WContainerWidget>
#include <Wt/WTemplateFormView>
#include <Wt/WText>
#include <Wt/WSignal>
#include "common/SessionData.hpp"
namespace UserInterface {
namespace Settings {
class DatabaseFormModel;
class DatabaseFormView : public Wt::WTemplateFormView
{
public:
DatabaseFormView(SessionData& sessionData, Wt::WContainerWidget *parent = 0);
Wt::Signal<void>& changed() { return _sigChanged; }
private:
Wt::Signal<void> _sigChanged;
void processSave();
void processDiscard();
void processImmediateScan();
Wt::WText *_applyInfo;
DatabaseFormModel *_model;
};
} // namespace Settings
} // namespace UserInterface
#endif
@@ -0,0 +1,230 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <vector>
#include <Wt/WFormModel>
#include <Wt/WLineEdit>
#include <Wt/WPushButton>
#include <Wt/Auth/Identity>
#include "logger/Logger.hpp"
#include "common/Validators.hpp"
#include "SettingsFirstConnectionFormView.hpp"
namespace UserInterface {
namespace Settings {
class FirstConnectionFormModel : 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;
FirstConnectionFormModel(SessionData& sessionData, Wt::WObject *parent = 0)
: Wt::WFormModel(parent),
_db(sessionData.getDatabaseHandler())
{
addField(NameField);
addField(EmailField);
addField(PasswordField);
addField(PasswordConfirmField);
setValidator(NameField, createNameValidator());
setValidator(EmailField, createEmailValidator());
}
bool saveData(Wt::WString& error)
{
// DBO transaction active here
try {
Wt::Dbo::Transaction transaction(_db.getSession());
// Check if a user already exist
// If it's the case, just do nothing
if (Database::User::getAll(_db.getSession()).size() > 0)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Admin user already created";
error = Wt::WString("Admin user already created!");
return false;
}
// Create user
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());
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField));
user.modify()->setAdmin( true );
}
catch(Wt::Dbo::Exception& exception)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
error = Wt::WString(exception.what());
return false;
}
return true;
}
bool validateField(Field field)
{
// DBO transaction active here
Wt::WString error;
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;
};
const Wt::WFormModel::Field FirstConnectionFormModel::NameField = "name";
const Wt::WFormModel::Field FirstConnectionFormModel::EmailField = "email";
const Wt::WFormModel::Field FirstConnectionFormModel::PasswordField = "password";
const Wt::WFormModel::Field FirstConnectionFormModel::PasswordConfirmField = "password-confirm";
FirstConnectionFormView::FirstConnectionFormView(SessionData& sessionData, Wt::WContainerWidget *parent)
: Wt::WTemplateFormView(parent)
{
_model = new FirstConnectionFormModel(sessionData, this);
setTemplateText(tr("firstConnectionForm-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(FirstConnectionFormModel::NameField, accountEdit);
accountEdit->changed().connect(_applyInfo, &Wt::WWidget::hide);
// Email
Wt::WLineEdit* emailEdit = new Wt::WLineEdit();
setFormWidget(FirstConnectionFormModel::EmailField, emailEdit);
emailEdit->changed().connect(_applyInfo, &Wt::WWidget::hide);
// Password
Wt::WLineEdit* passwordEdit = new Wt::WLineEdit();
setFormWidget(FirstConnectionFormModel::PasswordField, passwordEdit );
passwordEdit->setEchoMode(Wt::WLineEdit::Password);
passwordEdit->changed().connect(_applyInfo, &Wt::WWidget::hide);
// Password confirmation
Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit();
setFormWidget(FirstConnectionFormModel::PasswordConfirmField, passwordConfirmEdit);
passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password);
passwordConfirmEdit->changed().connect(_applyInfo, &Wt::WWidget::hide);
// Title & Buttons
bindString("title", "Create Admin account");
_saveButton = new Wt::WPushButton("Create");
_saveButton->setStyleClass("btn-primary");
bindWidget("save-button", _saveButton);
_saveButton->clicked().connect(this, &FirstConnectionFormView::processSave);
updateView(_model);
}
void
FirstConnectionFormView::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! Please refresh this page in order to login"));
_applyInfo->setStyleClass("alert alert-success");
_saveButton->hide();
}
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
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_SETTINGS_FIRST_CONNECTION_FORM_VIEW_HPP
#define UI_SETTINGS_FIRST_CONNECTION_FORM_VIEW_HPP
#include <Wt/WContainerWidget>
#include <Wt/WTemplateFormView>
#include <Wt/WText>
#include <Wt/WPushButton>
#include "common/SessionData.hpp"
namespace UserInterface {
namespace Settings {
class FirstConnectionFormModel;
class FirstConnectionFormView : public Wt::WTemplateFormView
{
public:
FirstConnectionFormView(SessionData& sessionData, Wt::WContainerWidget *parent = 0);
private:
void processSave();
Wt::WPushButton* _saveButton;
FirstConnectionFormModel* _model;
Wt::WText* _applyInfo;
};
} // namespace Settings
} // namespace UserInterface
#endif
@@ -0,0 +1,171 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WGroupBox>
#include <Wt/WText>
#include <Wt/WPushButton>
#include <Wt/WMessageBox>
#include "database/MediaDirectory.hpp"
#include "SettingsMediaDirectoryFormView.hpp"
#include "SettingsMediaDirectories.hpp"
namespace UserInterface {
namespace Settings {
MediaDirectories::MediaDirectories(SessionData& sessionData, Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_db(sessionData.getDatabaseHandler())
{
// Stack two widgets:
_stack = new Wt::WStackedWidget(this);
// 1/ the media directory table view
{
Wt::WGroupBox *container = new Wt::WGroupBox("Media Folders", _stack);
_table = new Wt::WTable(container);
_table->addStyleClass("table form-inline");
_table->toggleStyleClass("table-hover", true);
_table->toggleStyleClass("table-striped", true);
_table->setHeaderCount(1);
_table->elementAt(0, 0)->addWidget(new Wt::WText("#"));
_table->elementAt(0, 1)->addWidget(new Wt::WText("Path"));
_table->elementAt(0, 2)->addWidget(new Wt::WText("Type"));
Wt::WPushButton* addBtn = new Wt::WPushButton("Add Folder");
addBtn->setStyleClass("btn-success");
container->addWidget( addBtn );
addBtn->clicked().connect(boost::bind(&MediaDirectories::handleCreateMediaDirectory, this));
}
refresh();
}
void
MediaDirectories::refresh(void)
{
assert(_table->rowCount() > 0);
for (int i = _table->rowCount() - 1; i > 0; --i)
_table->deleteRow(i);
Wt::Dbo::Transaction transaction(_db.getSession());
std::vector<Database::MediaDirectory::pointer> mediaDirectories = Database::MediaDirectory::getAll(_db.getSession());
std::size_t id = 1;
BOOST_FOREACH(Database::MediaDirectory::pointer mediaDirectory, mediaDirectories)
{
_table->elementAt(id, 0)->addWidget(new Wt::WText( Wt::WString::fromUTF8("{1}").arg(id)));
_table->elementAt(id, 1)->addWidget(new Wt::WText( Wt::WString::fromUTF8(mediaDirectory->getPath().string()) ));
Wt::WString dirType;
switch(mediaDirectory->getType())
{
case Database::MediaDirectory::Video: dirType = "Video"; break;
case Database::MediaDirectory::Audio: dirType = "Audio"; break;
}
_table->elementAt(id, 2)->addWidget( new Wt::WText( dirType ));
Wt::WPushButton* delBtn = new Wt::WPushButton("Delete");
delBtn->setStyleClass("btn-danger");
_table->elementAt(id, 3)->addWidget(delBtn);
delBtn->clicked().connect(boost::bind( &MediaDirectories::handleDelMediaDirectory, this, mediaDirectory->getPath(), mediaDirectory->getType() ) );
++id;
}
}
void
MediaDirectories::handleDelMediaDirectory(boost::filesystem::path p, Database::MediaDirectory::Type type)
{
Wt::WMessageBox *messageBox = new Wt::WMessageBox
("Delete Folder",
Wt::WString( "Deleting folder '{1}'?").arg(p.string()),
Wt::Question, Wt::Yes | Wt::No);
messageBox->setModal(true);
messageBox->buttonClicked().connect(std::bind([=] () {
if (messageBox->buttonResult() == Wt::Yes)
{
{
Wt::Dbo::Transaction transaction(_db.getSession());
// Delete the media diretory
Database::MediaDirectory::pointer mediaDirectory = Database::MediaDirectory::get(_db.getSession(), p, type);
if (mediaDirectory)
mediaDirectory.remove();
}
refresh();
// Emit something changed in the settings
_sigChanged.emit();
}
delete messageBox;
}));
messageBox->show();
}
void
MediaDirectories::handleCreateMediaDirectory(void)
{
assert(_stack->count() == 1);
MediaDirectoryFormView* formView = new MediaDirectoryFormView(_db, _stack);
formView->completed().connect(this, &MediaDirectories::handleMediaDirectoryFormCompleted);
_stack->setCurrentIndex(1);
}
void
MediaDirectories::handleMediaDirectoryFormCompleted(bool changed)
{
_stack->setCurrentIndex(0);
if (changed)
{
// Refresh the user table if a change has been made
refresh();
// Emit something changed in the settings
_sigChanged.emit();
}
// Delete the form view
delete _stack->widget(1);
}
} // namespace UserInterface
} // namespace Settings
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_SETTINGS_MEDIA_DIRECTORIES_HPP
#define UI_SETTINGS_MEDIA_DIRECTORIES_HPP
#include <Wt/WStackedWidget>
#include <Wt/WContainerWidget>
#include <Wt/WTable>
#include <Wt/WSignal>
#include "database/MediaDirectory.hpp"
#include "common/SessionData.hpp"
namespace UserInterface {
namespace Settings {
class MediaDirectories : public Wt::WContainerWidget
{
public:
MediaDirectories(SessionData& sessioNData, Wt::WContainerWidget *parent = 0);
void refresh();
Wt::Signal<void>& changed() { return _sigChanged; }
private:
Wt::Signal<void> _sigChanged;
void handleMediaDirectoryFormCompleted(bool changed);
void handleDelMediaDirectory(boost::filesystem::path p, Database::MediaDirectory::Type type);
void handleCreateMediaDirectory(void);
Database::Handler& _db;
Wt::WStackedWidget* _stack;
Wt::WTable* _table;
};
} // namespace UserInterface
} // namespace Settings
#endif
@@ -0,0 +1,186 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <vector>
#include <Wt/WStringListModel>
#include <Wt/WLineEdit>
#include <Wt/WFormModel>
#include <Wt/WComboBox>
#include <Wt/WPushButton>
#include "logger/Logger.hpp"
#include "database/MediaDirectory.hpp"
#include "common/DirectoryValidator.hpp"
#include "SettingsMediaDirectoryFormView.hpp"
namespace UserInterface {
namespace Settings {
// TODO validate if directory already exists
class MediaDirectoryFormModel : public Wt::WFormModel
{
public:
// Associate each field with a unique string literal.
static const Field PathField;
static const Field TypeField;
MediaDirectoryFormModel(Database::Handler& db, Wt::WObject *parent = 0)
: Wt::WFormModel(parent),
_db(db)
{
initializeModels();
addField(PathField);
addField(TypeField);
DirectoryValidator* dirValidator = new DirectoryValidator();
dirValidator->setMandatory(true);
setValidator(PathField, dirValidator);
setValidator(TypeField, new Wt::WValidator(true)); // mandatory
}
Wt::WAbstractItemModel *typeModel() { return _typeModel; }
bool saveData(Wt::WString& error)
{
try {
Wt::Dbo::Transaction transaction(_db.getSession());
Database::MediaDirectory::Type type
= (valueText(TypeField) == "Audio") ? Database::MediaDirectory::Audio : Database::MediaDirectory::Video;
if (Database::MediaDirectory::get(_db.getSession(), valueText(PathField).toUTF8(), type))
{
error = "This Path/Type already exists!";
return false;
}
Database::MediaDirectory::create(_db.getSession(), valueText(PathField).toUTF8(), type);
}
catch(Wt::Dbo::Exception& exception)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
return false;
}
return true;
}
private:
void initializeModels()
{
_typeModel = new Wt::WStringListModel(this);
_typeModel->addString("Audio");
_typeModel->addString("Video");
}
Database::Handler& _db;
std::string _userId;
Wt::WStringListModel* _typeModel;
};
const Wt::WFormModel::Field MediaDirectoryFormModel::PathField = "path";
const Wt::WFormModel::Field MediaDirectoryFormModel::TypeField = "type";
MediaDirectoryFormView::MediaDirectoryFormView(Database::Handler& db, Wt::WContainerWidget *parent)
: Wt::WTemplateFormView(parent)
{
_model = new MediaDirectoryFormModel(db, this);
setTemplateText(tr("mediaDirectoryForm-template"));
addFunction("id", &WTemplate::Functions::id);
addFunction("block", &WTemplate::Functions::id);
_applyInfo = new Wt::WText();
_applyInfo->setInline(false);
_applyInfo->hide();
bindWidget("apply-info", _applyInfo);
// Path
Wt::WLineEdit* pathEdit = new Wt::WLineEdit();
setFormWidget(MediaDirectoryFormModel::PathField, pathEdit);
pathEdit->changed().connect(_applyInfo, &Wt::WWidget::hide);
// Type
Wt::WComboBox *typeCB = new Wt::WComboBox();
setFormWidget(MediaDirectoryFormModel::TypeField, typeCB);
typeCB->setModel(_model->typeModel());
typeCB->changed().connect(_applyInfo, &Wt::WWidget::hide);
// Title & Buttons
bindString("title", "Add Media Folder");
Wt::WPushButton *saveButton = new Wt::WPushButton("Add");
saveButton->setStyleClass("btn-primary");
bindWidget("save-button", saveButton);
saveButton->clicked().connect(this, &MediaDirectoryFormView::processSave);
Wt::WPushButton *cancelButton = new Wt::WPushButton("Cancel");
bindWidget("cancel-button", cancelButton);
cancelButton->clicked().connect(this, &MediaDirectoryFormView::processCancel);
updateView(_model);
}
void
MediaDirectoryFormView::processCancel()
{
// parent widget will delete this widget
completed().emit(false);
}
void
MediaDirectoryFormView::processSave()
{
updateModel(_model);
_applyInfo->show();
if (_model->validate())
{
Wt::WString error;
// commit model into DB
if (_model->saveData(error) ) {
completed().emit(true);
return;
}
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
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_SETTINGS_MEDIA_DIR_FORM_VIEW_HPP
#define UI_SETTINGS_MEDIA_DIR_FORM_VIEW_HPP
#include <Wt/WContainerWidget>
#include <Wt/WTemplateFormView>
#include <Wt/WText>
#include <Wt/WSignal>
#include "common/SessionData.hpp"
namespace UserInterface {
namespace Settings {
class MediaDirectoryFormModel;
class MediaDirectoryFormView : public Wt::WTemplateFormView
{
public:
MediaDirectoryFormView(Database::Handler& db, Wt::WContainerWidget *parent = 0);
// Signal emitted once the form is completed
Wt::Signal<bool>& completed() { return _sigCompleted; }
private:
void processCancel(); // reload from DB
void processSave(); // commit into DB
Wt::Signal<bool> _sigCompleted;
MediaDirectoryFormModel* _model;
Wt::WText* _applyInfo;
};
} // namespace Settings
} // namespace UserInterface
#endif
+413
View File
@@ -0,0 +1,413 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <vector>
#include <Wt/WStringListModel>
#include <Wt/WFormModel>
#include <Wt/WLineEdit>
#include <Wt/WCheckBox>
#include <Wt/WComboBox>
#include <Wt/WPushButton>
#include <Wt/Auth/Identity>
#include "logger/Logger.hpp"
#include "common/Validators.hpp"
#include "SettingsUserFormView.hpp"
namespace UserInterface {
namespace Settings {
class UserFormModel : 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;
static const Field AdminField;
static const Field AudioBitrateLimitField;
static const Field VideoBitrateLimitField;
UserFormModel(SessionData& sessionData, std::string userId, Wt::WObject *parent = 0)
: Wt::WFormModel(parent),
_db(sessionData.getDatabaseHandler()),
_userId(userId)
{
initializeModels();
addField(NameField);
addField(EmailField);
addField(PasswordField);
addField(PasswordConfirmField);
addField(AdminField);
addField(AudioBitrateLimitField);
addField(VideoBitrateLimitField);
setValidator(NameField, createNameValidator());
setValidator(EmailField, createEmailValidator());
// If creating a user, passwords are mandatory
if (_userId.empty())
{
setValidator(PasswordField, new Wt::WValidator(true)); // mandatory
setValidator(PasswordConfirmField, new Wt::WValidator(true)); // mandatory
}
setValidator(AudioBitrateLimitField, new Wt::WValidator(true)); // mandatory
setValidator(VideoBitrateLimitField, new Wt::WValidator(true)); // mandatory
// populate the model with initial data
loadData(userId);
}
Wt::WAbstractItemModel *audioBitrateModel() { return _audioBitrateModel; }
Wt::WAbstractItemModel *videoBitrateModel() { return _videoBitrateModel; }
void loadData(std::string userId)
{
if (!userId.empty())
{
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 = _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());
}
}
}
bool saveData()
{
try {
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);
// Account
authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(NameField));
authUser.setEmail(valueText(EmailField).toUTF8());
Database::Handler::getPasswordService().updatePassword(authUser, valueText(PasswordField));
// Access
{
boost::any v = value(AdminField);
if (!v.empty() && boost::any_cast<bool>(v) == true)
user.modify()->setAdmin( true );
else
user.modify()->setAdmin( false );
}
if (!isReadOnly(AudioBitrateLimitField))
user.modify()->setMaxAudioBitrate( Wt::asNumber(value(AudioBitrateLimitField)) * 1000); // in bps
if (!isReadOnly(VideoBitrateLimitField))
user.modify()->setMaxVideoBitrate( Wt::asNumber(value(VideoBitrateLimitField)) * 1000); // in bps
transaction.commit();
}
else
{
// 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()) {
LMS_LOG(MOD_UI, SEV_ERROR) << "user identity does not exist!";
return false;
}
else if(!user)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "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));
// Access
if (!isReadOnly(AdminField))
{
boost::any v = value(AdminField);
if (!v.empty() && boost::any_cast<bool>(v) == true)
user.modify()->setAdmin( true );
else
user.modify()->setAdmin( false );
}
if (!isReadOnly(AudioBitrateLimitField))
user.modify()->setMaxAudioBitrate( Wt::asNumber(value(AudioBitrateLimitField)) * 1000); // in bps
if (!isReadOnly(VideoBitrateLimitField))
user.modify()->setMaxVideoBitrate( Wt::asNumber(value(VideoBitrateLimitField)) * 1000); // in bps
transaction.commit();
}
}
catch(Wt::Dbo::Exception& exception)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Dbo exception: " << exception.what();
return false;
}
return true;
}
bool validateField(Field field)
{
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:
void initializeModels()
{
// AUDIO
_audioBitrateModel = new Wt::WStringListModel();
BOOST_FOREACH(std::size_t bitrate, Database::User::audioBitrates)
_audioBitrateModel->addString( Wt::WString("{1}").arg( bitrate / 1000 ) ); // in kbps
// VIDEO
_videoBitrateModel = new Wt::WStringListModel();
BOOST_FOREACH(std::size_t bitrate, Database::User::videoBitrates)
_videoBitrateModel->addString( Wt::WString("{1}").arg( bitrate / 1000 ) ); // in kbps
}
Database::Handler& _db;
std::string _userId;
Wt::WStringListModel* _audioBitrateModel;
Wt::WStringListModel* _videoBitrateModel;
};
const Wt::WFormModel::Field UserFormModel::NameField = "name";
const Wt::WFormModel::Field UserFormModel::EmailField = "email";
const Wt::WFormModel::Field UserFormModel::PasswordField = "password";
const Wt::WFormModel::Field UserFormModel::PasswordConfirmField = "password-confirm";
const Wt::WFormModel::Field UserFormModel::AdminField = "admin";
const Wt::WFormModel::Field UserFormModel::AudioBitrateLimitField = "audio-bitrate-limit";
const Wt::WFormModel::Field UserFormModel::VideoBitrateLimitField = "video-bitrate-limit";
UserFormView::UserFormView(SessionData& sessionData, std::string userId, Wt::WContainerWidget *parent)
: Wt::WTemplateFormView(parent)
{
_model = new UserFormModel(sessionData, userId, this);
setTemplateText(tr("userForm-template"));
addFunction("id", &WTemplate::Functions::id);
addFunction("block", &WTemplate::Functions::id);
// Name
setFormWidget(UserFormModel::NameField, new Wt::WLineEdit());
// Email
setFormWidget(UserFormModel::EmailField, new Wt::WLineEdit());
// Password
Wt::WLineEdit* passwordEdit = new Wt::WLineEdit();
setFormWidget(UserFormModel::PasswordField, passwordEdit );
passwordEdit->setEchoMode(Wt::WLineEdit::Password);
// Password confirmation
Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit();
setFormWidget(UserFormModel::PasswordConfirmField, passwordConfirmEdit);
passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password);
bindString("access", "Access");
// Admin Field
setFormWidget(UserFormModel::AdminField, new Wt::WCheckBox());
// AudioBitrate
Wt::WComboBox *audioBitrateCB = new Wt::WComboBox();
setFormWidget(UserFormModel::AudioBitrateLimitField, audioBitrateCB);
audioBitrateCB->setStyleClass("span2");
audioBitrateCB->setModel(_model->audioBitrateModel());
// VideoBitrate
Wt::WComboBox *videoBitrateCB = new Wt::WComboBox();
setFormWidget(UserFormModel::VideoBitrateLimitField, videoBitrateCB);
videoBitrateCB->setStyleClass("span2");
videoBitrateCB->setModel(_model->videoBitrateModel());
// Title & Buttons
Wt::WString title;
if (userId.empty()) {
title = Wt::WString("Create user");
}
else {
Database::Handler &db = sessionData.getDatabaseHandler();
Wt::Dbo::Transaction transaction (db.getSession());
Wt::Auth::User authUser = db.getUserDatabase().findWithId( userId );
Wt::WString userName;
if (authUser.isValid())
userName = authUser.identity(Wt::Auth::Identity::LoginName);
else
; // TODO display user deleted and close the widget
title = Wt::WString("Edit user {1}").arg(userName);
}
bindString("title", title);
Wt::WPushButton *saveButton = new Wt::WPushButton();
if (userId.empty()) {
saveButton->setText("Create user");
saveButton->setStyleClass("btn-success");
}
else
{
saveButton->setText("Save");
saveButton->setStyleClass("btn-primary");
}
bindWidget("save-button", saveButton);
saveButton->clicked().connect(this, &UserFormView::processSave);
Wt::WPushButton *cancelButton = new Wt::WPushButton("Cancel");
bindWidget("cancel-button", cancelButton);
cancelButton->clicked().connect(this, &UserFormView::processCancel);
updateView(_model);
}
void
UserFormView::processCancel()
{
// parent widget will delete this widget
completed().emit(false);
}
void
UserFormView::processSave()
{
updateModel(_model);
if (_model->validate())
{
// commit model into DB
if (_model->saveData() )
{
// parent widget will delete this widget
completed().emit(true);
}
// else TODO display a nice error message
}
else
{
updateView(_model);
}
}
} // namespace Settings
} // namespace UserInterface
+57
View File
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_SETTINGS_USER_FORM_VIEW_HPP
#define UI_SETTINGS_USER_FORM_VIEW_HPP
#include <Wt/WContainerWidget>
#include <Wt/WTemplateFormView>
#include <Wt/WSignal>
#include "common/SessionData.hpp"
namespace UserInterface {
namespace Settings {
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
Wt::Signal<bool>& completed() { return _sigCompleted; }
private:
Wt::Signal<bool> _sigCompleted;
void processSave();
void processCancel();
UserFormModel* _model;
};
} // namespace Settings
} // namespace UserInterface
#endif
+206
View File
@@ -0,0 +1,206 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WGroupBox>
#include <Wt/WText>
#include <Wt/WPushButton>
#include <Wt/WMessageBox>
#include <Wt/Auth/Identity>
#include "logger/Logger.hpp"
#include "SettingsUserFormView.hpp"
#include "SettingsUsers.hpp"
namespace UserInterface {
namespace Settings {
Users::Users(SessionData& sessionData, Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_sessionData(sessionData)
{
// Stack two widgets:
_stack = new Wt::WStackedWidget(this);
// 1/ the user table view
{
Wt::WGroupBox *container = new Wt::WGroupBox("Users", _stack);
_table = new Wt::WTable(container);
_table->addStyleClass("table form-inline");
_table->toggleStyleClass("table-hover", true);
_table->toggleStyleClass("table-striped", true);
_table->setHeaderCount(1);
_table->elementAt(0, 0)->addWidget(new Wt::WText("#"));
_table->elementAt(0, 1)->addWidget(new Wt::WText("Name"));
_table->elementAt(0, 2)->addWidget(new Wt::WText("e-Mail"));
_table->elementAt(0, 3)->addWidget(new Wt::WText("Admin"));
Wt::WPushButton* addBtn = new Wt::WPushButton("Add User");
addBtn->setStyleClass("btn-success");
container->addWidget( addBtn );
addBtn->clicked().connect(boost::bind(&Users::handleCreateUser, this, ""));
}
refresh();
}
void
Users::refresh(void)
{
assert(_table->rowCount() > 0);
for (int i = _table->rowCount() - 1; i > 0; --i)
_table->deleteRow(i);
Database::Handler& db = _sessionData.getDatabaseHandler();
Wt::Dbo::Transaction transaction(db.getSession());
const Wt::Auth::User& currentUser = db.getLogin().user();
std::vector<Database::User::pointer> users = Database::User::getAll(db.getSession());
std::size_t userIndex = 1;
for (std::size_t i = 0; i < users.size(); ++i)
{
std::string userId = Database::User::getId(users[i]);
Wt::Auth::User authUser;
// Hack try/catch here since it may fail!
try {
authUser = db.getUserDatabase().findWithId( userId );
}
catch(Wt::Dbo::Exception& e)
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Caught exception when getting userId=" << userId << ": " << e.code();
continue;
}
if (!authUser.isValid()) {
LMS_LOG(MOD_UI, SEV_ERROR) << "Users::refresh: skipping invalid userId = " << userId;
continue;
}
_table->elementAt(userIndex, 0)->addWidget(new Wt::WText( Wt::WString::fromUTF8("{1}").arg(userIndex)));
_table->elementAt(userIndex, 1)->addWidget(new Wt::WText( authUser.identity(Wt::Auth::Identity::LoginName)) );
Wt::WText* email = new Wt::WText();
if (!authUser.email().empty()) {
email->setText(authUser.email());
}
else {
email->setStyleClass("alert-danger");
email->setText(authUser.unverifiedEmail());
}
_table->elementAt(userIndex, 2)->addWidget( email ) ;
_table->elementAt(userIndex, 3)->addWidget(new Wt::WText( users[i]->isAdmin() ? "Yes" : "No" ));
Wt::WPushButton* editBtn = new Wt::WPushButton("Edit");
_table->elementAt(userIndex, 4)->addWidget(editBtn);
editBtn->clicked().connect(boost::bind( &Users::handleCreateUser, this, userId));
if (currentUser != authUser)
{
Wt::WPushButton* delBtn = new Wt::WPushButton("Delete");
delBtn->setStyleClass("btn-danger");
delBtn->setMargin(5, Wt::Left);
_table->elementAt(userIndex, 4)->addWidget(delBtn);
delBtn->clicked().connect(boost::bind( &Users::handleDelUser, this, authUser.identity(Wt::Auth::Identity::LoginName), userId));
}
++userIndex;
}
}
void
Users::handleDelUser(Wt::WString loginNameIdentity, std::string id)
{
Wt::WMessageBox *messageBox = new Wt::WMessageBox
("Delete User",
Wt::WString( "Deleting user '{1}'?").arg(loginNameIdentity),
Wt::Question, Wt::Yes | Wt::No);
messageBox->setModal(true);
messageBox->buttonClicked().connect(std::bind([=] () {
if (messageBox->buttonResult() == Wt::Yes)
{
Database::Handler& db = _sessionData.getDatabaseHandler();
Wt::Dbo::Transaction transaction(db.getSession());
// Delete the user
Wt::Auth::User authUser = db.getUserDatabase().findWithId( id );
db.getUserDatabase().deleteUser( authUser );
Database::User::pointer user = Database::User::getById(db.getSession(), id);
if (user)
user.remove();
refresh();
}
delete messageBox;
}));
messageBox->show();
}
void
Users::handleCreateUser(std::string id)
{
assert(_stack->count() == 1);
UserFormView* userFormView = new UserFormView(_sessionData, id, _stack);
userFormView->completed().connect(this, &Users::handleUserFormCompleted);
_stack->setCurrentIndex(1);
}
void
Users::handleUserFormCompleted(bool changed)
{
_stack->setCurrentIndex(0);
// Refresh the user table if a change has been made
if (changed)
refresh();
// Delete the form view
delete _stack->widget(1);
}
} // namespace UserInterface
} // namespace Settings
+56
View File
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef UI_SETTINGS_USERS_HPP
#define UI_SETTINGS_USERS_HPP
#include <Wt/WStackedWidget>
#include <Wt/WContainerWidget>
#include <Wt/WTable>
#include "common/SessionData.hpp"
namespace UserInterface {
namespace Settings {
class Users : public Wt::WContainerWidget
{
public:
Users(SessionData& sessioNData, Wt::WContainerWidget *parent = 0);
void refresh();
private:
void handleUserFormCompleted(bool changed);
void handleDelUser(Wt::WString loginNameIdentity, std::string id);
void handleCreateUser(std::string id); // set the id in order to edit the user
SessionData& _sessionData;
Wt::WStackedWidget* _stack;
Wt::WTable* _table;
};
} // namespace UserInterface
} // namespace Settings
#endif
+146
View File
@@ -0,0 +1,146 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include <Wt/WText>
#include <Wt/WPushButton>
#include <Wt/WMediaPlayer>
#include <Wt/WFileResource>
#include "transcode/Parameters.hpp"
#include "database/VideoTypes.hpp"
#include "database/MediaDirectory.hpp"
#include "VideoDatabaseWidget.hpp"
namespace UserInterface {
VideoDatabaseWidget::VideoDatabaseWidget(Database::Handler& db, Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_db(db)
{
_table = new Wt::WTable( this );
_table->setHeaderCount(1);
_table->addStyleClass("table form-inline");
_table->toggleStyleClass("table-hover", true);
_table->toggleStyleClass("table-striped", true);
updateView( boost::filesystem::path(), 0);
}
void
VideoDatabaseWidget::addHeader(void)
{
_table->elementAt(0, 0)->addWidget(new Wt::WText("Name"));
_table->elementAt(0, 1)->addWidget(new Wt::WText("Duration"));
_table->elementAt(0, 2)->addWidget(new Wt::WText("Action"));
}
void
VideoDatabaseWidget::addDirectory(const std::string& name, boost::filesystem::path path, size_t depth)
{
int row = _table->rowCount();
new Wt::WText(Wt::WString::fromUTF8( name ), _table->elementAt(row, 0));
new Wt::WText(Wt::WString::fromUTF8( " " ), _table->elementAt(row, 1));
Wt::WPushButton* btn = new Wt::WPushButton(Wt::WString::fromUTF8( "Open"), _table->elementAt(row, 2));
btn->clicked().connect(std::bind([=] () {
updateView(path, depth);
}));
}
void
VideoDatabaseWidget::addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path)
{
int row = _table->rowCount();
new Wt::WText(Wt::WString::fromUTF8( name ), _table->elementAt(row, 0));
new Wt::WText(Wt::WString::fromUTF8( boost::posix_time::to_simple_string( duration )), _table->elementAt(row, 1));
Wt::WPushButton* btn = new Wt::WPushButton(Wt::WString::fromUTF8( "Play"), _table->elementAt(row, 2));
btn->clicked().connect(std::bind([=] () {
playVideo().emit(path);
}));
}
void
VideoDatabaseWidget::updateView(boost::filesystem::path directory, size_t depth)
{
_table->clear();
addHeader();
// If directory is not valid, add the root Media Directories
if (depth == 0)
{
Wt::Dbo::Transaction transaction ( _db.getSession() );
std::vector<Database::MediaDirectory::pointer> dirs
= Database::MediaDirectory::getByType(_db.getSession(), Database::MediaDirectory::Video);
BOOST_FOREACH(Database::MediaDirectory::pointer dir, dirs)
{
addDirectory( dir->getPath().filename().string(),
dir->getPath(),
depth + 1);
}
}
else
{
addDirectory( "..", directory.parent_path(), depth - 1);
// Iterators over files in the directory
// If directory, add a directory entry
// If video, add a video entry
std::vector<boost::filesystem::path> paths;
std::copy(boost::filesystem::directory_iterator(directory), boost::filesystem::directory_iterator(), std::back_inserter(paths));
std::sort(paths.begin(), paths.end());
BOOST_FOREACH(const boost::filesystem::path& path, paths)
{
if (boost::filesystem::is_directory(path))
addDirectory( path.filename().string(), path, depth + 1);
else if (boost::filesystem::is_regular(path) )
{
Wt::Dbo::Transaction transaction ( _db.getSession() );
Database::Video::pointer video = Database::Video::getByPath( _db.getSession(), path);
if (video)
addVideo( video->getName(), video->getDuration(), path);
}
}
}
}
} // namespace UserInterface
+56
View File
@@ -0,0 +1,56 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef VIDEO_DB_WIDGET_HPP
#define VIDEO_DB_WIDGET_HPP
#include <Wt/WTable>
#include <Wt/WContainerWidget>
#include "common/SessionData.hpp"
namespace UserInterface {
class VideoDatabaseWidget : public Wt::WContainerWidget
{
public:
VideoDatabaseWidget( Database::Handler& db, Wt::WContainerWidget *parent = 0);
// Signals
Wt::Signal< boost::filesystem::path >& playVideo() { return _playVideo; }
private:
void addHeader(void);
void addDirectory(const std::string& name, boost::filesystem::path path, size_t depth);
void addVideo(const std::string& name, const boost::posix_time::time_duration& duration, const boost::filesystem::path& path);
void updateView(boost::filesystem::path directory, size_t depth);
Database::Handler& _db;
Wt::Signal< boost::filesystem::path > _playVideo;
Wt::WTable* _table;
};
} // namespace UserInterface
#endif
+202
View File
@@ -0,0 +1,202 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include <Wt/WMediaPlayer>
#include "VideoParametersDialog.hpp"
#include "VideoMediaPlayerWidget.hpp"
namespace UserInterface {
Wt::WMediaPlayer::Encoding
convert(Transcode::Format format)
{
switch( format.getEncoding() )
{
case Transcode::Format::OGA: return Wt::WMediaPlayer::OGA;
case Transcode::Format::OGV: return Wt::WMediaPlayer::OGV;
case Transcode::Format::MP3: return Wt::WMediaPlayer::MP3;
case Transcode::Format::WEBMA: return Wt::WMediaPlayer::WEBMA;
case Transcode::Format::WEBMV: return Wt::WMediaPlayer::WEBMV;
case Transcode::Format::FLV: return Wt::WMediaPlayer::FLV;
case Transcode::Format::M4A: return Wt::WMediaPlayer::M4A;
case Transcode::Format::M4V: return Wt::WMediaPlayer::M4V;
}
assert(0);
}
VideoMediaPlayerWidget::VideoMediaPlayerWidget( const Transcode::Parameters& parameters, Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent),
_mediaResource(nullptr),
_currentParameters(parameters),
_dialog(nullptr)
{
_mediaPlayer = new Wt::WMediaPlayer( Wt::WMediaPlayer::Video, this );
// _mediaPlayer->setAlternativeContent (new Wt::WText("You don't have HTML5 audio support!"));
// _mediaPlayer->setOptions( Wt::WMediaPlayer::Autoplay );
// _mediaPlayer->addSource( Wt::WMediaPlayer::WEBMV, "" );
{
Wt::WContainerWidget *container = new Wt::WContainerWidget(this);
_playBtn = new Wt::WPushButton("Play", container );
_pauseBtn = new Wt::WPushButton("Pause", container );
_curTime = new Wt::WText(container);
_timeSlider = new Wt::WSlider( container );
_duration = new Wt::WText(container);
_volumeSlider = new Wt::WSlider( container );
_volumeSlider->setRange(0,100);
_volumeSlider->setValue(_mediaPlayer->volume() * 100);
_mediaPlayer->setControlsWidget( container );
_mediaPlayer->setButton(Wt::WMediaPlayer::Play, _playBtn);
_mediaPlayer->setButton(Wt::WMediaPlayer::Pause, _pauseBtn);
_mediaPlayer->setText( Wt::WMediaPlayer::CurrentTime, _curTime);
_mediaPlayer->setText( Wt::WMediaPlayer::Duration, _duration);
_mediaPlayer->timeUpdated().connect(this, &VideoMediaPlayerWidget::handleTimeUpdated);
Wt::WPushButton* fullScreenButton = new Wt::WPushButton("Fullscreen", container);
_mediaPlayer->setButton(Wt::WMediaPlayer::FullScreen, fullScreenButton);
Wt::WPushButton* restoreScreenButton = new Wt::WPushButton("Restore screen", container);
_mediaPlayer->setButton(Wt::WMediaPlayer::RestoreScreen, restoreScreenButton);
_mediaPlayer->timeUpdated().connect(this, &VideoMediaPlayerWidget::handleTimeUpdated);
_timeSlider->valueChanged().connect(this, &VideoMediaPlayerWidget::handlePlayOffset);
_timeSlider->sliderMoved().connect(this, &VideoMediaPlayerWidget::handleSliderMoved);
_volumeSlider->sliderMoved().connect(this, &VideoMediaPlayerWidget::handleVolumeSliderMoved);
}
Wt::WPushButton* closeButton = new Wt::WPushButton("Close", this);
closeButton->clicked().connect( this, &VideoMediaPlayerWidget::handleClose );
Wt::WPushButton* parametersButton = new Wt::WPushButton("Parameters", this);
parametersButton->clicked().connect( this, &VideoMediaPlayerWidget::handleParametersEdit );
load(parameters);
}
void
VideoMediaPlayerWidget::load(const Transcode::Parameters& parameters)
{
_mediaPlayer->clearSources();
_currentParameters = parameters;
_mediaInternalLink.setResource( nullptr );
if (_mediaResource)
delete _mediaResource;
_mediaResource = new AvConvTranscodeStreamResource( parameters, this );
_mediaInternalLink.setResource( _mediaResource );
_mediaPlayer->addSource( convert(parameters.getOutputFormat()), _mediaInternalLink );
_timeSlider->setRange(0, parameters.getInputMediaFile().getDuration().total_seconds() );
_timeSlider->setValue( parameters.getOffset().total_seconds() );
_duration->setText( boost::posix_time::to_simple_string( parameters.getInputMediaFile().getDuration() ));
_mediaPlayer->play();
}
void
VideoMediaPlayerWidget::handlePlayOffset(int offsetSecs)
{
std::cout << "Want to play at offset " << offsetSecs << std::endl;;
_currentParameters.setOffset( boost::posix_time::seconds(offsetSecs) );
load( _currentParameters );
_timeSlider->setValue( offsetSecs );
}
void
VideoMediaPlayerWidget::handleSliderMoved(int value)
{
std::cout << "Slider moved to " << value << std::endl;
_curTime->setText( boost::posix_time::to_simple_string( boost::posix_time::seconds( value ) ) );
}
void
VideoMediaPlayerWidget::handleTimeUpdated(void)
{
std::cout << "Time updated to " << _mediaPlayer->currentTime() << std::endl;
if (_mediaPlayer->currentTime() > 0 && _mediaPlayer->currentTime() < _currentParameters.getInputMediaFile().getDuration().total_seconds())
{
boost::posix_time::time_duration currentTime ( boost::posix_time::seconds( _mediaPlayer->currentTime() + _currentParameters.getOffset().total_seconds()));
_timeSlider->setValue( currentTime.total_seconds() );
_curTime->setText( boost::posix_time::to_simple_string( currentTime) );
}
}
void
VideoMediaPlayerWidget::handleVolumeSliderMoved(int value)
{
_mediaPlayer->setVolume( value / 100. );
}
void
VideoMediaPlayerWidget::handleClose(void)
{
_close.emit();
}
void
VideoMediaPlayerWidget::handleParametersEdit(void)
{
_dialog = new VideoParametersDialog("Parameters");
_dialog->load(_currentParameters);
_dialog->show();
_dialog->finished().connect(this, &VideoMediaPlayerWidget::handleParametersDone);
}
void
VideoMediaPlayerWidget::handleParametersDone(Wt::WDialog::DialogCode code)
{
assert(_dialog != nullptr);
if (code == Wt::WDialog::Accepted)
{
_dialog->save( _currentParameters );
// TODO SYNC current offset with player?
// HACK use slider current value
handlePlayOffset( _timeSlider->value());
}
}
} // namespace UserInterface
+85
View File
@@ -0,0 +1,85 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __VIDEO_MEDIA_PLAYER_WIDGET_HPP
#define __VIDEO_MEDIA_PLAYER_WIDGET_HPP
#include <Wt/WDialog>
#include <Wt/WSlider>
#include <Wt/WPushButton>
#include <Wt/WContainerWidget>
#include <Wt/WLink>
#include "VideoParametersDialog.hpp"
#include "transcode/Parameters.hpp"
#include "resource/AvConvTranscodeStreamResource.hpp"
namespace UserInterface {
class VideoMediaPlayerWidget : public Wt::WContainerWidget
{
public:
VideoMediaPlayerWidget( const Transcode::Parameters& parameters, Wt::WContainerWidget *parent = 0);
Wt::Signal<void>& close() { return _close; };
private:
// Signals
Wt::Signal<void> _close;
void load(const Transcode::Parameters& parameters);
// Player controls
void handlePlayOffset(int offsetSecs);
void handleTimeUpdated(void);
void handleSliderMoved(int value);
void handleFullscreen(void);
void handleVolumeSliderMoved(int value);
void handleClose(void);
void handleParametersEdit(void);
void handleParametersDone(Wt::WDialog::DialogCode);
// Core
Wt::WMediaPlayer* _mediaPlayer;
AvConvTranscodeStreamResource* _mediaResource;
Wt::WLink _mediaInternalLink;
// Controls
Transcode::Parameters _currentParameters;
Wt::WPushButton* _playBtn;
Wt::WPushButton* _pauseBtn;
Wt::WSlider* _timeSlider;
Wt::WSlider* _volumeSlider;
Wt::WText* _curTime;
Wt::WText* _duration;
VideoParametersDialog* _dialog;
};
} // namespace UserInterface
#endif
+190
View File
@@ -0,0 +1,190 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include <Wt/WPushButton>
#include <Wt/WLabel>
#include <Wt/WTable>
#include "VideoParametersDialog.hpp"
using namespace Transcode;
namespace UserInterface {
static const std::list<Stream::Type> streamTypes = {Stream::Video, Stream::Audio, Stream::Subtitle};
VideoParametersDialog::VideoParametersDialog(const Wt::WString &windowTitle, Wt::WDialog* parent)
: Wt::WDialog(windowTitle, parent)
{
Wt::WTable* layout = new Wt::WTable(contents());
int row = 0;
{
Wt::WLabel* label = new Wt::WLabel("Format");
_outputFormat = new Wt::WComboBox();
label->setBuddy(_outputFormat);
layout->elementAt(row, 0)->addWidget(label);
layout->elementAt(row, 1)->addWidget(_outputFormat);
_outputFormatModel = new Wt::WStringListModel(_outputFormat);
std::vector<Format> formats = Format::get( Format::Video );
for(std::size_t idFormat = 0; idFormat < formats.size(); ++idFormat)
{
_outputFormatModel->addString(formats[idFormat].getDesc());
_outputFormatModel->setData(idFormat, 0, formats[idFormat].getEncoding(), Wt::UserRole);
}
_outputFormat->setModel(_outputFormatModel);
row++;
}
createStreamWidgets("Video", Stream::Video, layout);
createStreamWidgets("Audio", Stream::Audio, layout);
createStreamWidgets("Subtitles", Stream::Subtitle, layout);
Wt::WPushButton *ok = new Wt::WPushButton("Apply", contents());
ok->clicked().connect(this, &Wt::WDialog::accept);
Wt::WPushButton *cancel = new Wt::WPushButton("Cancel", contents());
cancel->clicked().connect(this, &Wt::WDialog::reject);
}
void
VideoParametersDialog::createStreamWidgets(const Wt::WString& labelString, Transcode::Stream::Type type, Wt::WTable* layout)
{
int row = layout->rowCount();
Wt::WLabel* label = new Wt::WLabel(labelString);
Wt::WComboBox *combo = new Wt::WComboBox();
label->setBuddy(combo);
layout->elementAt(row, 0)->addWidget(label);
layout->elementAt(row, 1)->addWidget(combo);
Wt::WStringListModel* model = new Wt::WStringListModel(combo);
combo->setModel(model);
_streamSelection.insert( std::make_pair(type, std::make_pair(combo, model) ) );
}
void
VideoParametersDialog::handleApply()
{
_apply.emit();
}
void
VideoParametersDialog::addStreams(Wt::WStringListModel* model, const std::vector<Stream>& streams)
{
for (std::size_t idStream = 0; idStream < streams.size(); ++idStream)
{
const Stream& stream = streams[idStream];
std::ostringstream oss;
if (!stream.getLanguage().empty())
oss << "[" << stream.getLanguage() << "] ";
oss << stream.getDesc();
model->addString(oss.str());
model->setData(idStream, 0, stream.getId(), Wt::UserRole);
}
}
void
VideoParametersDialog::selectStream(const Wt::WStringListModel* model, Stream::Id streamId, Wt::WComboBox* combo)
{
for (int idStream = 0; idStream < model->rowCount(); ++idStream)
{
Stream::Id id = boost::any_cast<Stream::Id>( model->data( model->index(idStream, 0), Wt::UserRole));
if (id == streamId)
{
combo->setCurrentIndex(idStream);
break;
}
}
}
void
VideoParametersDialog::load(const Transcode::Parameters& parameters)
{
// Select proper encoding
for (int row = 0; row < _outputFormat->count(); ++row)
{
Format::Encoding encoding = boost::any_cast<Format::Encoding>( _outputFormatModel->data(_outputFormatModel->index(row,0), Wt::UserRole));
if (encoding == parameters.getOutputFormat().getEncoding()) {
_outputFormat->setCurrentIndex(row);
break;
}
}
// Get the current selected input streams
Parameters::StreamMap streamMap = parameters.getInputStreams();;
// Populate the combox with the available streams
// And then show the selected one
BOOST_FOREACH(Stream::Type streamType, streamTypes)
{
Wt::WComboBox* combo = _streamSelection[streamType].first;
Wt::WStringListModel* model = _streamSelection[streamType].second;
addStreams(model, parameters.getInputMediaFile().getStreams( streamType ) );
selectStream(model, streamMap[streamType], combo);
}
}
void
VideoParametersDialog::save(Transcode::Parameters& parameters)
{
std::cout << "Grabbing user input!" << std::endl;
// Get encoder used
Format::Encoding encoding = boost::any_cast<Format::Encoding>( _outputFormatModel->data(_outputFormatModel->index(_outputFormat->currentIndex(), 0), Wt::UserRole));
parameters.setOutputFormat( Format::get(encoding) );
// Get stream selected, if any
BOOST_FOREACH(Stream::Type streamType, streamTypes)
{
Wt::WComboBox* combo = _streamSelection[streamType].first;
Wt::WStringListModel* model = _streamSelection[streamType].second;
if (combo->currentIndex() >= 0)
{
Stream::Id streamId = boost::any_cast<Stream::Id>( model->data(model->index(combo->currentIndex(), 0), Wt::UserRole));
parameters.selectInputStream(streamType, streamId);
}
}
}
} // namespace UserInterface
+70
View File
@@ -0,0 +1,70 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef VIDEO_PARAMETER_DIALOG
#define VIDEO_PARAMETER_DIALOG
#include <map>
#include <Wt/WSignal>
#include <Wt/WDialog>
#include <Wt/WComboBox>
#include <Wt/WStringListModel>
#include <Wt/WString>
#include "transcode/Parameters.hpp"
namespace UserInterface {
class VideoParametersDialog : public Wt::WDialog
{
public:
// parameters to be edited
VideoParametersDialog(const Wt::WString &windowTitle, Wt::WDialog* parent = 0);
// Populates widget contents using these paramaters
void load(const Transcode::Parameters& parameters);
// Save widgets contents into these parameters
void save(Transcode::Parameters& parameters);
// Signal to be emitted if parameters are changed
Wt::Signal<void>& apply() { return _apply; }
private:
void handleApply(void);
// Stream handling
void createStreamWidgets(const Wt::WString& label, Transcode::Stream::Type type, Wt::WTable* layout);
void addStreams(Wt::WStringListModel* model, const std::vector<Transcode::Stream>& streams);
void selectStream(const Wt::WStringListModel* model, Transcode::Stream::Id streamId, Wt::WComboBox* combo);
Wt::Signal<void> _apply;
Wt::WComboBox* _outputFormat;
Wt::WStringListModel* _outputFormatModel;
std::map<Transcode::Stream::Type, std::pair<Wt::WComboBox*, Wt::WStringListModel* > > _streamSelection;
};
} // namespace UserInterface
#endif
+113
View File
@@ -0,0 +1,113 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WApplication>
#include <Wt/WEnvironment>
#include "logger/Logger.hpp"
#include "VideoWidget.hpp"
namespace UserInterface {
VideoWidget::VideoWidget(SessionData& sessionData, Wt::WContainerWidget* parent )
: Wt::WContainerWidget(parent),
_sessionData(sessionData)
{
_videoDbWidget = new VideoDatabaseWidget(_sessionData.getDatabaseHandler(), this);
_videoDbWidget->playVideo().connect(this, &VideoWidget::playVideo);
}
void
VideoWidget::search(const std::string& searchText)
{
//TODO
}
void
VideoWidget::backToList(void)
{
if (_mediaPlayer)
delete _mediaPlayer;
_videoDbWidget->setHidden(false);
}
void
VideoWidget::playVideo(boost::filesystem::path p)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "Want to play video " << p << "'" << std::endl;
try {
std::size_t audioBitrate = 0;
std::size_t videoBitrate = 0;
// Get user preferences
{
Wt::Dbo::Transaction transaction(_sessionData.getDatabaseHandler().getSession());
Database::User::pointer user = _sessionData.getDatabaseHandler().getCurrentUser();
if (user)
{
audioBitrate = user->getMaxAudioBitrate();
videoBitrate = user->getMaxVideoBitrate();
}
else
{
LMS_LOG(MOD_UI, SEV_ERROR) << "Can't play video: user does not exists!";
return; // TODO logout?
}
}
LMS_LOG(MOD_UI, SEV_DEBUG) << "Max bitrate set to " << videoBitrate << "/" << audioBitrate;
Transcode::InputMediaFile inputFile(p);
Transcode::Format::Encoding encoding;
if (Wt::WApplication::instance()->environment().agentIsChrome())
encoding = Transcode::Format::WEBMV;
else
encoding = Transcode::Format::FLV;
Transcode::Parameters parameters(inputFile, Transcode::Format::get(encoding));
// TODO, make a quality button in order to choose...
parameters.setBitrate(Transcode::Stream::Audio, 0/*audioBitrate*/);
parameters.setBitrate(Transcode::Stream::Video, 0/*videoBitrate*/);
_mediaPlayer = new VideoMediaPlayerWidget(parameters, this);
_mediaPlayer->close().connect(this, &VideoWidget::backToList);
_videoDbWidget->setHidden(true);
}
catch( std::exception& e) {
LMS_LOG(MOD_UI, SEV_ERROR) << "Caught exception while loading '" << p << "': " << e.what() << std::endl;
}
}
} // namespace UserInterface
+57
View File
@@ -0,0 +1,57 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef VIDEO_WIDGET_HPP
#define VIDEO_WIDGET_HPP
#include <string>
#include <Wt/WContainerWidget>
#include "common/SessionData.hpp"
#include "video/VideoMediaPlayerWidget.hpp"
#include "video/VideoDatabaseWidget.hpp"
namespace UserInterface {
class VideoWidget : public Wt::WContainerWidget
{
public:
VideoWidget(SessionData& sessionData, Wt::WContainerWidget* parent = 0);
void search(const std::string& searchText);
private:
void backToList(void);
void playVideo(boost::filesystem::path p);
SessionData& _sessionData;
VideoDatabaseWidget* _videoDbWidget;
VideoMediaPlayerWidget* _mediaPlayer;
};
} // namespace UserInterface
#endif