Upgrade from Wt3 to Wt4

This commit is contained in:
emeric
2018-04-16 16:56:51 +02:00
parent b743127076
commit 9cb4918f44
87 changed files with 954 additions and 2057 deletions
+14 -18
View File
@@ -1,17 +1,13 @@
# LMS - Lightweight Media Server
LMS is a self-hosted media streaming software, released under the GPLv3 license.
It allows you to access your music and your videos using an http(s) web interface.
It allows you to access your music using an http(s) web interface.
## Features
- Audio transcode for maximum interoperability and low bandwith requirements
- User management
- Playlist support
- MusicBrainzID support to handle duplicated artist and release names
Tags are automatically created to facilitate music searching, using:
- Multi genre information from metadata
- High-level information from AcousticBrainz
- Filter by genre and/or group
- User management
LMS is written entirely in C++. Therefore, it is suitable to run on embedded devices, where space and memory are limited.
@@ -19,29 +15,29 @@ LMS is written entirely in C++. Therefore, it is suitable to run on embedded dev
### Debian
```sh
$ apt-get install g++ autoconf automake libboost-dev libboost-locale-dev libboost-iostreams-dev libavcodec-dev libavutil-dev libavformat-dev libav-tools libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libmagick++-dev libpstreams-dev libcurlpp-dev libconfig++-dev libpstreams-dev ffmpeg libtag1-dev
apt-get install g++ autoconf automake libboost-filesystem-dev libboost-system-dev libavcodec-dev libavutil-dev libavformat-dev libav-tools libwtdbosqlite-dev libwthttp-dev libwtdbo-dev libwt-dev libmagick++-dev libpstreams-dev libconfig++-dev libpstreams-dev ffmpeg libtag1-dev
```
## Build
```sh
$ git clone https://github.com/epoupon/lms.git lms
$ cd lms
$ autoreconf -vfi
$ mkdir build
$ cd build
$ ../configure --prefix=/usr --sysconfdir=/etc
git clone https://github.com/epoupon/lms.git lms
cd lms
autoreconf -vfi
mkdir build
cd build
../configure --prefix=/usr --sysconfdir=/etc
```
configure will complain if a mandatory library is missing.
```sh
$ make -j 4
make
```
## Install
```sh
$ make install
make install
```
This command requires root privileges
@@ -49,13 +45,13 @@ This command requires root privileges
LMS uses a configuration file, installed in '/etc/lms.conf'
It is recommended to edit this file and change the relevant settings (working directory, listen port, etc.)
Other settings are set using the web interface.
All other settings are set using the web interface.
It is highly recommended to run LMS as a non root user. Therefore make sure the user has write permissions on the working directory.
## Running
```sh
$ lms [config_file]
lms [config_file]
```
Logs are output in the working directory, in the file 'lms.log'
-1
View File
@@ -37,7 +37,6 @@
- Display only the most popular cluster of each cluster type?
[Settings]
- Add a audio settings: format + bitrate to be used
- Add a user management view
- Add multi root directories support
- Add a first connection wizard
+1 -1
View File
@@ -5,7 +5,7 @@
<message id="Lms.Admin.InitWizard.template">
<div class="container">
<div class="page-header">
<h2>${tr:Lms.AdminWizard.header}</h2>
<h2>${tr:Lms.Admin.InitWizard.header}</h2>
</div>
<div class="form-horizontal">
<div class="form-group">
+1 -31
View File
@@ -16,7 +16,7 @@ fi
AC_SUBST(MAGICKXX_CFLAGS)
AC_SUBST(MAGICKXX_LIBS)
AC_CHECK_HEADERS([Wt/WApplication pstreams/pstream.h],
AC_CHECK_HEADERS([Wt/WApplication.h pstreams/pstream.h],
[],
[AC_MSG_ERROR([Header not found or unusable !])])
@@ -66,36 +66,6 @@ AC_CHECK_LIB( [boost_filesystem],
,
[AC_MSG_ERROR([libboost_filesystem not found!])])
AC_CHECK_LIB( [boost_locale],
[main],
,
[AC_MSG_ERROR([libboost_locale not found!])])
AC_CHECK_LIB( [boost_date_time],
[main],
,
[AC_MSG_ERROR([libboost_date_time not found!])])
AC_CHECK_LIB( [boost_iostreams],
[main],
,
[AC_MSG_ERROR([libboost_iostreams not found!])])
AC_CHECK_LIB( [boost_thread],
[main],
,
[AC_MSG_ERROR([libboost_thread not found!])])
AC_CHECK_LIB( [curlpp],
[main],
,
[AC_MSG_ERROR([libcurlpp not found!])])
AC_CHECK_LIB( [curl],
[curl_easy_cleanup],
,
[AC_MSG_ERROR([libcurl not found!])])
AC_CHECK_LIB( [tag],
[main],
,
+1 -6
View File
@@ -10,13 +10,10 @@ lms_SOURCES = \
$(srcdir)/database/MediaDirectory.cpp \
$(srcdir)/database/Playlist.cpp \
$(srcdir)/database/Release.cpp \
$(srcdir)/database/SearchFilter.cpp \
$(srcdir)/database/Setting.cpp \
$(srcdir)/database/SqlQuery.cpp \
$(srcdir)/database/Track.cpp \
$(srcdir)/database/User.cpp \
$(srcdir)/feature/FeatureExtractor.cpp \
$(srcdir)/feature/FeatureStore.cpp \
$(srcdir)/image/Image.cpp \
$(srcdir)/metadata/AvFormat.cpp \
$(srcdir)/metadata/TagLibParser.cpp \
@@ -31,8 +28,6 @@ lms_SOURCES = \
$(srcdir)/ui/admin/InitWizardView.cpp \
$(srcdir)/ui/admin/UserView.cpp \
$(srcdir)/ui/admin/UsersView.cpp \
$(srcdir)/ui/common/InputRange.cpp \
$(srcdir)/ui/common/LineEdit.cpp \
$(srcdir)/ui/common/Validators.cpp \
$(srcdir)/ui/explore/ArtistView.cpp \
$(srcdir)/ui/explore/ArtistsView.cpp \
@@ -48,6 +43,6 @@ lms_SOURCES = \
$(srcdir)/utils/Path.cpp \
$(srcdir)/utils/Utils.cpp
lms_CXXFLAGS=-std=c++11 -Wall -I$(srcdir)/third-party -I$(srcdir)/ui $(MAGICKXX_CFLAGS) -D_REENTRANT -DBOOST_SPIRIT_THREADSAFE
lms_CXXFLAGS=-std=c++14 -Wall -I$(srcdir)/third-party -I$(srcdir)/ui $(MAGICKXX_CFLAGS) -D_REENTRANT
lms_LDADD=$(MAGICKXX_LIBS)
+7 -7
View File
@@ -57,14 +57,14 @@ MediaFile::MediaFile(const boost::filesystem::path& p)
int error = avformat_open_input(&_context, _p.string().c_str(), nullptr, nullptr);
if (error < 0)
{
LMS_LOG(AV, ERROR) << "Cannot open " << _p << ": " << averror_to_string(error);
LMS_LOG(AV, ERROR) << "Cannot open " << _p.string() << ": " << averror_to_string(error);
throw MediaFileException(error);
}
error = avformat_find_stream_info(_context, nullptr);
if (error < 0)
{
LMS_LOG(AV, ERROR) << "Cannot find stream information on " << _p << ": " << averror_to_string(error);
LMS_LOG(AV, ERROR) << "Cannot find stream information on " << _p.string() << ": " << averror_to_string(error);
avformat_close_input(&_context);
throw MediaFileException(error);
}
@@ -75,13 +75,13 @@ MediaFile::~MediaFile()
avformat_close_input(&_context);
}
boost::posix_time::time_duration
std::chrono::milliseconds
MediaFile::getDuration() const
{
if (static_cast<int>(_context->duration) != AV_NOPTS_VALUE )
return boost::posix_time::seconds(_context->duration / AV_TIME_BASE);
else
return boost::posix_time::seconds(0); // TODO, do something better?
if (_context->duration == AV_NOPTS_VALUE)
return std::chrono::milliseconds(0); // TODO estimate
return std::chrono::milliseconds(_context->duration / AV_TIME_BASE * 1000);
}
void
+2 -2
View File
@@ -33,10 +33,10 @@ extern "C"
#include <string>
#include <cstdint>
#include <map>
#include <chrono>
#include <boost/optional.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp> //no i/o just types
#include "utils/Exception.hpp"
@@ -75,7 +75,7 @@ class MediaFile
const boost::filesystem::path& getPath() const {return _p;};
boost::posix_time::time_duration getDuration() const;
std::chrono::milliseconds getDuration() const;
std::map<std::string, std::string> getMetaData(void);
std::vector<StreamInfo> getStreamInfo() const;
+2 -2
View File
@@ -101,7 +101,7 @@ Transcoder::init()
}
if (!avConvPath.empty())
LMS_LOG(TRANSCODE, INFO) << "Using transcoder " << avConvPath;
LMS_LOG(TRANSCODE, INFO) << "Using transcoder " << avConvPath.string();
else
throw std::runtime_error("Cannot find any transcoder binary!");
}
@@ -123,7 +123,7 @@ Transcoder::start()
else if (!boost::filesystem::is_regular( _filePath) )
return false;
LMS_LOG_TRANSCODE(INFO) << "Transcoding file '" << _filePath << "'";
LMS_LOG_TRANSCODE(INFO) << "Transcoding file '" << _filePath.string() << "'";
std::vector<std::string> args;
+4 -4
View File
@@ -65,7 +65,7 @@ getFromAvMediaFile(const Av::MediaFile& input, std::size_t nbMaxCovers)
if (image.load(picture.data))
res.push_back( image );
else
LMS_LOG(COVER, ERROR) << "Cannot load embedded cover file in '" << input.getPath() << "'";
LMS_LOG(COVER, ERROR) << "Cannot load embedded cover file in '" << input.getPath().string() << "'";
}
return res;
@@ -87,7 +87,7 @@ Grabber::getFromDirectory(const boost::filesystem::path& p, std::size_t nbMaxCov
if (image.load(coverPath))
res.push_back(image);
else
LMS_LOG(COVER, ERROR) << "Cannot load image in file '" << coverPath << "'";
LMS_LOG(COVER, ERROR) << "Cannot load image in file '" << coverPath.string() << "'";
}
return res;
@@ -116,7 +116,7 @@ Grabber::getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t
if (boost::filesystem::file_size(path) > _maxFileSize)
{
LMS_LOG(COVER, INFO) << "Cover file '" << path << " is too big (" << boost::filesystem::file_size(path) << "), limit is " << _maxFileSize;
LMS_LOG(COVER, INFO) << "Cover file '" << path.string() << " is too big (" << boost::filesystem::file_size(path) << "), limit is " << _maxFileSize;
continue;
}
@@ -138,7 +138,7 @@ Grabber::getFromTrack(const boost::filesystem::path& p, std::size_t nbMaxCovers)
}
catch (Av::MediaFileException& e)
{
LMS_LOG(COVER, ERROR) << "Cannot get covers from track " << p << ": " << e.what();
LMS_LOG(COVER, ERROR) << "Cannot get covers from track " << p.string() << ": " << e.what();
}
return std::vector<Image::Image>();
+30 -28
View File
@@ -19,17 +19,17 @@
#include <boost/make_unique.hpp>
#include <Wt/Dbo/FixedSqlConnectionPool>
#include <Wt/Dbo/backend/Sqlite3>
#include <Wt/Dbo/FixedSqlConnectionPool.h>
#include <Wt/Dbo/backend/Sqlite3.h>
#include <Wt/Auth/Dbo/AuthInfo>
#include <Wt/Auth/Dbo/UserDatabase>
#include <Wt/Auth/AuthService>
#include <Wt/Auth/HashFunction>
#include <Wt/Auth/Identity>
#include <Wt/Auth/PasswordService>
#include <Wt/Auth/PasswordStrengthValidator>
#include <Wt/Auth/PasswordVerifier>
#include <Wt/Auth/Dbo/AuthInfo.h>
#include <Wt/Auth/Dbo/UserDatabase.h>
#include <Wt/Auth/AuthService.h>
#include <Wt/Auth/HashFunction.h>
#include <Wt/Auth/Identity.h>
#include <Wt/Auth/PasswordService.h>
#include <Wt/Auth/PasswordStrengthValidator.h>
#include <Wt/Auth/PasswordVerifier.h>
#include "Setting.hpp"
@@ -51,24 +51,24 @@ Handler::configureAuth(void)
{
authService.setEmailVerificationEnabled(false);
authService.setAuthTokensEnabled(true, "lmsauth");
authService.setIdentityPolicy(Wt::Auth::LoginNameIdentity);
authService.setIdentityPolicy(Wt::Auth::IdentityPolicy::LoginName);
authService.setRandomTokenLength(32);
authService.setTokenHashFunction(new Wt::Auth::BCryptHashFunction(8));
Wt::Auth::PasswordVerifier *verifier = new Wt::Auth::PasswordVerifier();
verifier->addHashFunction(new Wt::Auth::BCryptHashFunction(8));
passwordService.setVerifier(verifier);
auto verifier = std::make_unique<Wt::Auth::PasswordVerifier>();
verifier->addHashFunction(std::make_unique<Wt::Auth::BCryptHashFunction>(8));
passwordService.setVerifier(std::move(verifier));
passwordService.setAttemptThrottlingEnabled(true);
Wt::Auth::PasswordStrengthValidator* strengthValidator = new Wt::Auth::PasswordStrengthValidator();
auto strengthValidator = std::make_unique<Wt::Auth::PasswordStrengthValidator>();
// Reduce some constraints...
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::PassPhrase, 4);
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::OneCharClass, 4);
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::TwoCharClass, 4);
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::ThreeCharClass, 4 );
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthValidator::FourCharClass, 4 );
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthType::PassPhrase, 4);
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthType::OneCharClass, 4);
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthType::TwoCharClass, 4);
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthType::ThreeCharClass, 4 );
strengthValidator->setMinimumLength( Wt::Auth::PasswordStrengthType::FourCharClass, 4 );
passwordService.setStrengthValidator(strengthValidator);
passwordService.setStrengthValidator(std::move(strengthValidator));
}
const Wt::Auth::AuthService&
@@ -182,24 +182,26 @@ Handler::createUser(const Wt::Auth::User& authUser)
return User::pointer();
}
User::pointer user = _session.add(new User());
User::pointer user = _session.add(std::make_unique<User>());
Wt::Dbo::ptr<AuthInfo> authInfo = _users->find(authUser);
authInfo.modify()->setUser(user);
return user;
}
Wt::Dbo::SqlConnectionPool*
std::unique_ptr<Wt::Dbo::SqlConnectionPool>
Handler::createConnectionPool(boost::filesystem::path p)
{
LMS_LOG(DB, INFO) << "Creating connection pool on file " << p;
Wt::Dbo::backend::Sqlite3 *connection = new Wt::Dbo::backend::Sqlite3(p.string());
LMS_LOG(DB, INFO) << "Creating connection pool on file " << p.string();
auto connection = std::make_unique<Wt::Dbo::backend::Sqlite3>(p.string());
connection->executeSql("pragma journal_mode=WAL");
connection->setProperty("show-queries", "true");
return new Wt::Dbo::FixedSqlConnectionPool(connection, 1);
auto pool = std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), 1);
pool->setTimeout(std::chrono::seconds(1));
return pool;
}
+6 -6
View File
@@ -23,12 +23,12 @@
#include <boost/filesystem.hpp>
#include <memory>
#include <Wt/Dbo/Dbo>
#include <Wt/Dbo/SqlConnectionPool>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/SqlConnectionPool.h>
#include <Wt/Auth/Dbo/UserDatabase>
#include <Wt/Auth/Login>
#include <Wt/Auth/PasswordService>
#include <Wt/Auth/Dbo/UserDatabase.h>
#include <Wt/Auth/Login.h>
#include <Wt/Auth/PasswordService.h>
#include "Types.hpp"
@@ -60,7 +60,7 @@ class Handler
static const Wt::Auth::AuthService& getAuthService();
static const Wt::Auth::PasswordService& getPasswordService();
static Wt::Dbo::SqlConnectionPool* createConnectionPool(boost::filesystem::path db);
static std::unique_ptr<Wt::Dbo::SqlConnectionPool> createConnectionPool(boost::filesystem::path db);
private:
+2 -2
View File
@@ -54,7 +54,7 @@ Artist::getById(Wt::Dbo::Session& session, Artist::id_type id)
Artist::pointer
Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID)
{
return session.add(new Artist(name, MBID));
return session.add(std::make_unique<Artist>(name, MBID));
}
std::vector<Artist::pointer>
@@ -75,7 +75,7 @@ Artist::getAllOrphans(Wt::Dbo::Session& session)
static
Wt::Dbo::Query<Artist::pointer>
getQuery(Wt::Dbo::Session& session,
const std::set<id_type>& clusterIds,
const std::set<Artist::id_type>& clusterIds,
const std::vector<std::string>& keywords)
{
WhereClause where;
+2 -4
View File
@@ -23,10 +23,8 @@
#include <string>
#include <vector>
#include <Wt/Dbo/Dbo>
#include <Wt/Dbo/QueryModel>
#include "SearchFilter.hpp"
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/QueryModel.h>
namespace Database
{
+1 -1
View File
@@ -31,7 +31,7 @@ MediaDirectory::MediaDirectory(boost::filesystem::path p)
MediaDirectory::pointer
MediaDirectory::create(Wt::Dbo::Session& session, boost::filesystem::path p)
{
return session.add( new MediaDirectory(p) );
return session.add( std::make_unique<MediaDirectory>(p) );
}
void
+3 -2
View File
@@ -24,8 +24,9 @@
#include <boost/filesystem/path.hpp>
#include <Wt/Dbo/Dbo>
#include <Wt/Dbo/WtSqlTraits>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/WtSqlTraits.h>
namespace Database {
class MediaDirectory
+2 -2
View File
@@ -38,7 +38,7 @@ Playlist::Playlist(std::string name, bool isPublic, Wt::Dbo::ptr<User> user)
Playlist::pointer
Playlist::create(Wt::Dbo::Session& session, std::string name, bool isPublic, Wt::Dbo::ptr<User> user)
{
return session.add( new Playlist(name, isPublic, user) );
return session.add( std::make_unique<Playlist>(name, isPublic, user) );
}
PlaylistEntry::PlaylistEntry()
@@ -75,7 +75,7 @@ PlaylistEntry::PlaylistEntry(Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Playlist> p
PlaylistEntry::pointer
PlaylistEntry::create(Wt::Dbo::Session& session, Wt::Dbo::ptr<Track> track, Wt::Dbo::ptr<Playlist> playlist)
{
return session.add( new PlaylistEntry( track, playlist) );
return session.add( std::make_unique<PlaylistEntry>( track, playlist) );
}
+1 -1
View File
@@ -20,7 +20,7 @@
#ifndef DATABASE_PLAYLIST_HPP
#define DATABASE_PLAYLIST_HPP
#include <Wt/Dbo/Dbo>
#include <Wt/Dbo/Dbo.h>
#include <string>
+7 -8
View File
@@ -18,7 +18,6 @@
*/
#include "Types.hpp"
#include "SearchFilter.hpp"
#include "SqlQuery.hpp"
namespace Database
@@ -53,7 +52,7 @@ Release::getById(Wt::Dbo::Session& session, Release::id_type id)
Release::pointer
Release::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID)
{
return session.add(new Release(name, MBID));
return session.add(std::make_unique<Release>(name, MBID));
}
std::vector<Release::pointer>
@@ -74,7 +73,7 @@ Release::getAllOrphans(Wt::Dbo::Session& session)
static
Wt::Dbo::Query<Release::pointer>
getQuery(Wt::Dbo::Session& session,
const std::set<id_type>& clusterIds,
const std::set<Release::id_type>& clusterIds,
const std::vector<std::string> keywords)
{
WhereClause where;
@@ -140,22 +139,22 @@ Release::getReleaseYear(bool original) const
{
assert(session());
Wt::Dbo::collection<boost::posix_time::ptime> times = session()->query<boost::posix_time::ptime>(
Wt::Dbo::collection<Wt::WDate> dates = session()->query<Wt::WDate>(
std::string("SELECT ") + (original ? "t.original_date" : "t.date") + " FROM track t INNER JOIN release r ON r.id = t.release_id")
.where("r.id = ?")
.groupBy("t.date")
.bind(this->id());
/* various dates, no date */
if (times.empty() || times.size() > 1)
if (dates.empty() || dates.size() > 1)
return boost::none;
boost::gregorian::date date = times.front().date();
auto date = dates.front();
if (date.is_special())
if (!date.isValid())
return boost::none;
return boost::make_optional<int>(date.year());
return date.year();
}
std::vector<Wt::Dbo::ptr<Artist>>
+2 -4
View File
@@ -21,9 +21,7 @@
#include <boost/optional.hpp>
#include <Wt/Dbo/Dbo>
#include "SearchFilter.hpp"
#include <Wt/Dbo/Dbo.h>
namespace Database
{
@@ -68,7 +66,7 @@ class Release : public Wt::Dbo::Dbo<Release>
// Accessors
std::string getName() const { return _name; }
std::string getMBID() const { return _MBID; }
boost::posix_time::time_duration getDuration(void) const;
std::chrono::seconds getDuration(void) const;
// Get the artists of this release
std::vector<Wt::Dbo::ptr<Artist> > getArtists() const;
-40
View File
@@ -1,40 +0,0 @@
/*
* Copyright (C) 2015 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 "utils/Logger.hpp"
#include "SearchFilter.hpp"
namespace Database
{
void
SearchFilter::operator+=(const SearchFilter& filter)
{
}
SqlQuery
SearchFilter::generatePartialQuery()
{
SqlQuery sqlQuery;
return sqlQuery;
}
} // namespace Database
-68
View File
@@ -1,68 +0,0 @@
/*
* Copyright (C) 2015 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 _DB_SEARCH_FILTER_HPP_
#define _DB_SEARCH_FILTER_HPP_
#include <string>
#include <vector>
#include <map>
#include <Wt/Dbo/Dbo>
#include "SqlQuery.hpp"
namespace Database
{
typedef Wt::Dbo::dbo_default_traits::IdType id_type;
class SearchFilter
{
public:
typedef int id_type;
SearchFilter() {}
static SearchFilter Artist(std::string name) {return SearchFilter();}
static SearchFilter Artist(id_type id) {return SearchFilter();}
static SearchFilter Release(std::string name) {return SearchFilter();}
static SearchFilter Release(id_type id) {return SearchFilter();}
static SearchFilter Track(std::string name) {return SearchFilter();}
static SearchFilter Cluster(id_type id) {return SearchFilter();}
// Combine search filters by add operation
// Caution: multiple filters on different artist/release/track
// values may lead to empty results
void operator+=(const SearchFilter& filter);
SqlQuery generatePartialQuery();
private:
};
} // namespace Database
#endif // _DB_SEARCH_FILTER_HPP_
+6 -25
View File
@@ -52,8 +52,8 @@ Setting::getBool(Wt::Dbo::Session& session, std::string setting, bool defaultVal
return (res->_value == "true");
}
boost::posix_time::time_duration
Setting::getDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration defaultValue)
Wt::WTime
Setting::getTime(Wt::Dbo::Session& session, std::string setting, Wt::WTime defaultValue)
{
Wt::Dbo::Transaction transaction(session);
@@ -61,19 +61,7 @@ Setting::getDuration(Wt::Dbo::Session& session, std::string setting, boost::posi
if (!res)
return defaultValue;
return boost::posix_time::duration_from_string(res->_value);
}
boost::posix_time::ptime
Setting::getTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime defaultValue)
{
Wt::Dbo::Transaction transaction(session);
pointer res = getByName(session, setting);
if (!res)
return defaultValue;
return boost::posix_time::time_from_string(res->_value);
return Wt::WTime::fromString(res->_value);
}
int
@@ -93,7 +81,7 @@ Setting::getInt(Wt::Dbo::Session& session, std::string setting, int defaultValue
Setting::pointer
Setting::create(Wt::Dbo::Session& session, std::string name)
{
return session.add<Setting>(new Setting(name));
return session.add<Setting>(std::make_unique<Setting>(name));
}
Setting::pointer
@@ -127,17 +115,10 @@ Setting::setBool(Wt::Dbo::Session& session, std::string setting, bool value)
}
void
Setting::setDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration value)
Setting::setTime(Wt::Dbo::Session& session, std::string setting, Wt::WTime value)
{
Wt::Dbo::Transaction transaction(session);
getOrCreateByName(session, setting).modify()->_value = boost::posix_time::to_simple_string(value);
}
void
Setting::setTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime value)
{
Wt::Dbo::Transaction transaction(session);
getOrCreateByName(session, setting).modify()->_value = boost::posix_time::to_simple_string(value);
getOrCreateByName(session, setting).modify()->_value = value.toString().toUTF8();
}
void
+7 -11
View File
@@ -19,9 +19,8 @@
#pragma once
#include <Wt/Dbo/Dbo>
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <Wt/Dbo/Dbo.h>
#include <Wt/WTime.h>
namespace Database {
@@ -29,7 +28,10 @@ namespace Database {
class Setting
{
public:
using pointer = Wt::Dbo::ptr<Setting>;
Setting() {}
Setting(std::string name) : _name(name) {}
// check if a setting exists or not
static bool exists(Wt::Dbo::Session& session, std::string setting);
@@ -38,16 +40,14 @@ class Setting
// Nested transactions
static std::string getString(Wt::Dbo::Session& session, std::string setting, std::string defaultValue = "");
static bool getBool(Wt::Dbo::Session& session, std::string setting, bool defaultValue = false);
static boost::posix_time::time_duration getDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration defaultDuration = boost::posix_time::seconds(0) );
static boost::posix_time::ptime getTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime defaultTime = boost::posix_time::ptime());
static Wt::WTime getTime(Wt::Dbo::Session& session, std::string setting, Wt::WTime defaultValue = Wt::WTime());
static int getInt(Wt::Dbo::Session& session, std::string setting, int defaultValue = 0);
// Setters
// Nested transactions
static void setString(Wt::Dbo::Session& session, std::string setting, std::string value);
static void setBool(Wt::Dbo::Session& session, std::string setting, bool value);
static void setDuration(Wt::Dbo::Session& session, std::string setting, boost::posix_time::time_duration value);
static void setTime(Wt::Dbo::Session& session, std::string setting, boost::posix_time::ptime time);
static void setTime(Wt::Dbo::Session& session, std::string setting, Wt::WTime value);
static void setInt(Wt::Dbo::Session& session, std::string setting, int value);
template<class Action>
@@ -58,10 +58,6 @@ class Setting
}
private:
Setting(std::string name) : _name(name) {}
typedef Wt::Dbo::ptr<Setting> pointer;
static pointer getByName(Wt::Dbo::Session& session, std::string name);
static pointer create(Wt::Dbo::Session& session, std::string name);
static pointer getOrCreateByName(Wt::Dbo::Session& session, std::string name);
+4 -44
View File
@@ -17,10 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/property_tree/json_parser.hpp>
#include <Wt/Dbo/QueryModel>
#include "utils/Logger.hpp"
#include "SqlQuery.hpp"
@@ -75,7 +71,7 @@ Track::getByMBID(Wt::Dbo::Session& session, const std::string& mbid)
Track::pointer
Track::create(Wt::Dbo::Session& session, const boost::filesystem::path& p)
{
return session.add(new Track(p) );
return session.add(std::make_unique<Track>(p));
}
std::vector<boost::filesystem::path>
@@ -111,7 +107,7 @@ Track::getClusters(void) const
static
Wt::Dbo::Query< Track::pointer >
getQuery(Wt::Dbo::Session& session,
const std::set<id_type>& clusterIds,
const std::set<Cluster::id_type>& clusterIds,
const std::vector<std::string> keywords)
{
WhereClause where;
@@ -149,20 +145,6 @@ getQuery(Wt::Dbo::Session& session,
return query;
}
Track::StatsQueryResult
Track::getStats(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = filter.generatePartialQuery();
Wt::Dbo::Query<StatsQueryResult> query = session.query<StatsQueryResult>( "SELECT COUNT(\"id\"), SUM(\"dur\") FROM (SELECT t.id as \"id\", t.duration as \"dur\" FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get() + " GROUP BY t.id)");
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg);
return query;
}
std::vector<Track::pointer>
Track::getByFilter(Wt::Dbo::Session& session,
const std::set<id_type>& clusterIds,
@@ -233,7 +215,7 @@ Cluster::Cluster(Wt::Dbo::ptr<ClusterType> type, std::string name)
Cluster::pointer
Cluster::create(Wt::Dbo::Session& session, Wt::Dbo::ptr<ClusterType> type, std::string name)
{
return session.add(new Cluster(type, name));
return session.add(std::make_unique<Cluster>(type, name));
}
std::vector<Cluster::pointer>
@@ -244,28 +226,6 @@ Cluster::getAll(Wt::Dbo::Session& session)
return std::vector<Cluster::pointer>(res.begin(), res.end());
}
Wt::Dbo::Query<Cluster::pointer>
Cluster::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = filter.generatePartialQuery();
Wt::Dbo::Query<pointer> query
= session.query<pointer>( "SELECT g FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_c.track_id " + sqlQuery.where().get()).groupBy("c.name").orderBy("c.name");
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg);
return query;
}
std::vector<Cluster::pointer>
Cluster::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
{
Wt::Dbo::collection<pointer> res = getQuery(session, filter).limit(size).offset(offset);
return std::vector<pointer>(res.begin(), res.end());
}
ClusterType::ClusterType(std::string name)
: _name(name)
{
@@ -297,7 +257,7 @@ ClusterType::getAll(Wt::Dbo::Session& session)
ClusterType::pointer
ClusterType::create(Wt::Dbo::Session& session, std::string name)
{
return session.add(new ClusterType(name));
return session.add(std::make_unique<ClusterType>(name));
}
Cluster::pointer
+19 -31
View File
@@ -21,17 +21,15 @@
#include <string>
#include <vector>
#include <chrono>
#include <boost/filesystem.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/optional.hpp>
#include <Wt/Dbo/Dbo>
#include <Wt/Dbo/WtSqlTraits>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/WtSqlTraits.h>
#include <Wt/WDateTime>
#include "SearchFilter.hpp"
#include <Wt/WDateTime.h>
namespace Database {
@@ -52,7 +50,6 @@ class Cluster : public Wt::Dbo::Dbo<Cluster>
Cluster(Wt::Dbo::ptr<ClusterType> type, std::string name);
// Find utility
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static std::vector<pointer> getAll(Wt::Dbo::Session& session);
// Create utility
@@ -75,7 +72,6 @@ class Cluster : public Wt::Dbo::Dbo<Cluster>
}
private:
static Wt::Dbo::Query<pointer> getQuery(Wt::Dbo::Session& session, SearchFilter filter);
static const std::size_t _maxNameLength = 128;
@@ -159,14 +155,6 @@ class Track
static std::vector<pointer> getMBIDDuplicates(Wt::Dbo::Session& session);
static std::vector<pointer> getChecksumDuplicates(Wt::Dbo::Session& session);
// Utility fonctions
// Stats for a given search filter
typedef boost::tuple<
int, // Total tracks
boost::posix_time::time_duration // Total duration
> StatsQueryResult;
static StatsQueryResult getStats(Wt::Dbo::Session& session, SearchFilter filter);
// Create utility
static pointer create(Wt::Dbo::Session& session, const boost::filesystem::path& p);
@@ -179,12 +167,12 @@ class Track
void setDiscNumber(int num) { _discNumber = num; }
void setTotalDiscNumber(int num) { _totalDiscNumber = num; }
void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); }
void setDuration(boost::posix_time::time_duration duration) { _duration = duration; }
void setLastWriteTime(boost::posix_time::ptime time) { _fileLastWrite = time; }
void setAddedTime(boost::posix_time::ptime time) { _fileAdded = time; }
void setDuration(std::chrono::milliseconds duration) { _duration = duration; }
void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; }
void setAddedTime(Wt::WDateTime time) { _fileAdded = time; }
void setChecksum(const std::vector<unsigned char>& checksum) { _fileChecksum = checksum; }
void setDate(const boost::posix_time::ptime& date) { _date = date; }
void setOriginalDate(const boost::posix_time::ptime& date) { _originalDate = date; }
void setDate(Wt::WDate date) { _date = date; }
void setOriginalDate(Wt::WDate date) { _originalDate = date; }
void setGenres(const std::string& genreList) { _genreList = genreList; }
void setCoverType(CoverType coverType) { _coverType = coverType; }
void setMBID(const std::string& MBID) { _MBID = MBID; }
@@ -197,11 +185,11 @@ class Track
boost::optional<std::size_t> getTotalDiscNumber(void) const;
std::string getName(void) const { return _name; }
boost::filesystem::path getPath(void) const { return _filePath; }
boost::posix_time::time_duration getDuration(void) const { return _duration; }
boost::posix_time::ptime getDate(void) const { return _date; }
boost::posix_time::ptime getOriginalDate(void) const { return _originalDate; }
boost::posix_time::ptime getLastWriteTime(void) const { return _fileLastWrite; }
boost::posix_time::ptime getAddedTime(void) const { return _fileAdded; }
std::chrono::milliseconds getDuration(void) const { return _duration; }
Wt::WDate getDate(void) const { return _date; }
Wt::WDate getOriginalDate(void) const { return _originalDate; }
Wt::WDateTime getLastWriteTime(void) const { return _fileLastWrite; }
Wt::WDateTime getAddedTime(void) const { return _fileAdded; }
const std::vector<unsigned char>& getChecksum(void) const { return _fileChecksum; }
CoverType getCoverType(void) const { return _coverType; }
const std::string& getMBID(void) const { return _MBID; }
@@ -244,14 +232,14 @@ class Track
std::string _name;
std::string _artistName;
std::string _releaseName;
boost::posix_time::time_duration _duration;
boost::posix_time::ptime _date;
boost::posix_time::ptime _originalDate; // original date time
std::chrono::duration<int, std::milli> _duration;
Wt::WDate _date;
Wt::WDate _originalDate; // original date time
std::string _genreList;
std::string _filePath;
std::vector<unsigned char> _fileChecksum;
boost::posix_time::ptime _fileLastWrite;
boost::posix_time::ptime _fileAdded;
Wt::WDateTime _fileLastWrite;
Wt::WDateTime _fileAdded;
CoverType _coverType;
std::string _MBID; // Musicbrainz Identifier
+1 -1
View File
@@ -51,7 +51,7 @@ User::getAll(Wt::Dbo::Session& session)
User::pointer
User::create(Wt::Dbo::Session& session)
{
return session.add(new User());
return session.add(std::make_unique<User>());
}
User::pointer
+2 -2
View File
@@ -22,8 +22,8 @@
#include <vector>
#include <Wt/Dbo/Dbo>
#include <Wt/Auth/Dbo/AuthInfo>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Auth/Dbo/AuthInfo.h>
namespace Database {
-216
View File
@@ -1,216 +0,0 @@
/*
* Copyright (C) 2016 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 <curlpp/cURLpp.hpp>
#include <curlpp/Options.hpp>
#include <pstreams/pstream.h>
#include <boost/property_tree/json_parser.hpp>
#include "utils/Logger.hpp"
#include "utils/Path.hpp"
#include "FeatureExtractor.hpp"
namespace Feature {
static boost::filesystem::path extractorPath = boost::filesystem::path();
bool
Extractor::init(void)
{
static const std::string execName = "streaming_extractor_music";
extractorPath = searchExecPath(execName);
if (extractorPath.empty())
{
LMS_LOG(FEATURE, ERROR) << "Failed to find path to " << execName;
return false;
}
return true;
}
Extractor::Extractor()
{ }
static bool fetchJSONData(boost::property_tree::ptree& pt, std::string url)
{
try
{
curlpp::Cleanup myCleanup;
std::ostringstream os;
os << curlpp::options::Url(url);
std::istringstream iss(os.str());
boost::property_tree::ptree res;
boost::property_tree::json_parser::read_json(iss, res);
pt = res;
}
catch( curlpp::RuntimeError &e )
{
LMS_LOG(FEATURE, ERROR) << "curlpp error: " << e.what();
return false;
}
catch( curlpp::LogicError &e )
{
LMS_LOG(FEATURE, ERROR) << "curlpp error: " << e.what();
return false;
}
catch ( boost::property_tree::ptree_error& e)
{
LMS_LOG(FEATURE, ERROR) << "JSON paring failed: " << e.what();
return false;
}
return true;
}
bool
Extractor::getLowLevel(boost::property_tree::ptree& pt, std::string mbid)
{
LMS_LOG(FEATURE, DEBUG) << "Trying to fetch low level metadata for track '" << mbid << "' on AcousticBrainz";
boost::property_tree::ptree res;
if (!fetchJSONData(res, "https://acousticbrainz.org/" + mbid + "/low-level"))
return false;
auto message = res.get_child_optional("message");
if (message)
{
LMS_LOG(FEATURE, ERROR) << "Track '" << mbid << "': cannot get data on AcousticBrainz: " << message->data();
return false;
}
auto lowlevel = res.get_child_optional("lowlevel");
if (!lowlevel)
{
LMS_LOG(FEATURE, ERROR) << "Track '" << mbid << "': low level data not found!";
return false;
}
// Keep metadata to ease debugging
// res.erase("metadata");
pt = res;
return true;
}
bool
Extractor::getHighLevel(boost::property_tree::ptree& pt, std::string mbid)
{
LMS_LOG(FEATURE, DEBUG) << "Trying to fetch high level metadata for track '" << mbid << "' on AcousticBrainz";
// TODO check MBID
if (mbid.empty())
return false;
boost::property_tree::ptree res;
if (!fetchJSONData(res, "https://acousticbrainz.org/" + mbid + "/high-level"))
return false;
auto message = res.get_child_optional("message");
if (message)
{
LMS_LOG(FEATURE, ERROR) << "Track '" << mbid << "': cannot get data on AcousticBrainz: " << message->data();
return false;
}
auto lowlevel = res.get_child_optional("highlevel");
if (!lowlevel)
{
LMS_LOG(FEATURE, ERROR) << "Track '" << mbid << "': high level data not found!";
return false;
}
res.erase("metadata");
pt = res;
return true;
}
bool
Extractor::getLowLevel(boost::property_tree::ptree& pt, boost::filesystem::path path)
{
LMS_LOG(FEATURE, DEBUG) << "Extracting low level data from '" << path << "'";
if (extractorPath.empty())
return false;
std::vector<std::string> args;
args.push_back(extractorPath.string());
args.push_back(path.string());
args.push_back("-"); // output to stdout
std::string jsonData;
{
redi::ipstream in;
in.open(extractorPath.string(), args);
if (!in.is_open())
{
LMS_LOG(FEATURE, ERROR) << "Exec failed!";
return false;
}
bool firstLineHit = false;
std::string line;
while(std::getline(in, line))
{
if (!firstLineHit)
{
if (line != "{")
continue;
firstLineHit = true;
}
jsonData += line;
jsonData += '\n';
}
}
try
{
std::istringstream iss(jsonData);
boost::property_tree::json_parser::read_json(iss, pt);
pt.erase("metadata");
}
catch ( boost::property_tree::ptree_error& e)
{
LMS_LOG(FEATURE, ERROR) << "JSON parsing failed: " << e.what();
return false;
}
return true;
}
} // namespace Feature
-43
View File
@@ -1,43 +0,0 @@
/*
* Copyright (C) 2016 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 <boost/property_tree/ptree.hpp>
#pragma once
namespace Feature {
class Extractor
{
public:
static bool init(void);
static bool getLowLevel(boost::property_tree::ptree& pt, boost::filesystem::path path);
static bool getLowLevel(boost::property_tree::ptree& pt, std::string mbid);
static bool getHighLevel(boost::property_tree::ptree& pt, std::string mbid);
private:
Extractor();
};
} // namespace Feature
-126
View File
@@ -1,126 +0,0 @@
/*
* Copyright (C) 2016 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/property_tree/json_parser.hpp>
#include "utils/Config.hpp"
#include "utils/Logger.hpp"
#include "utils/Path.hpp"
#include "FeatureStore.hpp"
namespace Feature {
Store::Store()
{
}
Store&
Store::instance(void)
{
static Store instance;
return instance;
}
static boost::filesystem::path
getPath(std::string mbid, std::string type)
{
return Config::instance().getPath("working-dir") / "features" / (mbid + "_" + type);
}
bool
Store::exists(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type)
{
Wt::Dbo::Transaction transaction(session);
auto track = Database::Track::getById(session, trackId);
std::string mbid = track->getMBID();
transaction.commit();
if (mbid.empty())
return false;
boost::filesystem::path path = getPath(mbid, type);
return boost::filesystem::exists(path);
}
bool
Store::get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, boost::property_tree::ptree& feature)
{
Wt::Dbo::Transaction transaction(session);
auto track = Database::Track::getById(session, trackId);
std::string mbid = track->getMBID();
transaction.commit();
if (mbid.empty())
return false;
boost::filesystem::path path = getPath(mbid, type);
if (!boost::filesystem::exists(path))
return false;
try
{
std::ifstream iss(path.string().c_str(), std::ios::in);
boost::property_tree::json_parser::read_json(iss, feature);
}
catch (boost::property_tree::ptree_error& e)
{
LMS_LOG(FEATURE, ERROR) << "JSON parsing failed: " << e.what();
return false;
}
return true;
}
bool
Store::set(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, const boost::property_tree::ptree& feature)
{
Wt::Dbo::Transaction transaction(session);
auto track = Database::Track::getById(session, trackId);
std::string mbid = track->getMBID();
transaction.commit();
if (mbid.empty())
return false;
boost::filesystem::path path = getPath(mbid, type);
try
{
std::ofstream oss(path.string().c_str(), std::ios::out);
boost::property_tree::json_parser::write_json(oss, feature);
}
catch (boost::property_tree::ptree_error& e)
{
LMS_LOG(FEATURE, ERROR) << "JSON writing failed: " << e.what();
return false;
}
return true;
}
} // namespace CoverArt
-47
View File
@@ -1,47 +0,0 @@
/*
* Copyright (C) 2016 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <boost/filesystem/path.hpp>
#include <boost/property_tree/ptree.hpp>
#include "database/Types.hpp"
namespace Feature {
class Store
{
public:
Store(const Store&) = delete;
Store& operator=(const Store&) = delete;
static Store& instance();
bool exists(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type);
bool get(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, boost::property_tree::ptree& feature);
bool set(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::string type, const boost::property_tree::ptree& feature);
private:
Store();
};
} // namespace CoverArt
+1 -1
View File
@@ -79,7 +79,7 @@ Image::load(boost::filesystem::path p)
}
catch (Magick::Exception& e)
{
LMS_LOG(COVER, ERROR) << "Caught Magick exception while loading image from file '" << p << "': " << e.what();
LMS_LOG(COVER, ERROR) << "Caught Magick exception while loading image from file '" << p.string() << "': " << e.what();
return false;
}
}
+7 -8
View File
@@ -20,14 +20,14 @@
#include <boost/filesystem.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <Wt/WServer>
#include <Wt/WServer.h>
#include <Wt/WApplication.h>
#include "utils/Config.hpp"
#include "utils/Logger.hpp"
#include "av/AvInfo.hpp"
#include "av/AvTranscoder.hpp"
#include "image/Image.hpp"
#include "feature/FeatureExtractor.hpp"
#include "scanner/MediaScanner.hpp"
@@ -117,17 +117,16 @@ int main(int argc, char* argv[])
Av::AvInit();
Av::Transcoder::init();
Database::Handler::configureAuth();
Feature::Extractor::init();
// Initializing a connection pool to the database that will be shared along services
std::unique_ptr<Wt::Dbo::SqlConnectionPool>
connectionPool( Database::Handler::createConnectionPool(Config::instance().getPath("working-dir") / "lms.db"));
auto connectionPool = Database::Handler::createConnectionPool(Config::instance().getPath("working-dir") / "lms.db");
Scanner::MediaScanner scanner(*connectionPool);
// bind entry point
server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create,
_1, boost::ref(*connectionPool), boost::ref(scanner)));
server.addEntryPoint(Wt::EntryPointType::Application,
std::bind(UserInterface::LmsApplication::create,
std::placeholders::_1, std::ref(*connectionPool), std::ref(scanner)));
// Start
LMS_LOG(MAIN, INFO) << "Starting Media scanner...";
@@ -138,7 +137,7 @@ int main(int argc, char* argv[])
// Wait
LMS_LOG(MAIN, INFO) << "Now running...";
Wt::WServer::waitForShutdown(argv[0]);
Wt::WServer::waitForShutdown();
// Stop
LMS_LOG(MAIN, INFO) << "Stopping server...";
+25 -25
View File
@@ -77,11 +77,11 @@ AvFormat::parse(const boost::filesystem::path& p)
std::cout << "TAG = " << tag << ", VAL = " << value << std::endl;
#endif
if (tag == "ARTIST")
items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( stringToUTF8(value)) ));
items.insert( std::make_pair(MetaData::Type::Artist, stringTrim( value) ));
else if (tag == "ALBUM")
items.insert( std::make_pair(MetaData::Type::Album, stringTrim( stringToUTF8(value)) ));
items.insert( std::make_pair(MetaData::Type::Album, stringTrim( value) ));
else if (tag == "TITLE")
items.insert( std::make_pair(MetaData::Type::Title, stringTrim( stringToUTF8(value)) ));
items.insert( std::make_pair(MetaData::Type::Title, stringTrim( value) ));
else if (tag == "TRACK")
{
// Expecting 'Number/Total'
@@ -89,15 +89,15 @@ AvFormat::parse(const boost::filesystem::path& p)
if (strings.size() > 0)
{
std::size_t number;
if (readAs<std::size_t>(strings[0], number))
items.insert( std::make_pair(MetaData::Type::TrackNumber, number ));
auto number = readAs<std::size_t>(strings[0]);
if (number)
items.insert( std::make_pair(MetaData::Type::TrackNumber, *number ));
if (strings.size() > 1)
{
std::size_t totalNumber;
if (readAs<std::size_t>(strings[1], totalNumber))
items.insert( std::make_pair(MetaData::Type::TotalTrack, totalNumber ));
auto totalNumber = readAs<std::size_t>(strings[1]);
if (totalNumber)
items.insert( std::make_pair(MetaData::Type::TotalTrack, *totalNumber ));
}
}
}
@@ -108,15 +108,15 @@ AvFormat::parse(const boost::filesystem::path& p)
if (strings.size() > 0)
{
std::size_t number;
if (readAs<std::size_t>(strings[0], number))
items.insert( std::make_pair(MetaData::Type::DiscNumber, number ));
auto number = readAs<std::size_t>(strings[0]);
if (number)
items.insert( std::make_pair(MetaData::Type::DiscNumber, *number ));
if (strings.size() > 1)
{
std::size_t totalNumber;
if (readAs<std::size_t>(strings[1], totalNumber))
items.insert( std::make_pair(MetaData::Type::TotalDisc, totalNumber ));
auto totalNumber = readAs<std::size_t>(strings[1]);
if (totalNumber)
items.insert( std::make_pair(MetaData::Type::TotalDisc, *totalNumber ));
}
}
}
@@ -124,36 +124,36 @@ AvFormat::parse(const boost::filesystem::path& p)
|| tag == "YEAR"
|| tag == "WM/Year")
{
boost::posix_time::ptime p;
if (readAsPosixTime(value, p))
items.insert( std::make_pair(MetaData::Type::Date, p));
auto date = readAs<Wt::WDate>(value);
if (date)
items.insert( std::make_pair(MetaData::Type::Date, *date));
}
else if (tag == "TDOR" // Original release time (ID3v2 2.4)
|| tag == "TORY") // Original release year
{
boost::posix_time::ptime p;
if (readAsPosixTime(value, p))
items.insert( std::make_pair(MetaData::Type::OriginalDate, p));
auto date = readAs<Wt::WDate>(value);
if (date)
items.insert( std::make_pair(MetaData::Type::OriginalDate, *date));
}
else if (tag == "MUSICBRAINZ ARTIST ID"
|| tag == "MUSICBRAINZ_ARTISTID")
{
items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, stringTrim( stringToUTF8(value)) ));
items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, stringTrim(value)) );
}
else if (tag == "MUSICBRAINZ ALBUM ID"
|| tag == "MUSICBRAINZ_ALBUMID")
{
items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, stringTrim( stringToUTF8(value)) ));
items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, stringTrim(value)) );
}
else if (tag == "MUSICBRAINZ RELEASE TRACK ID"
|| tag == "MUSICBRAINZ_RELEASETRACKID"
|| tag == "MUSICBRAINZ_TRACKID")
{
items.insert( std::make_pair(MetaData::Type::MusicBrainzTrackID, stringTrim( stringToUTF8(value)) ));
items.insert( std::make_pair(MetaData::Type::MusicBrainzTrackID, stringTrim(value)) );
}
else if (tag == "ACOUSTID ID")
{
items.insert( std::make_pair(MetaData::Type::AcoustID, stringTrim( stringToUTF8(value)) ));
items.insert( std::make_pair(MetaData::Type::AcoustID, stringTrim(value)) );
}
else if (_clusterMap.find(tag) != _clusterMap.end())
{
+3 -3
View File
@@ -37,13 +37,13 @@ namespace MetaData
Title, // string
Album, // string
Clusters, // Clusters, ex: { "genre", {"death metal", "brutal death"} }, { "albumgrouping", {"metal"} }
Duration, // boost::posix_time::time_duration
Duration, // std::chrono::milliseconds
TrackNumber, // size_t
DiscNumber, // size_t
TotalTrack, // size_t
TotalDisc, // size_t
Date, // boost::posix_time::ptime
OriginalDate, // boost::posix_time::ptime
Date, // Wt::WDate
OriginalDate, // Wt::WDate
HasCover, // bool
AudioStreams, // vector<AudioStream>
MusicBrainzArtistID, // string
+22 -23
View File
@@ -55,8 +55,7 @@ TagLibParser::parse(const boost::filesystem::path& p)
{
TagLib::AudioProperties *properties = f.audioProperties();
boost::posix_time::time_duration duration = boost::posix_time::seconds(properties->length());
std::chrono::milliseconds duration(properties->length() * 1000);
items.insert( std::make_pair(MetaData::Type::Duration, duration) );
MetaData::AudioStream audioStream = { .bitRate = static_cast<std::size_t>(properties->bitrate() * 1000) };
@@ -124,15 +123,15 @@ TagLibParser::parse(const boost::filesystem::path& p)
if (!strings.empty())
{
std::size_t number;
if (readAs<std::size_t>(strings[0], number))
items.insert( std::make_pair(MetaData::Type::TrackNumber, number ));
auto number = readAs<std::size_t>(strings[0]);
if (number)
items.insert( std::make_pair(MetaData::Type::TrackNumber, *number ));
if (strings.size() > 1)
{
std::size_t totalNumber;
if (readAs<std::size_t>(strings[1], totalNumber))
items.insert( std::make_pair(MetaData::Type::TotalTrack, totalNumber ));
auto totalNumber = readAs<std::size_t>(strings[1]);
if (totalNumber)
items.insert( std::make_pair(MetaData::Type::TotalTrack, *totalNumber ));
}
}
}
@@ -143,32 +142,32 @@ TagLibParser::parse(const boost::filesystem::path& p)
if (!strings.empty())
{
std::size_t number;
if (readAs<std::size_t>(strings[0], number))
items.insert( std::make_pair(MetaData::Type::DiscNumber, number ));
auto number = readAs<std::size_t>(strings[0]);
if (number)
items.insert( std::make_pair(MetaData::Type::DiscNumber, *number));
if (strings.size() > 1)
{
std::size_t totalNumber;
if (readAs<std::size_t>(strings[1], totalNumber))
items.insert( std::make_pair(MetaData::Type::TotalDisc, totalNumber ));
auto totalNumber = readAs<std::size_t>(strings[1]);
if (totalNumber)
items.insert( std::make_pair(MetaData::Type::TotalDisc, *totalNumber ));
}
}
}
else if (tag == "DATE")
{
boost::posix_time::ptime p;
if (readAsPosixTime(values.front().to8Bit(), p))
items.insert( std::make_pair(MetaData::Type::Date, p));
auto timePoint = readAs<Wt::WDate>(values.front().to8Bit());
if (timePoint)
items.insert( std::make_pair(MetaData::Type::Date, *timePoint));
}
else if (tag == "ORIGINALDATE")
{
boost::posix_time::ptime p;
if (readAsPosixTime(values.front().to8Bit(), p))
auto timePoint = readAs<Wt::WDate>(values.front().to8Bit());
if (timePoint)
{
// Take priority on original year
items.erase( MetaData::Type::OriginalDate );
items.insert( std::make_pair(MetaData::Type::OriginalDate, p));
items.insert( std::make_pair(MetaData::Type::OriginalDate, *timePoint));
}
}
else if (tag == "ORIGINALYEAR")
@@ -176,9 +175,9 @@ TagLibParser::parse(const boost::filesystem::path& p)
// lower priority than original date
if (items.find(MetaData::Type::OriginalDate) == items.end())
{
boost::posix_time::ptime p;
if (readAsPosixTime(values.front().to8Bit(), p))
items.insert( std::make_pair(MetaData::Type::OriginalDate, p));
auto timePoint = readAs<Wt::WDate>(values.front().to8Bit());
if (timePoint)
items.insert( std::make_pair(MetaData::Type::OriginalDate, *timePoint));
}
}
else if (tag == "METADATA_BLOCK_PICTURE")
+61 -76
View File
@@ -20,9 +20,10 @@
#include <stdexcept>
#include <boost/filesystem.hpp>
#include <boost/thread.hpp>
#include <boost/asio/placeholders.hpp>
#include <Wt/WLocalDateTime.h>
#include "cover/CoverArtGrabber.hpp"
#include "database/Setting.hpp"
@@ -36,40 +37,28 @@
namespace {
boost::gregorian::date
getNextDay(const boost::gregorian::date& current)
Wt::WDate
getNextMonday(Wt::WDate current)
{
boost::gregorian::day_iterator it(current);
return *(++it);
do
{
current.addDays(1);
} while (current.dayOfWeek() != 1);
return current;
}
boost::gregorian::date
getNextMonday(const boost::gregorian::date& current)
Wt::WDate
getNextFirstOfMonth(Wt::WDate current)
{
boost::gregorian::day_iterator it(current);
do
{
current.addDays(1);
} while (current.day() != 1);
++it;
// While it's not monday
while( it->day_of_week() != 1 )
++it;
return *(it);
return current;
}
boost::gregorian::date
getNextFirstOfMonth(const boost::gregorian::date& current)
{
boost::gregorian::day_iterator it(current);
++it;
// While it's not the 1st of the month
while( it->day() != 1 )
++it;
return (*it);
}
bool
isFileSupported(const boost::filesystem::path& file, const std::vector<boost::filesystem::path> extensions)
{
@@ -119,14 +108,14 @@ setUpdatePeriod(Wt::Dbo::Session& session, UpdatePeriod updatePeriod)
Setting::setInt(session, "update_period", static_cast<int>(updatePeriod));
}
boost::posix_time::time_duration getUpdateStartTime(Wt::Dbo::Session& session)
Wt::WTime getUpdateStartTime(Wt::Dbo::Session& session)
{
return Setting::getDuration(session, "update_start_time");
return Setting::getTime(session, "update_start_time");
}
void setUpdateStartTime(Wt::Dbo::Session& session, boost::posix_time::time_duration startTime)
void setUpdateStartTime(Wt::Dbo::Session& session, Wt::WTime startTime)
{
Setting::setDuration(session, "update_start_time", startTime);
Setting::setTime(session, "update_start_time", startTime);
}
MediaScanner::MediaScanner(Wt::Dbo::SqlConnectionPool& connectionPool)
@@ -176,7 +165,7 @@ MediaScanner::scheduleImmediateScan()
_ioService.post([=]()
{
LMS_LOG(DBUPDATER, INFO) << "Schedule immediate scan";
scheduleScan( boost::posix_time::seconds(0) );
scheduleScan(std::chrono::seconds(0));
});
}
@@ -193,29 +182,31 @@ MediaScanner::reschedule()
void
MediaScanner::scheduleScan()
{
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
boost::posix_time::time_duration startTime = getUpdateStartTime(_db.getSession());
using namespace std::chrono_literals;
boost::gregorian::date nextScanDate;
Wt::WTime startTime = getUpdateStartTime(_db.getSession());
Wt::WDateTime now = Wt::WLocalDateTime::currentServerDateTime().toUTC();
Wt::WDate nextScanDate;
switch ( getUpdatePeriod(_db.getSession()) )
{
case UpdatePeriod::Daily:
if (now.time_of_day() < startTime)
if (now.time() < startTime)
nextScanDate = now.date();
else
nextScanDate = getNextDay(now.date());
nextScanDate = now.date().addDays(1);
break;
case UpdatePeriod::Weekly:
if (now.time_of_day() < startTime && now.date().day_of_week() == 1)
if (now.time() < startTime && now.date().dayOfWeek() == 1)
nextScanDate = now.date();
else
nextScanDate = getNextMonday(now.date());
break;
case UpdatePeriod::Monthly:
if (now.time_of_day() < startTime && now.date().day() == 1)
if (now.time() < startTime && now.date().day() == 1)
nextScanDate = now.date();
else
nextScanDate = getNextFirstOfMonth(now.date());
@@ -226,24 +217,25 @@ MediaScanner::scheduleScan()
break;
}
if (!nextScanDate.is_special())
scheduleScan( boost::posix_time::ptime (nextScanDate, startTime) );
if (nextScanDate.isValid())
scheduleScan( Wt::WDateTime(nextScanDate, startTime).toTimePoint() );
}
void
MediaScanner::scheduleScan( boost::posix_time::time_duration duration)
MediaScanner::scheduleScan(std::chrono::seconds duration)
{
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan in " << duration;
_scheduleTimer.expires_from_now(duration);
_scheduleTimer.async_wait( boost::bind( &MediaScanner::scan, this, boost::asio::placeholders::error) );
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan in " << duration.count() << " seconds";
_scheduleTimer.expires_from_now(std::chrono::seconds(5)); //duration);
_scheduleTimer.async_wait( std::bind( &MediaScanner::scan, this, std::placeholders::_1) );
}
void
MediaScanner::scheduleScan( boost::posix_time::ptime time)
MediaScanner::scheduleScan(std::chrono::system_clock::time_point timePoint)
{
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan at " << time;
_scheduleTimer.expires_at(time);
_scheduleTimer.async_wait( boost::bind( &MediaScanner::scan, this, boost::asio::placeholders::error) );
std::time_t t = std::chrono::system_clock::to_time_t(timePoint);
LMS_LOG(DBUPDATER, INFO) << "Scheduling next scan at " << std::string(std::ctime(&t));
_scheduleTimer.expires_at(timePoint);
_scheduleTimer.async_wait(std::bind(&MediaScanner::scan, this, std::placeholders::_1));
}
void
@@ -263,9 +255,9 @@ MediaScanner::scan(boost::system::error_code err)
if (!_running)
break;
LMS_LOG(DBUPDATER, INFO) << "scaning root directory '" << rootDirectory << "'...";
LMS_LOG(DBUPDATER, INFO) << "scaning root directory '" << rootDirectory.string() << "'...";
scanRootDirectory(rootDirectory, stats);
LMS_LOG(DBUPDATER, INFO) << "scaning root directory '" << rootDirectory << "' DONE";
LMS_LOG(DBUPDATER, INFO) << "scaning root directory '" << rootDirectory.string() << "' DONE";
}
if (_running)
@@ -273,16 +265,9 @@ MediaScanner::scan(boost::system::error_code err)
LMS_LOG(DBUPDATER, INFO) << "Scan " << (_running ? "complete" : "aborted") << ". Changes = " << stats.nbChanges() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.scanErrors << ", not imported = " << stats.incompleteScans << "), duplicates = " << stats.nbDuplicates() << " (hash = " << stats.duplicateHashes << ", mbid = " << stats.duplicateMBID << ")";
// Update database stats
boost::posix_time::ptime now = boost::posix_time::second_clock::local_time();
if (stats.nbChanges() > 0)
Setting::setTime(_db.getSession(), "last_update", now);
// Save the last scan only if it has been completed
if (_running)
{
Setting::setTime(_db.getSession(), "last_scan", now);
scheduleScan();
scanComplete().emit(stats);
@@ -408,7 +393,7 @@ MediaScanner::getClusters( const MetaData::Clusters& clustersNames)
void
MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats)
{
boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) );
auto lastWriteTime = Wt::WDateTime::fromTime_t(boost::filesystem::last_write_time(file));
// Skip file if last write is the same
{
@@ -445,7 +430,7 @@ MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats)
if ((*items).find(MetaData::Type::AudioStreams) == (*items).end()
|| boost::any_cast<std::vector<MetaData::AudioStream>> ((*items)[MetaData::Type::AudioStreams]).empty())
{
LMS_LOG(DBUPDATER, INFO) << "Skipped '" << file << "' (no audio stream found)";
LMS_LOG(DBUPDATER, INFO) << "Skipped '" << file.string() << "' (no audio stream found)";
// If Track exists here, delete it!
if (track)
@@ -457,9 +442,9 @@ MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats)
return;
}
if ((*items).find(MetaData::Type::Duration) == (*items).end()
|| boost::any_cast<boost::posix_time::time_duration>((*items)[MetaData::Type::Duration]).total_seconds() <= 0)
|| boost::any_cast<std::chrono::milliseconds>((*items)[MetaData::Type::Duration]).count() <= 0)
{
LMS_LOG(DBUPDATER, INFO) << "Skipped '" << file << "' (no duration or duration <= 0)";
LMS_LOG(DBUPDATER, INFO) << "Skipped '" << file.string() << "' (no duration or duration <= 0)";
// If Track exists here, delete it!
if (track)
@@ -534,12 +519,12 @@ MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats)
{
// Create a new song
track = Track::create(_db.getSession(), file);
LMS_LOG(DBUPDATER, INFO) << "Adding '" << file << "'";
LMS_LOG(DBUPDATER, INFO) << "Adding '" << file.string() << "'";
stats.additions++;
}
else
{
LMS_LOG(DBUPDATER, INFO) << "Updating '" << file << "'";
LMS_LOG(DBUPDATER, INFO) << "Updating '" << file.string() << "'";
// Remove the songs from its clusters
for (auto cluster : track->getClusters())
@@ -555,8 +540,8 @@ MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats)
track.modify()->setRelease(release);
track.modify()->setLastWriteTime(lastWriteTime);
track.modify()->setName(title);
track.modify()->setDuration( boost::any_cast<boost::posix_time::time_duration>((*items)[MetaData::Type::Duration]) );
track.modify()->setAddedTime( boost::posix_time::second_clock::local_time() );
track.modify()->setDuration( boost::any_cast<std::chrono::milliseconds>((*items)[MetaData::Type::Duration]) );
track.modify()->setAddedTime( Wt::WLocalDateTime::currentServerDateTime().toUTC() );
{
std::string trackClusterList;
@@ -586,15 +571,15 @@ MediaScanner::scanAudioFile( const boost::filesystem::path& file, Stats& stats)
track.modify()->setTotalDiscNumber( boost::any_cast<std::size_t>((*items)[MetaData::Type::TotalDisc]) );
if ((*items).find(MetaData::Type::Date) != (*items).end())
track.modify()->setDate( boost::any_cast<boost::posix_time::ptime>((*items)[MetaData::Type::Date]) );
track.modify()->setDate( boost::any_cast<Wt::WDate>((*items)[MetaData::Type::Date]) );
if ((*items).find(MetaData::Type::OriginalDate) != (*items).end())
{
track.modify()->setOriginalDate( boost::any_cast<boost::posix_time::ptime>((*items)[MetaData::Type::OriginalDate]) );
track.modify()->setOriginalDate( boost::any_cast<Wt::WDate>((*items)[MetaData::Type::OriginalDate]) );
// If a file has an OriginalDate but no date, set the date to ease filtering
if ((*items).find(MetaData::Type::Date) == (*items).end())
track.modify()->setDate( boost::any_cast<boost::posix_time::ptime>((*items)[MetaData::Type::OriginalDate]) );
track.modify()->setDate( boost::any_cast<Wt::WDate>((*items)[MetaData::Type::OriginalDate]) );
}
if ((*items).find(MetaData::Type::MusicBrainzRecordingID) != (*items).end())
@@ -652,7 +637,7 @@ checkFile(const boost::filesystem::path& p, const std::vector<boost::filesystem:
if (!boost::filesystem::exists( p )
|| !boost::filesystem::is_regular( p ) )
{
LMS_LOG(DBUPDATER, INFO) << "Missing file '" << p << "'";
LMS_LOG(DBUPDATER, INFO) << "Missing file '" << p.string() << "'";
status = false;
}
else
@@ -670,12 +655,12 @@ checkFile(const boost::filesystem::path& p, const std::vector<boost::filesystem:
if (!foundRoot)
{
LMS_LOG(DBUPDATER, INFO) << "Out of root file '" << p << "'";
LMS_LOG(DBUPDATER, INFO) << "Out of root file '" << p.string() << "'";
status = false;
}
else if (!isFileSupported(p, extensions))
{
LMS_LOG(DBUPDATER, INFO) << "File format no longer supported for '" << p << "'";
LMS_LOG(DBUPDATER, INFO) << "File format no longer supported for '" << p.string() << "'";
status = false;
}
}
@@ -685,7 +670,7 @@ checkFile(const boost::filesystem::path& p, const std::vector<boost::filesystem:
}
catch (boost::filesystem::filesystem_error& e)
{
LMS_LOG(DBUPDATER, ERROR) << "Caught exception while checking file '" << p << "': " << e.what();
LMS_LOG(DBUPDATER, ERROR) << "Caught exception while checking file '" << p.string() << "': " << e.what();
return false;
}
@@ -779,14 +764,14 @@ MediaScanner::checkDuplicatedAudioFiles(Stats& stats)
std::vector<Track::pointer> tracks = Database::Track::getMBIDDuplicates(_db.getSession());
for (Track::pointer track : tracks)
{
LMS_LOG(DBUPDATER, INFO) << "Found duplicated MBID [" << track->getMBID() << "], file: " << track->getPath() << " - " << track->getName();
LMS_LOG(DBUPDATER, INFO) << "Found duplicated MBID [" << track->getMBID() << "], file: " << track->getPath().string() << " - " << track->getName();
stats.duplicateMBID++;
}
tracks = Database::Track::getChecksumDuplicates(_db.getSession());
for (Track::pointer track : tracks)
{
LMS_LOG(DBUPDATER, INFO) << "Found duplicated checksum [" << bufferToString(track->getChecksum()) << "], file: " << track->getPath() << " - " << track->getName();
LMS_LOG(DBUPDATER, INFO) << "Found duplicated checksum [" << bufferToString(track->getChecksum()) << "], file: " << track->getPath().string() << " - " << track->getName();
stats.duplicateHashes++;
}
+10 -8
View File
@@ -19,10 +19,12 @@
#pragma once
#include <boost/asio/deadline_timer.hpp>
#include <chrono>
#include <Wt/WIOService>
#include <Wt/WSignal>
#include <Wt/WIOService.h>
#include <Wt/WSignal.h>
#include <boost/asio/system_timer.hpp>
#include "metadata/TagLibParser.hpp"
@@ -40,8 +42,8 @@ enum class UpdatePeriod {
UpdatePeriod getUpdatePeriod(Wt::Dbo::Session& session);
void setUpdatePeriod(Wt::Dbo::Session& session, UpdatePeriod updatePeriod);
boost::posix_time::time_duration getUpdateStartTime(Wt::Dbo::Session& session);
void setUpdateStartTime(Wt::Dbo::Session& session, boost::posix_time::time_duration);
Wt::WTime getUpdateStartTime(Wt::Dbo::Session& session);
void setUpdateStartTime(Wt::Dbo::Session& session, Wt::WTime time);
class MediaScanner
{
@@ -92,8 +94,8 @@ class MediaScanner
// Job handling
void scheduleScan();
void scheduleScan(boost::posix_time::time_duration duration);
void scheduleScan(boost::posix_time::ptime time);
void scheduleScan(std::chrono::seconds duration);
void scheduleScan(std::chrono::system_clock::time_point time);
// Update database (scheduled callback)
void scan(boost::system::error_code ec);
@@ -119,7 +121,7 @@ class MediaScanner
Wt::Signal<Database::Track::pointer> _sigAddedTrack;
Wt::Signal<Database::Track::pointer> _sigRemovedTrack;
boost::asio::deadline_timer _scheduleTimer;
boost::asio::system_timer _scheduleTimer;
Database::Handler _db;
+22 -25
View File
@@ -17,10 +17,10 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WFormModel>
#include <Wt/WLineEdit>
#include <Wt/WCheckBox>
#include <Wt/WPushButton>
#include <Wt/WFormModel.h>
#include <Wt/WLineEdit.h>
#include <Wt/WCheckBox.h>
#include <Wt/WPushButton.h>
#include "utils/Logger.hpp"
@@ -31,10 +31,10 @@
namespace UserInterface {
Auth::Auth(Wt::WContainerWidget *parent)
: Wt::WTemplateFormView(parent)
Auth::Auth()
: Wt::WTemplateFormView()
{
_model = new Wt::Auth::AuthModel(DbHandler().getAuthService(), DbHandler().getUserDatabase());
_model = std::make_shared<Wt::Auth::AuthModel>(LmsApp->getDb().getAuthService(), LmsApp->getDb().getUserDatabase());
_model->addPasswordAuth(&Database::Handler::getPasswordService());
@@ -43,50 +43,47 @@ Auth::Auth(Wt::WContainerWidget *parent)
addFunction("id", &WTemplate::Functions::id);
// LoginName
auto loginName = new Wt::WLineEdit();
setFormWidget(Wt::Auth::AuthModel::LoginNameField, loginName);
setFormWidget(Wt::Auth::AuthModel::LoginNameField, std::make_unique<Wt::WLineEdit>());
// Password
auto password = new Wt::WLineEdit();
setFormWidget(Wt::Auth::AuthModel::PasswordField, password);
password->setEchoMode(Wt::WLineEdit::Password);
auto password = std::make_unique<Wt::WLineEdit>();
password->setEchoMode(Wt::EchoMode::Password);
password->enterPressed().connect(this, &Auth::processAuth);
setFormWidget(Wt::Auth::AuthModel::PasswordField, std::move(password));
// Remember Me
auto rememberMe = new Wt::WCheckBox();
setFormWidget(Wt::Auth::AuthModel::RememberMeField, rememberMe);
setFormWidget(Wt::Auth::AuthModel::RememberMeField, std::make_unique<Wt::WCheckBox>());
auto loginBtn = new Wt::WPushButton(Wt::WString::tr("Lms.login"));
bindWidget("login-btn", loginBtn);
Wt::WPushButton* loginBtn = bindNew<Wt::WPushButton>("login-btn", Wt::WString::tr("Lms.login"));
loginBtn->clicked().connect(this, &Auth::processAuth);
password->enterPressed().connect(this, &Auth::processAuth);
DbHandler().getLogin().changed().connect(std::bind([=]
LmsApp->getDb().getLogin().changed().connect(std::bind([=]
{
if (DbHandler().getLogin().loggedIn())
if (LmsApp->getDb().getLogin().loggedIn())
this->setHidden(true);
}));
Wt::Auth::User user = _model->processAuthToken();
_model->loginUser(DbHandler().getLogin(), user, Wt::Auth::WeakLogin);
_model->loginUser(LmsApp->getDb().getLogin(), user, Wt::Auth::LoginState::Weak);
updateView(_model);
updateView(_model.get());
}
void
Auth::processAuth()
{
updateModel(_model);
updateModel(_model.get());
if (_model->validate())
_model->login(DbHandler().getLogin());
_model->login(LmsApp->getDb().getLogin());
else
updateView(_model);
updateView(_model.get());
}
void
Auth::logout()
{
_model->logout(DbHandler().getLogin());
_model->logout(LmsApp->getDb().getLogin());
}
} // namespace UserInterface
+4 -6
View File
@@ -19,24 +19,22 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WTemplateFormView>
#include <Wt/Auth/Login>
#include <Wt/Auth/AuthModel>
#include <Wt/WTemplateFormView.h>
#include <Wt/Auth/AuthModel.h>
namespace UserInterface {
class Auth : public Wt::WTemplateFormView
{
public:
Auth(Wt::WContainerWidget *parent = 0);
Auth();
void logout();
private:
void processAuth();
Wt::Auth::AuthModel* _model;
std::shared_ptr<Wt::Auth::AuthModel> _model;
};
+5 -4
View File
@@ -17,7 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WTemplate>
#include <Wt/WTemplate.h>
#include "utils/Logger.hpp"
@@ -28,12 +28,13 @@
namespace UserInterface {
Home::Home(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent)
Home::Home()
: Wt::WContainerWidget()
{
auto t = new Wt::WTemplate(Wt::WString::tr("Lms.Home.template"), this);
auto t = std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Home.template"));
t->addFunction("tr", &Wt::WTemplate::Functions::tr);
this->addWidget(std::move(t));
}
} // namespace UserInterface
+2 -2
View File
@@ -19,14 +19,14 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WContainerWidget.h>
namespace UserInterface {
class Home : public Wt::WContainerWidget
{
public:
Home(Wt::WContainerWidget *parent = 0);
Home();
private:
+60 -110
View File
@@ -17,15 +17,16 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WAnchor>
#include <Wt/WBootstrapTheme>
#include <Wt/WEnvironment>
#include <Wt/WMenu>
#include <Wt/WNavigationBar>
#include <Wt/WPopupMenu>
#include <Wt/WStackedWidget>
#include <Wt/WText>
#include <Wt/Auth/Identity>
#include <Wt/WAnchor.h>
#include <Wt/WBootstrapTheme.h>
#include <Wt/WEnvironment.h>
#include <Wt/WMenu.h>
#include <Wt/WNavigationBar.h>
#include <Wt/WPopupMenu.h>
#include <Wt/WServer.h>
#include <Wt/WStackedWidget.h>
#include <Wt/WText.h>
#include <Wt/Auth/Identity.h>
#include "config/config.h"
#include "utils/Logger.hpp"
@@ -49,14 +50,14 @@
namespace UserInterface {
Wt::WApplication*
std::unique_ptr<Wt::WApplication>
LmsApplication::create(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner)
{
/*
* You could read information from the environment to decide whether
* the user has permission to start a new application
*/
return new LmsApplication(env, connectionPool, scanner);
return std::make_unique<LmsApplication>(env, connectionPool, scanner);
}
LmsApplication*
@@ -78,10 +79,10 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti
_imageResource(nullptr),
_transcodeResource(nullptr)
{
Wt::WBootstrapTheme *bootstrapTheme = new Wt::WBootstrapTheme(this);
bootstrapTheme->setVersion(Wt::WBootstrapTheme::Version3);
auto bootstrapTheme = std::make_unique<Wt::WBootstrapTheme>();
bootstrapTheme->setVersion(Wt::BootstrapVersion::v3);
bootstrapTheme->setResponsive(true);
setTheme(bootstrapTheme);
setTheme(std::move(bootstrapTheme));
useStyleSheet("css/lms.css");
useStyleSheet("resources/font-awesome/css/font-awesome.min.css");
@@ -90,7 +91,7 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti
messageResourceBundle().use(appRoot() + "admin-database");
messageResourceBundle().use(appRoot() + "admin-user");
messageResourceBundle().use(appRoot() + "admin-users");
messageResourceBundle().use(appRoot() + "admin-wizard");
messageResourceBundle().use(appRoot() + "admin-initwizard");
messageResourceBundle().use(appRoot() + "artist");
messageResourceBundle().use(appRoot() + "artists");
messageResourceBundle().use(appRoot() + "home");
@@ -113,20 +114,22 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti
// If here is no account in the database, launch the first connection wizard
bool firstConnection;
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
firstConnection = (Database::User::getAll(DboSession()).size() == 0);
firstConnection = (Database::User::getAll(LmsApp->getDboSession()).size() == 0);
}
LMS_LOG(UI, DEBUG) << "Creating root widget. First connection = " << std::boolalpha << firstConnection;
if (firstConnection)
{
root()->addWidget(new InitWizardView());
root()->addWidget(std::make_unique<InitWizardView>());
}
else
{
DbHandler().getLogin().changed().connect(std::bind([=]
_auth = root()->addNew<Auth>();
LmsApp->getDb().getLogin().changed().connect(std::bind([=]
{
try
{
@@ -138,68 +141,31 @@ LmsApplication::LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnecti
throw std::runtime_error("Internal error");
}
}));
_auth = new Auth();
root()->addWidget(_auth);
}
}
Database::Handler& DbHandler()
{
return LmsApplication::instance()->getDbHandler();
}
Wt::Dbo::Session& DboSession()
{
return DbHandler().getSession();
}
const Wt::Auth::User& CurrentAuthUser()
{
return DbHandler().getLogin().user();
}
Database::User::pointer CurrentUser()
{
return DbHandler().getCurrentUser();
}
ImageResource* SessionImageResource()
{
return LmsApplication::instance()->getImageResource();
}
TranscodeResource* SessionTranscodeResource()
{
return LmsApplication::instance()->getTranscodeResource();
}
Scanner::MediaScanner& MediaScanner()
{
return LmsApplication::instance()->getMediaScanner();
}
Wt::WAnchor*
std::unique_ptr<Wt::WAnchor>
LmsApplication::createArtistAnchor(Database::Artist::pointer artist, bool addText)
{
auto res = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/artist/" + std::to_string(artist.id())));
auto res = std::make_unique<Wt::WAnchor>(Wt::WLink(Wt::LinkType::InternalPath, "/artist/" + std::to_string(artist.id())));
if (addText)
{
res->setTextFormat(Wt::PlainText);
res->setTextFormat(Wt::TextFormat::Plain);
res->setText(Wt::WString::fromUTF8(artist->getName()));
}
return res;
}
Wt::WAnchor*
std::unique_ptr<Wt::WAnchor>
LmsApplication::createReleaseAnchor(Database::Release::pointer release, bool addText)
{
auto res = new Wt::WAnchor(Wt::WLink(Wt::WLink::InternalPath, "/release/" + std::to_string(release.id())));
auto res = std::make_unique<Wt::WAnchor>(Wt::WLink(Wt::LinkType::InternalPath, "/release/" + std::to_string(release.id())));
if (addText)
{
res->setTextFormat(Wt::PlainText);
res->setTextFormat(Wt::TextFormat::Plain);
res->setText(Wt::WString::fromUTF8(release->getName()));
}
@@ -274,7 +240,7 @@ handlePathChange(Wt::WStackedWidget* stack, bool isAdmin)
void
LmsApplication::handleAuthEvent(void)
{
if (!DbHandler().getLogin().loggedIn())
if (!LmsApp->getDb().getLogin().loggedIn())
{
LMS_LOG(UI, INFO) << "User logged out, session = " << Wt::WApplication::instance()->sessionId();
@@ -282,73 +248,69 @@ LmsApplication::handleAuthEvent(void)
return;
}
LMS_LOG(UI, INFO) << "User '" << CurrentAuthUser().identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().agent() << ", session = " << Wt::WApplication::instance()->sessionId();
LMS_LOG(UI, INFO) << "User '" << LmsApp->getCurrentAuthUser().identity(Wt::Auth::Identity::LoginName) << "' logged in from '" << Wt::WApplication::instance()->environment().clientAddress() << "', user agent = " << Wt::WApplication::instance()->environment().userAgent() << ", session = " << Wt::WApplication::instance()->sessionId();
{
Wt::Dbo::Transaction transaction (DboSession());
_isAdmin = CurrentUser()->isAdmin();
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
_isAdmin = LmsApp->getCurrentUser()->isAdmin();
}
_imageResource = new ImageResource(_db, root());
_transcodeResource = new TranscodeResource(_db, root());
_imageResource = std::make_shared<ImageResource>(_db);
_transcodeResource = std::make_shared<TranscodeResource>(_db);
setConfirmCloseMessage(Wt::WString::tr("Lms.quit-confirm"));
auto main = new Wt::WTemplate(Wt::WString::tr("Lms.template"), root());
Wt::WTemplate* main = root()->addWidget(std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.template")));
// Navbar
auto navbar = new Wt::WNavigationBar();
navbar->setTitle("LMS", Wt::WLink(Wt::WLink::InternalPath, "/home"));
Wt::WNavigationBar* navbar = main->bindNew<Wt::WNavigationBar>("navbar-top");
navbar->setTitle("LMS", Wt::WLink(Wt::LinkType::InternalPath, "/home"));
navbar->setResponsive(true);
main->bindWidget("navbar-top", navbar);
auto menu = new Wt::WMenu();
Wt::WMenu* menu = navbar->addMenu(std::make_unique<Wt::WMenu>());
{
auto menuItem = menu->insertItem(0, Wt::WString::tr("Lms.Explore.artists"));
menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/artists"));
menuItem->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/artists"));
menuItem->setSelectable(false);
}
{
auto menuItem = menu->insertItem(1, Wt::WString::tr("Lms.Explore.releases"));
menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/releases"));
menuItem->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/releases"));
menuItem->setSelectable(false);
}
{
auto menuItem = menu->insertItem(2, Wt::WString::tr("Lms.Explore.tracks"));
menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/tracks"));
menuItem->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/tracks"));
menuItem->setSelectable(false);
}
{
auto menuItem = menu->insertItem(3, Wt::WString::tr("Lms.PlayQueue.playqueue"));
menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/playqueue"));
menuItem->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/playqueue"));
menuItem->setSelectable(false);
}
navbar->addMenu(menu);
auto rightMenu = new Wt::WMenu();
Wt::WMenu* rightMenu = navbar->addMenu(std::make_unique<Wt::WMenu>(), Wt::AlignmentFlag::Right);
std::size_t itemCounter = 0;
if (_isAdmin)
{
auto menuItem = rightMenu->insertItem(itemCounter++, Wt::WString::tr("Lms.administration"));
menuItem->setSelectable(false);
Wt::WPopupMenu *admin = new Wt::WPopupMenu();
auto admin = std::make_unique<Wt::WPopupMenu>();
auto dbSettings = admin->insertItem(0, Wt::WString::tr("Lms.Admin.Database.database"));
dbSettings->setLink(Wt::WLink(Wt::WLink::InternalPath, "/admin/database"));
dbSettings->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/admin/database"));
dbSettings->setSelectable(false);
auto usersSettings = admin->insertItem(1, Wt::WString::tr("Lms.Admin.Users.users"));
usersSettings->setLink(Wt::WLink(Wt::WLink::InternalPath, "/admin/users"));
usersSettings->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/admin/users"));
usersSettings->setSelectable(false);
menuItem->setMenu(admin);
menuItem->setMenu(std::move(admin));
}
{
auto menuItem = rightMenu->insertItem(itemCounter++, Wt::WString::tr("Lms.Settings.settings"));
menuItem->setLink(Wt::WLink(Wt::WLink::InternalPath, "/settings"));
menuItem->setLink(Wt::WLink(Wt::LinkType::InternalPath, "/settings"));
menuItem->setSelectable(false);
}
{
@@ -360,34 +322,22 @@ LmsApplication::handleAuthEvent(void)
_auth->logout();
}));
}
navbar->addMenu(rightMenu, Wt::AlignRight);
// Contents
Wt::WStackedWidget* mainStack = new Wt::WStackedWidget();
main->bindWidget("contents", mainStack);
// Order is important in mainStack, see IdxRoot!
Wt::WStackedWidget* mainStack = main->bindNew<Wt::WStackedWidget>("contents");
mainStack->addWidget(new Home());
auto explore = new Explore();
mainStack->addWidget(explore);
auto playqueue = new PlayQueue();
mainStack->addWidget(playqueue);
auto settings = new SettingsView();
mainStack->addWidget(settings);
mainStack->addNew<Home>();
Explore* explore = mainStack->addNew<Explore>();
PlayQueue* playqueue = mainStack->addNew<PlayQueue>();
mainStack->addNew<SettingsView>();
// Admin stuff
if (_isAdmin)
{
auto databaseSettings = new DatabaseSettingsView();
mainStack->addWidget(databaseSettings);
auto users = new UsersView();
mainStack->addWidget(users);
auto user = new UserView();
mainStack->addWidget(user);
mainStack->addNew<DatabaseSettingsView>();
mainStack->addNew<UsersView>();
mainStack->addNew<UserView>();
}
explore->tracksAdd.connect(std::bind([=] (std::vector<Database::Track::pointer> tracks)
@@ -400,9 +350,9 @@ LmsApplication::handleAuthEvent(void)
playqueue->playTracks(tracks);
}, std::placeholders::_1));
// MediaPlayer
auto player = new MediaPlayer();
main->bindWidget("player", player);
MediaPlayer* player = main->bindNew<MediaPlayer>("player");
// Events from MediaPlayer
player->playNext.connect(std::bind([=]
@@ -468,7 +418,7 @@ void
LmsApplication::notifyMsg(const Wt::WString& message)
{
LMS_LOG(UI, INFO) << "Notifying message '" << message.toUTF8() << "'";
root()->addWidget(new Wt::WText(message));
root()->addNew<Wt::WText>(message);
}
} // namespace UserInterface
+18 -19
View File
@@ -20,8 +20,8 @@
#ifndef LMS_APPLICATION_HPP
#define LMS_APPLICATION_HPP
#include <Wt/WApplication>
#include <Wt/Dbo/SqlConnectionPool>
#include <Wt/WApplication.h>
#include <Wt/Dbo/SqlConnectionPool.h>
#include "database/DatabaseHandler.hpp"
#include "scanner/MediaScanner.hpp"
@@ -36,13 +36,20 @@ class ImageResource;
class LmsApplication : public Wt::WApplication
{
public:
static Wt::WApplication *create(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner);
LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner);
static std::unique_ptr<Wt::WApplication> create(const Wt::WEnvironment& env,
Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner);
static LmsApplication* instance();
// Session application data
ImageResource* getImageResource() { return _imageResource; }
TranscodeResource* getTranscodeResource() { return _transcodeResource; }
Database::Handler& getDbHandler() { return _db;}
std::shared_ptr<ImageResource> getImageResource() { return _imageResource; }
std::shared_ptr<TranscodeResource> getTranscodeResource() { return _transcodeResource; }
Database::Handler& getDb() { return _db;}
Wt::Dbo::Session& getDboSession() { return _db.getSession();}
const Wt::Auth::User& getCurrentAuthUser() { return _db.getLogin().user(); }
Database::User::pointer getCurrentUser() { return _db.getCurrentUser(); }
Scanner::MediaScanner& getMediaScanner() { return _scanner; }
@@ -51,33 +58,25 @@ class LmsApplication : public Wt::WApplication
void goHomeAndQuit();
void notifyMsg(const Wt::WString& message);
static Wt::WAnchor* createArtistAnchor(Database::Artist::pointer artist, bool addText = true);
static Wt::WAnchor* createReleaseAnchor(Database::Release::pointer release, bool addText = true);
static std::unique_ptr<Wt::WAnchor> createArtistAnchor(Database::Artist::pointer artist, bool addText = true);
static std::unique_ptr<Wt::WAnchor> createReleaseAnchor(Database::Release::pointer release, bool addText = true);
private:
LmsApplication(const Wt::WEnvironment& env, Wt::Dbo::SqlConnectionPool& connectionPool, Scanner::MediaScanner& scanner);
void handleAuthEvent(void);
void notify(const Wt::WEvent& event) override;
Database::Handler _db;
Auth* _auth;
Scanner::MediaScanner& _scanner;
ImageResource* _imageResource;
TranscodeResource* _transcodeResource;
std::shared_ptr<ImageResource> _imageResource;
std::shared_ptr<TranscodeResource> _transcodeResource;
bool _isAdmin = false;
};
// Helpers to get session data
// Helper to get session data
#define LmsApp LmsApplication::instance()
Database::Handler& DbHandler();
Wt::Dbo::Session& DboSession();
const Wt::Auth::User& CurrentAuthUser();
Database::User::pointer CurrentUser();
} // namespace UserInterface
#endif
+5 -5
View File
@@ -30,8 +30,8 @@
namespace UserInterface {
MediaPlayer::MediaPlayer(Wt::WContainerWidget* parent)
: Wt::WTemplate(Wt::WString::tr("template-mediaplayer"), parent),
MediaPlayer::MediaPlayer()
: Wt::WTemplate(Wt::WString::tr("template-mediaplayer")),
playbackEnded(this, "playbackEnded"),
playPrevious(this, "playPrevious"),
playNext(this, "playNext")
@@ -49,8 +49,8 @@ MediaPlayer::playTrack(Database::Track::id_type trackId)
{
LMS_LOG(UI, DEBUG) << "Playing track ID = " << trackId;
Wt::Dbo::Transaction transaction(DboSession());
auto track = Database::Track::getById(DboSession(), trackId);
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto track = Database::Track::getById(LmsApp->getDboSession(), trackId);
try
{
@@ -66,7 +66,7 @@ MediaPlayer::playTrack(Database::Track::id_type trackId)
<< " release: " << (track->getRelease() ? "\"" + escape(track->getRelease()->getName()) + "\"" : "undefined" ) << ","
<< " artist: " << (track->getArtist() ? "\"" + escape(track->getArtist()->getName()) + "\"" : "undefined" ) << ","
<< " resource: \"" << resource << "\","
<< " duration: " << track->getDuration().total_seconds() << ","
<< " duration: " << std::chrono::duration_cast<std::chrono::seconds>(track->getDuration()).count() << ","
<< " imgResource: \"" << imgResource << "\","
<< "};";
oss << "LMS.mediaplayer.loadTrack(params, true)"; // true to autoplay
+6 -6
View File
@@ -19,8 +19,8 @@
#pragma once
#include <Wt/WJavaScript>
#include <Wt/WTemplate>
#include <Wt/WJavaScript.h>
#include <Wt/WTemplate.h>
#include "database/Types.hpp"
@@ -29,15 +29,15 @@ namespace UserInterface {
class MediaPlayer : public Wt::WTemplate
{
public:
MediaPlayer(Wt::WContainerWidget* parent = 0);
MediaPlayer();
void stop();
void playTrack(Database::Track::id_type);
// Signals
Wt::JSignal<void> playbackEnded;
Wt::JSignal<void> playPrevious;
Wt::JSignal<void> playNext;
Wt::JSignal<> playbackEnded;
Wt::JSignal<> playPrevious;
Wt::JSignal<> playNext;
private:
+47 -58
View File
@@ -17,9 +17,9 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WAnchor>
#include <Wt/WImage>
#include <Wt/WText>
#include <Wt/WAnchor.h>
#include <Wt/WImage.h>
#include <Wt/WText.h>
#include "utils/Logger.hpp"
@@ -30,26 +30,39 @@ namespace UserInterface {
static const std::string currentPlayQueueName = "__current__playqueue__";
PlayQueue::PlayQueue(Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent)
PlayQueue::PlayQueue()
: Wt::WTemplate(Wt::WString::tr("Lms.PlayQueue.template"))
{
auto container = new Wt::WTemplate(Wt::WString::tr("Lms.PlayQueue.template"), this);
container->addFunction("tr", &Wt::WTemplate::Functions::tr);
addFunction("tr", &Wt::WTemplate::Functions::tr);
auto saveBtn = new Wt::WText(Wt::WString::tr("Lms.PlayQueue.save-to-playlist"), Wt::XHTMLText);
container->bindWidget("save-btn", saveBtn);
bindNew<Wt::WText>("save-btn", Wt::WString::tr("Lms.PlayQueue.save-to-playlist"), Wt::TextFormat::XHTML);
bindNew<Wt::WText>("load-btn", Wt::WString::tr("Lms.PlayQueue.load-from-playlist"), Wt::TextFormat::XHTML);
Wt::WText* clearBtn = bindNew<Wt::WText>("clear-btn", Wt::WString::tr("Lms.PlayQueue.clear"), Wt::TextFormat::XHTML);
auto loadBtn = new Wt::WText(Wt::WString::tr("Lms.PlayQueue.load-from-playlist"), Wt::XHTMLText);
container->bindWidget("load-btn", loadBtn);
_entriesContainer = bindNew<Wt::WContainerWidget>("entries");
_showMore = bindNew<Wt::WTemplate>("show-more", Wt::WString::tr("Lms.Explore.show-more"));
_showMore->addFunction("tr", &Wt::WTemplate::Functions::tr);
_showMore->setHidden(true);
_nbTracks = bindNew<Wt::WText>("nb-tracks");
{
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
auto playlist = Database::Playlist::get(LmsApp->getDboSession(), currentPlayQueueName, LmsApp->getCurrentUser());
if (!playlist)
playlist = Database::Playlist::create(LmsApp->getDboSession(), currentPlayQueueName, false, LmsApp->getCurrentUser());
_trackPos = LmsApp->getCurrentUser()->getCurPlayingTrackPos();
}
auto clearBtn = new Wt::WText(Wt::WString::tr("Lms.PlayQueue.clear"), Wt::XHTMLText);
container->bindWidget("clear-btn", clearBtn);
clearBtn->clicked().connect(std::bind([=]
{
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser());
auto playlist = Database::Playlist::get(LmsApp->getDboSession(), currentPlayQueueName, LmsApp->getCurrentUser());
playlist.modify()->clear();
_showMore->setHidden(true);
}
@@ -58,27 +71,6 @@ PlayQueue::PlayQueue(Wt::WContainerWidget* parent)
updateInfo();
}));
_entriesContainer = new Wt::WContainerWidget();
container->bindWidget("entries", _entriesContainer);
_showMore = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.show-more"));
_showMore->addFunction("tr", &Wt::WTemplate::Functions::tr);
_showMore->setHidden(true);
container->bindWidget("show-more", _showMore);
_nbTracks = new Wt::WText();
container->bindWidget("nb-tracks", _nbTracks);
{
Wt::Dbo::Transaction transaction (DboSession());
auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser());
if (!playlist)
playlist = Database::Playlist::create(DboSession(), currentPlayQueueName, false, CurrentUser());
_trackPos = CurrentUser()->getCurPlayingTrackPos();
}
_showMore->clicked().connect(std::bind([=]
{
addSome();
@@ -103,9 +95,9 @@ PlayQueue::play(std::size_t pos)
Database::Track::id_type trackId;
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser());
auto playlist = Database::Playlist::get(LmsApp->getDboSession(), currentPlayQueueName, LmsApp->getCurrentUser());
// If out of range, stop playing
if (pos >= playlist->getCount())
@@ -149,9 +141,9 @@ PlayQueue::playNext()
void
PlayQueue::updateInfo()
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser());
auto playlist = Database::Playlist::get(LmsApp->getDboSession(), currentPlayQueueName, LmsApp->getCurrentUser());
_nbTracks->setText(Wt::WString::tr("Lms.PlayQueue.nb-tracks").arg(playlist->getCount()));
}
@@ -179,10 +171,10 @@ PlayQueue::addTracks(const std::vector<Database::Track::pointer>& tracks)
LMS_LOG(UI, DEBUG) << "Adding tracks to the current queue";
auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser());
auto playlist = Database::Playlist::get(LmsApp->getDboSession(), currentPlayQueueName, LmsApp->getCurrentUser());
for (auto track : tracks)
Database::PlaylistEntry::create(DboSession(), track, playlist);
Database::PlaylistEntry::create(LmsApp->getDboSession(), track, playlist);
updateInfo();
addSome();
@@ -193,9 +185,9 @@ PlayQueue::playTracks(const std::vector<Database::Track::pointer>& tracks)
{
LMS_LOG(UI, DEBUG) << "Emptying current queue to play new tracks";
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto playqueue = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser());
auto playqueue = Database::Playlist::get(LmsApp->getDboSession(), currentPlayQueueName, LmsApp->getCurrentUser());
playqueue.modify()->clear();
_entriesContainer->clear();
@@ -209,9 +201,9 @@ PlayQueue::playTracks(const std::vector<Database::Track::pointer>& tracks)
void
PlayQueue::addSome()
{
Wt::Dbo::Transaction transaction (DboSession());
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
auto playlist = Database::Playlist::get(DboSession(), currentPlayQueueName, CurrentUser());
auto playlist = Database::Playlist::get(LmsApp->getDboSession(), currentPlayQueueName, LmsApp->getCurrentUser());
bool moreResults;
auto playlistEntries = playlist->getEntries(_entriesContainer->count(), 50, moreResults);
@@ -220,27 +212,24 @@ PlayQueue::addSome()
auto playlistEntryId = playlistEntry.id();
auto track = playlistEntry->getTrack();
Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("Lms.PlayQueue.template.entry"), _entriesContainer);
Wt::WTemplate* entry = _entriesContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.PlayQueue.template.entry"));
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText);
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
auto artist = track->getArtist();
if (artist)
{
entry->setCondition("if-has-artist", true);
Wt::WAnchor *artistAnchor = LmsApplication::createArtistAnchor(track->getArtist());
entry->bindWidget("artist-name", artistAnchor);
entry->bindWidget("artist-name", LmsApplication::createArtistAnchor(track->getArtist()));
}
auto release = track->getRelease();
if (release)
{
entry->setCondition("if-has-release", true);
Wt::WAnchor *releaseAnchor = LmsApplication::createReleaseAnchor(track->getRelease());
entry->bindWidget("release-name", releaseAnchor);
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(track->getRelease()));
}
auto playBtn = new Wt::WText(Wt::WString::tr("Lms.PlayQueue.play"), Wt::XHTMLText);
entry->bindWidget("play-btn", playBtn);
Wt::WText* playBtn = entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.PlayQueue.play"), Wt::TextFormat::XHTML);
playBtn->clicked().connect(std::bind([=]
{
auto pos = _entriesContainer->indexOf(entry);
@@ -248,15 +237,14 @@ PlayQueue::addSome()
play(pos);
}));
auto delBtn = new Wt::WText(Wt::WString::tr("Lms.PlayQueue.delete"), Wt::XHTMLText);
entry->bindWidget("del-btn", delBtn);
Wt::WText* delBtn = entry->bindNew<Wt::WText>("del-btn", Wt::WString::tr("Lms.PlayQueue.delete"), Wt::TextFormat::XHTML);
delBtn->clicked().connect(std::bind([=]
{
// Remove the entry n both the widget tree and the playqueue
{
Wt::Dbo::Transaction transaction (DboSession());
Wt::Dbo::Transaction transaction (LmsApp->getDboSession());
auto entryToRemove = Database::PlaylistEntry::getById(DboSession(), playlistEntryId);
auto entryToRemove = Database::PlaylistEntry::getById(LmsApp->getDboSession(), playlistEntryId);
entryToRemove.remove();
}
@@ -271,6 +259,7 @@ PlayQueue::addSome()
updateInfo();
}));
}
_showMore->setHidden(!moreResults);
+7 -7
View File
@@ -19,10 +19,10 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
#include <Wt/WTemplate>
#include <Wt/WText>
#include <Wt/WContainerWidget.h>
#include <Wt/WSignal.h>
#include <Wt/WTemplate.h>
#include <Wt/WText.h>
#include <boost/optional.hpp>
@@ -30,10 +30,10 @@
namespace UserInterface {
class PlayQueue : public Wt::WContainerWidget
class PlayQueue : public Wt::WTemplate
{
public:
PlayQueue(Wt::WContainerWidget* parent = 0);
PlayQueue();
void addTracks(const std::vector<Database::Track::pointer>& tracks);
void playTracks(const std::vector<Database::Track::pointer>& tracks);
@@ -48,7 +48,7 @@ class PlayQueue : public Wt::WContainerWidget
Wt::Signal<Database::Track::id_type> playTrack;
// Signal emitted when play has to be stopped
Wt::Signal<void> playbackStop;
Wt::Signal<> playbackStop;
private:
void addSome();
+61 -68
View File
@@ -17,13 +17,13 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WString>
#include <Wt/WPushButton>
#include <Wt/WComboBox>
#include <Wt/WLineEdit>
#include <Wt/WString.h>
#include <Wt/WPushButton.h>
#include <Wt/WComboBox.h>
#include <Wt/WLineEdit.h>
#include <Wt/WFormModel>
#include <Wt/WStringListModel>
#include <Wt/WFormModel.h>
#include <Wt/WStringListModel.h>
#include "common/Validators.hpp"
@@ -45,8 +45,8 @@ class SettingsModel : public Wt::WFormModel
static const Field PasswordField;
static const Field PasswordConfirmField;
SettingsModel(Wt::WObject *parent = 0)
: Wt::WFormModel(parent)
SettingsModel()
: Wt::WFormModel()
{
initializeModels();
@@ -61,18 +61,18 @@ class SettingsModel : public Wt::WFormModel
loadData();
}
Wt::WAbstractItemModel *bitrateModel() { return _bitrateModel; }
Wt::WAbstractItemModel *encodingModel() { return _encodingModel; }
std::shared_ptr<Wt::WAbstractItemModel> bitrateModel() { return _bitrateModel; }
std::shared_ptr<Wt::WAbstractItemModel> encodingModel() { return _encodingModel; }
void loadData()
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto bitrate = getBitrateRow(CurrentUser()->getAudioBitrate());
auto bitrate = getBitrateRow(LmsApp->getCurrentUser()->getAudioBitrate());
if (bitrate)
setValue(BitrateField, bitrateString(*bitrate));
auto encodingRow = getEncodingRow(CurrentUser()->getAudioEncoding());
auto encodingRow = getEncodingRow(LmsApp->getCurrentUser()->getAudioEncoding());
if (encodingRow)
setValue(EncodingField, encodingString(*encodingRow));
@@ -80,18 +80,18 @@ class SettingsModel : public Wt::WFormModel
void saveData()
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto bitrateRow = getBitrateRow(Wt::asString(value(BitrateField)));
assert(bitrateRow);
CurrentUser().modify()->setAudioBitrate(bitrate(*bitrateRow));
LmsApp->getCurrentUser().modify()->setAudioBitrate(bitrate(*bitrateRow));
auto encodingRow = getEncodingRow(Wt::asString(value(EncodingField)));
CurrentUser().modify()->setAudioEncoding(encoding(*encodingRow));
LmsApp->getCurrentUser().modify()->setAudioEncoding(encoding(*encodingRow));
if (!valueText(PasswordField).empty())
{
Database::Handler::getPasswordService().updatePassword(CurrentAuthUser(), valueText(PasswordField));
Database::Handler::getPasswordService().updatePassword(LmsApp->getCurrentAuthUser(), valueText(PasswordField));
}
}
@@ -105,7 +105,7 @@ class SettingsModel : public Wt::WFormModel
{
// Evaluate the strength of the password
auto res = Database::Handler::getPasswordService().strengthValidator()->evaluateStrength(valueText(PasswordField),
CurrentAuthUser().identity(Wt::Auth::Identity::LoginName), "");
LmsApp->getCurrentAuthUser().identity(Wt::Auth::Identity::LoginName), "");
if (!res.isValid())
error = res.message();
@@ -115,7 +115,7 @@ class SettingsModel : public Wt::WFormModel
}
else if (field == PasswordConfirmField)
{
if (validation(PasswordField).state() == Wt::WValidator::Valid)
if (validation(PasswordField).state() == Wt::ValidationState::Valid)
{
if (valueText(PasswordField) != valueText(PasswordConfirmField))
error = Wt::WString::tr("Lms.passwords-dont-match");
@@ -126,9 +126,9 @@ class SettingsModel : public Wt::WFormModel
return Wt::WFormModel::validateField(field);
}
setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error));
setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::ValidationState::Valid : Wt::ValidationState::Invalid, error));
return (validation(field).state() == Wt::WValidator::Valid);
return (validation(field).state() == Wt::ValidationState::Valid);
}
boost::optional<int> getBitrateRow(Wt::WString value)
@@ -155,14 +155,14 @@ class SettingsModel : public Wt::WFormModel
std::size_t bitrate(int row)
{
return boost::any_cast<std::size_t>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::UserRole));
return Wt::cpp17::any_cast<std::size_t>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::ItemDataRole::User));
}
Wt::WString bitrateString(int row)
{
return boost::any_cast<Wt::WString>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::DisplayRole));
return Wt::cpp17::any_cast<Wt::WString>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::ItemDataRole::Display));
}
boost::optional<int> getEncodingRow(Wt::WString value)
@@ -189,14 +189,14 @@ class SettingsModel : public Wt::WFormModel
Database::AudioEncoding encoding(int row)
{
return boost::any_cast<Database::AudioEncoding>
(_encodingModel->data(_encodingModel->index(row, 0), Wt::UserRole));
return Wt::cpp17::any_cast<Database::AudioEncoding>
(_encodingModel->data(_encodingModel->index(row, 0), Wt::ItemDataRole::User));
}
Wt::WString encodingString(int row)
{
return boost::any_cast<Wt::WString>
(_encodingModel->data(_encodingModel->index(row, 0), Wt::DisplayRole));
return Wt::cpp17::any_cast<Wt::WString>
(_encodingModel->data(_encodingModel->index(row, 0), Wt::ItemDataRole::Display));
}
@@ -204,42 +204,42 @@ class SettingsModel : public Wt::WFormModel
void initializeModels()
{
_bitrateModel = new Wt::WStringListModel(this);
_bitrateModel = std::make_shared<Wt::WStringListModel>();
std::size_t maxAudioBitrate;
{
Wt::Dbo::Transaction transaction(DboSession());
maxAudioBitrate = CurrentUser()->getMaxAudioBitrate();
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
maxAudioBitrate = LmsApp->getCurrentUser()->getMaxAudioBitrate();
}
std::size_t id = 0;
for (auto bitrate : Database::User::audioBitrates)
for (std::size_t bitrate : Database::User::audioBitrates)
{
if (bitrate > maxAudioBitrate)
break;
_bitrateModel->addString( Wt::WString::fromUTF8(std::to_string(bitrate / 1000)) );
_bitrateModel->setData( id++, 0, bitrate, Wt::UserRole);
_bitrateModel->setData( id, 0, bitrate, Wt::ItemDataRole::User);
id++;
}
_encodingModel = new Wt::WStringListModel(this);
_encodingModel = std::make_shared<Wt::WStringListModel>();
_encodingModel->addString(Wt::WString::tr("Lms.Settings.auto"));
_encodingModel->setData(0, 0, Database::AudioEncoding::AUTO, Wt::UserRole);
_encodingModel->setData(0, 0, Database::AudioEncoding::AUTO, Wt::ItemDataRole::User);
_encodingModel->addString(Wt::WString::tr("Lms.Settings.mp3"));
_encodingModel->setData(1, 0, Database::AudioEncoding::MP3, Wt::UserRole);
_encodingModel->setData(1, 0, Database::AudioEncoding::MP3, Wt::ItemDataRole::User);
_encodingModel->addString(Wt::WString::tr("Lms.Settings.oga"));
_encodingModel->setData(2, 0, Database::AudioEncoding::OGA, Wt::UserRole);
_encodingModel->setData(2, 0, Database::AudioEncoding::OGA, Wt::ItemDataRole::User);
_encodingModel->addString(Wt::WString::tr("Lms.Settings.webma"));
_encodingModel->setData(3, 0, Database::AudioEncoding::WEBMA, Wt::UserRole);
_encodingModel->setData(3, 0, Database::AudioEncoding::WEBMA, Wt::ItemDataRole::User);
}
Wt::WStringListModel* _bitrateModel;
Wt::WStringListModel* _encodingModel;
std::shared_ptr<Wt::WStringListModel> _bitrateModel;
std::shared_ptr<Wt::WStringListModel> _encodingModel;
};
@@ -248,45 +248,38 @@ const Wt::WFormModel::Field SettingsModel::EncodingField = "encoding";
const Wt::WFormModel::Field SettingsModel::PasswordField = "password";
const Wt::WFormModel::Field SettingsModel::PasswordConfirmField = "password-confirm";
SettingsView::SettingsView(Wt::WContainerWidget *parent)
: Wt::WTemplateFormView(parent)
SettingsView::SettingsView()
: Wt::WTemplateFormView(Wt::WString::tr("Lms.Settings.template"))
{
auto model = new SettingsModel(this);
setTemplateText(tr("Lms.Settings.template"));
addFunction("tr", &WTemplate::Functions::tr);
addFunction("id", &WTemplate::Functions::id);
auto model = std::make_shared<SettingsModel>();
// Password
Wt::WLineEdit *password = new Wt::WLineEdit();
setFormWidget(SettingsModel::PasswordField, password);
password->setEchoMode(Wt::WLineEdit::Password);
auto password = std::make_unique<Wt::WLineEdit>();
password->setEchoMode(Wt::EchoMode::Password);
setFormWidget(SettingsModel::PasswordField, std::move(password));
// Password confirm
Wt::WLineEdit *passwordConfirm = new Wt::WLineEdit();
setFormWidget(SettingsModel::PasswordConfirmField, passwordConfirm);
passwordConfirm->setEchoMode(Wt::WLineEdit::Password);
auto passwordConfirm = std::make_unique<Wt::WLineEdit>();
passwordConfirm->setEchoMode(Wt::EchoMode::Password);
setFormWidget(SettingsModel::PasswordConfirmField, std::move(passwordConfirm));
// Bitrate
Wt::WComboBox *bitrate = new Wt::WComboBox();
setFormWidget(SettingsModel::BitrateField, bitrate);
auto bitrate = std::make_unique<Wt::WComboBox>();
bitrate->setModel(model->bitrateModel());
setFormWidget(SettingsModel::BitrateField, std::move(bitrate));
// Encoding
Wt::WComboBox *encoding = new Wt::WComboBox();
setFormWidget(SettingsModel::EncodingField, encoding);
auto encoding = std::make_unique<Wt::WComboBox>();
encoding->setModel(model->encodingModel());
setFormWidget(SettingsModel::EncodingField, std::move(encoding));
// Buttons
Wt::WPushButton *saveBtn = new Wt::WPushButton(Wt::WString::tr("Lms.apply"));
bindWidget("apply-btn", saveBtn);
Wt::WPushButton *discardBtn = new Wt::WPushButton(Wt::WString::tr("Lms.discard"));
bindWidget("discard-btn", discardBtn);
Wt::WPushButton *saveBtn = bindWidget("apply-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.apply")));
Wt::WPushButton *discardBtn = bindWidget("discard-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.discard")));
saveBtn->clicked().connect(std::bind([=] ()
{
updateModel(model);
updateModel(model.get());
if (model->validate())
{
@@ -295,17 +288,17 @@ SettingsView::SettingsView(Wt::WContainerWidget *parent)
}
// Udate the view: Delete any validation message in the view, etc.
updateView(model);
updateView(model.get());
}));
discardBtn->clicked().connect(std::bind([=] ()
{
model->loadData();
model->validate();
updateView(model);
updateView(model.get());
}));
updateView(model);
updateView(model.get());
}
} // namespace UserInterface
+2 -3
View File
@@ -19,15 +19,14 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WTemplateFormView>
#include <Wt/WTemplateFormView.h>
namespace UserInterface {
class SettingsView : public Wt::WTemplateFormView
{
public:
SettingsView(Wt::WContainerWidget *parent = 0);
SettingsView();
};
} // namespace UserInterface
+60 -78
View File
@@ -17,13 +17,13 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WString>
#include <Wt/WPushButton>
#include <Wt/WComboBox>
#include <Wt/WLineEdit>
#include <Wt/WString.h>
#include <Wt/WPushButton.h>
#include <Wt/WComboBox.h>
#include <Wt/WLineEdit.h>
#include <Wt/WFormModel>
#include <Wt/WStringListModel>
#include <Wt/WFormModel.h>
#include <Wt/WStringListModel.h>
#include "common/Validators.hpp"
#include "database/MediaDirectory.hpp"
@@ -46,8 +46,8 @@ class DatabaseSettingsModel : public Wt::WFormModel
static const Field UpdatePeriodField;
static const Field UpdateStartTimeField;
DatabaseSettingsModel(Wt::WObject *parent = 0)
: Wt::WFormModel(parent)
DatabaseSettingsModel()
: Wt::WFormModel()
{
initializeModels();
@@ -55,7 +55,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
addField(UpdatePeriodField);
addField(UpdateStartTimeField);
DirectoryValidator* dirValidator = new DirectoryValidator();
auto dirValidator = std::make_shared<DirectoryValidator>();
dirValidator->setMandatory(true);
setValidator(MediaDirectoryField, dirValidator);
@@ -66,42 +66,42 @@ class DatabaseSettingsModel : public Wt::WFormModel
loadData();
}
Wt::WAbstractItemModel *updatePeriodModel() { return _updatePeriodModel; }
Wt::WAbstractItemModel *updateStartTimeModel() { return _updateStartTimeModel; }
std::shared_ptr<Wt::WAbstractItemModel> updatePeriodModel() { return _updatePeriodModel; }
std::shared_ptr<Wt::WAbstractItemModel> updateStartTimeModel() { return _updateStartTimeModel; }
void loadData()
{
using namespace Database;
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
std::vector<MediaDirectory::pointer> mediaDirectories = MediaDirectory::getAll(DboSession());
std::vector<MediaDirectory::pointer> mediaDirectories = MediaDirectory::getAll(LmsApp->getDboSession());
if (!mediaDirectories.empty())
setValue(MediaDirectoryField, mediaDirectories.front()->getPath().string());
auto periodRow = getUpdatePeriodModelRow( Scanner::getUpdatePeriod(DboSession()) );
auto periodRow = getUpdatePeriodModelRow( Scanner::getUpdatePeriod(LmsApp->getDboSession()) );
if (periodRow)
setValue(UpdatePeriodField, updatePeriodString(*periodRow));
auto startTimeRow = getUpdateStartTimeModelRow( Scanner::getUpdateStartTime(DboSession()) );
auto startTimeRow = getUpdateStartTimeModelRow( Scanner::getUpdateStartTime(LmsApp->getDboSession()) );
if (startTimeRow)
setValue(UpdateStartTimeField, updateStartTimeString(*startTimeRow) );
}
void saveData()
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
MediaDirectory::eraseAll(DboSession());
MediaDirectory::create(DboSession(), boost::any_cast<Wt::WString>(value(MediaDirectoryField)).toUTF8());
MediaDirectory::eraseAll(LmsApp->getDboSession());
MediaDirectory::create(LmsApp->getDboSession(), Wt::cpp17::any_cast<Wt::WString>(value(MediaDirectoryField)).toUTF8());
auto updatePeriodRow = getUpdatePeriodModelRow( boost::any_cast<Wt::WString>(value(UpdatePeriodField)));
auto updatePeriodRow = getUpdatePeriodModelRow( Wt::cpp17::any_cast<Wt::WString>(value(UpdatePeriodField)));
assert(updatePeriodRow);
Scanner::setUpdatePeriod(DboSession(), updatePeriod(*updatePeriodRow));
Scanner::setUpdatePeriod(LmsApp->getDboSession(), updatePeriod(*updatePeriodRow));
auto startTimeRow = getUpdateStartTimeModelRow( boost::any_cast<Wt::WString>(value(UpdateStartTimeField)));
auto startTimeRow = getUpdateStartTimeModelRow( Wt::cpp17::any_cast<Wt::WString>(value(UpdateStartTimeField)));
assert(startTimeRow);
Scanner::setUpdateStartTime(DboSession(), updateStartTime(*startTimeRow));
Scanner::setUpdateStartTime(LmsApp->getDboSession(), updateStartTime(*startTimeRow));
}
boost::optional<int> getUpdatePeriodModelRow(Wt::WString value)
@@ -128,14 +128,14 @@ class DatabaseSettingsModel : public Wt::WFormModel
Scanner::UpdatePeriod updatePeriod(int row)
{
return boost::any_cast<Scanner::UpdatePeriod>
(_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::UserRole));
return Wt::cpp17::any_cast<Scanner::UpdatePeriod>
(_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::ItemDataRole::User));
}
Wt::WString updatePeriodString(int row)
{
return boost::any_cast<Wt::WString>
(_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::DisplayRole));
return Wt::cpp17::any_cast<Wt::WString>
(_updatePeriodModel->data(_updatePeriodModel->index(row, 0), Wt::ItemDataRole::Display));
}
@@ -150,7 +150,7 @@ class DatabaseSettingsModel : public Wt::WFormModel
return boost::none;
}
boost::optional<int> getUpdateStartTimeModelRow(boost::posix_time::time_duration startTime)
boost::optional<int> getUpdateStartTimeModelRow(Wt::WTime startTime)
{
for (int i = 0; i < _updateStartTimeModel->rowCount(); ++i)
{
@@ -161,16 +161,16 @@ class DatabaseSettingsModel : public Wt::WFormModel
return boost::none;
}
boost::posix_time::time_duration updateStartTime(int row)
Wt::WTime updateStartTime(int row)
{
return boost::any_cast<boost::posix_time::time_duration>
(_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::UserRole));
return Wt::cpp17::any_cast<Wt::WTime>
(_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::ItemDataRole::User));
}
Wt::WString updateStartTimeString(int row)
{
return boost::any_cast<Wt::WString>
(_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::DisplayRole));
return Wt::cpp17::any_cast<Wt::WString>
(_updateStartTimeModel->data(_updateStartTimeModel->index(row, 0), Wt::ItemDataRole::Display));
}
@@ -179,43 +179,35 @@ class DatabaseSettingsModel : public Wt::WFormModel
void initializeModels()
{
_updatePeriodModel = new Wt::WStringListModel(this);
_updatePeriodModel = std::make_shared<Wt::WStringListModel>();
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.never"));
_updatePeriodModel->setData(0, 0, Scanner::UpdatePeriod::Never, Wt::UserRole);
_updatePeriodModel->setData(0, 0, Scanner::UpdatePeriod::Never, Wt::ItemDataRole::User);
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.daily"));
_updatePeriodModel->setData(1, 0, Scanner::UpdatePeriod::Daily, Wt::UserRole);
_updatePeriodModel->setData(1, 0, Scanner::UpdatePeriod::Daily, Wt::ItemDataRole::User);
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.weekly"));
_updatePeriodModel->setData(2, 0, Scanner::UpdatePeriod::Weekly, Wt::UserRole);
_updatePeriodModel->setData(2, 0, Scanner::UpdatePeriod::Weekly, Wt::ItemDataRole::User);
_updatePeriodModel->addString(Wt::WString::tr("Lms.Admin.Database.monthly"));
_updatePeriodModel->setData(3, 0, Scanner::UpdatePeriod::Monthly, Wt::UserRole);
_updatePeriodModel->setData(3, 0, Scanner::UpdatePeriod::Monthly, Wt::ItemDataRole::User);
_updateStartTimeModel = new Wt::WStringListModel(this);
_updateStartTimeModel = std::make_shared<Wt::WStringListModel>();
for (std::size_t i = 0; i < 24; ++i)
{
boost::posix_time::time_duration dur = boost::posix_time::hours(i);
Wt::WTime time(i, 0);
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);
_updateStartTimeModel->addString( time.toString() );
_updateStartTimeModel->setData(i, 0, time, Wt::ItemDataRole::User);
}
}
Wt::WStringListModel* _updatePeriodModel;
Wt::WStringListModel* _updateStartTimeModel;
std::shared_ptr<Wt::WStringListModel> _updatePeriodModel;
std::shared_ptr<Wt::WStringListModel> _updateStartTimeModel;
};
@@ -224,43 +216,33 @@ const Wt::WFormModel::Field DatabaseSettingsModel::UpdatePeriodField = "update-
const Wt::WFormModel::Field DatabaseSettingsModel::UpdateStartTimeField = "update-start-time";
DatabaseSettingsView::DatabaseSettingsView(Wt::WContainerWidget *parent)
: Wt::WTemplateFormView(parent)
DatabaseSettingsView::DatabaseSettingsView()
: Wt::WTemplateFormView(Wt::WString::tr("Lms.Admin.Database.template"))
{
auto model = new DatabaseSettingsModel(this);
setTemplateText(tr("Lms.Admin.Database.template"));
addFunction("tr", &WTemplate::Functions::tr);
addFunction("id", &WTemplate::Functions::id);
auto model = std::make_shared<DatabaseSettingsModel>();
// Media Directory
Wt::WLineEdit *mediaDirectoryEdit = new Wt::WLineEdit();
setFormWidget(DatabaseSettingsModel::MediaDirectoryField, mediaDirectoryEdit);
setFormWidget(DatabaseSettingsModel::MediaDirectoryField, std::make_unique<Wt::WLineEdit>());
// Update Period
Wt::WComboBox *updatePeriodCB = new Wt::WComboBox();
setFormWidget(DatabaseSettingsModel::UpdatePeriodField, updatePeriodCB);
updatePeriodCB->setModel(model->updatePeriodModel());
auto updatePeriod = std::make_unique<Wt::WComboBox>();
updatePeriod->setModel(model->updatePeriodModel());
setFormWidget(DatabaseSettingsModel::UpdatePeriodField, std::move(updatePeriod));
// Update Start Time
Wt::WComboBox *updateStartTimeCB = new Wt::WComboBox();
setFormWidget(DatabaseSettingsModel::UpdateStartTimeField, updateStartTimeCB);
updateStartTimeCB->setModel(model->updateStartTimeModel());
auto updateStartTime = std::make_unique<Wt::WComboBox>();
updateStartTime->setModel(model->updateStartTimeModel());
setFormWidget(DatabaseSettingsModel::UpdateStartTimeField, std::move(updateStartTime));
// Buttons
Wt::WPushButton *saveBtn = new Wt::WPushButton(Wt::WString::tr("Lms.apply"));
bindWidget("apply-btn", saveBtn);
Wt::WPushButton *discardBtn = new Wt::WPushButton(Wt::WString::tr("Lms.discard"));
bindWidget("discard-btn", discardBtn);
Wt::WPushButton *immScanBtn = new Wt::WPushButton(Wt::WString::tr("Lms.Admin.Database.immediate-scan"));
bindWidget("immediate-scan-btn", immScanBtn);
Wt::WPushButton *saveBtn = bindWidget("apply-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.apply")));
Wt::WPushButton *discardBtn = bindWidget("discard-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.discard")));
Wt::WPushButton *immScanBtn = bindWidget("immediate-scan-btn", std::make_unique<Wt::WPushButton>(Wt::WString::tr("Lms.Admin.Database.immediate-scan")));
saveBtn->clicked().connect(std::bind([=] ()
{
updateModel(model);
updateModel(model.get());
if (model->validate())
{
@@ -271,14 +253,14 @@ DatabaseSettingsView::DatabaseSettingsView(Wt::WContainerWidget *parent)
}
// Udate the view: Delete any validation message in the view, etc.
updateView(model);
updateView(model.get());
}));
discardBtn->clicked().connect(std::bind([=] ()
{
model->loadData();
model->validate();
updateView(model);
updateView(model.get());
}));
immScanBtn->clicked().connect(std::bind([=] ()
@@ -287,7 +269,7 @@ DatabaseSettingsView::DatabaseSettingsView(Wt::WContainerWidget *parent)
LmsApp->notifyMsg(Wt::WString::tr("Lms.Admin.Database.scan-launched"));
}));
updateView(model);
updateView(model.get());
}
} // namespace UserInterface
+2 -3
View File
@@ -19,15 +19,14 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WTemplateFormView>
#include <Wt/WTemplateFormView.h>
namespace UserInterface {
class DatabaseSettingsView : public Wt::WTemplateFormView
{
public:
DatabaseSettingsView(Wt::WContainerWidget *parent = 0);
DatabaseSettingsView();
};
+26 -33
View File
@@ -17,10 +17,10 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WFormModel>
#include <Wt/WLineEdit>
#include <Wt/WPushButton>
#include <Wt/Auth/Identity>
#include <Wt/WFormModel.h>
#include <Wt/WLineEdit.h>
#include <Wt/WPushButton.h>
#include <Wt/Auth/Identity.h>
#include "utils/Logger.hpp"
@@ -40,8 +40,7 @@ class InitWizardModel : public Wt::WFormModel
static const Field PasswordField;
static const Field PasswordConfirmField;
InitWizardModel(Wt::WObject *parent = 0)
: Wt::WFormModel(parent)
InitWizardModel() : Wt::WFormModel()
{
addField(AdminLoginField);
addField(PasswordField);
@@ -54,16 +53,16 @@ class InitWizardModel : public Wt::WFormModel
void saveData()
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
// Check if a user already exist
// If it's the case, just do nothing
if (!Database::User::getAll(DboSession()).empty())
if (!Database::User::getAll(LmsApp->getDboSession()).empty())
throw std::runtime_error("Admin user already created");
// Create user
Wt::Auth::User authUser = DbHandler().getUserDatabase().registerNew();
Database::User::pointer user = DbHandler().getUser(authUser);
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().registerNew();
Database::User::pointer user = LmsApp->getDb().createUser(authUser);
// Account
authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(AdminLoginField));
@@ -94,7 +93,7 @@ class InitWizardModel : public Wt::WFormModel
}
else if (field == PasswordConfirmField)
{
if (validation(PasswordField).state() == Wt::WValidator::Valid)
if (validation(PasswordField).state() == Wt::ValidationState::Valid)
{
if (valueText(PasswordField) != valueText(PasswordConfirmField))
error = Wt::WString::tr("Lms.passwords-dont-match");
@@ -105,9 +104,9 @@ class InitWizardModel : public Wt::WFormModel
return Wt::WFormModel::validateField(field);
}
setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::WValidator::Valid : Wt::WValidator::Invalid, error));
setValidation(field, Wt::WValidator::Result( error.empty() ? Wt::ValidationState::Valid : Wt::ValidationState::Invalid, error));
return (validation(field).state() == Wt::WValidator::Valid);
return (validation(field).state() == Wt::ValidationState::Valid);
}
};
@@ -116,34 +115,28 @@ const Wt::WFormModel::Field InitWizardModel::AdminLoginField = "admin-login";
const Wt::WFormModel::Field InitWizardModel::PasswordField = "password";
const Wt::WFormModel::Field InitWizardModel::PasswordConfirmField = "password-confirm";
InitWizardView::InitWizardView(Wt::WContainerWidget *parent)
: Wt::WTemplateFormView(parent)
InitWizardView::InitWizardView()
: Wt::WTemplateFormView(Wt::WString::tr("Lms.Admin.InitWizard.template"))
{
auto model = new InitWizardModel(this);
setTemplateText(Wt::WString::tr("Lms.Admin.InitWizard.template"));
addFunction("tr", &WTemplate::Functions::tr);
addFunction("id", &WTemplate::Functions::id);
auto model = std::make_shared<InitWizardModel>();
// AdminLogin
Wt::WLineEdit* accountEdit = new Wt::WLineEdit();
setFormWidget(InitWizardModel::AdminLoginField, accountEdit);
setFormWidget(InitWizardModel::AdminLoginField, std::make_unique<Wt::WLineEdit>());
// Password
Wt::WLineEdit* passwordEdit = new Wt::WLineEdit();
setFormWidget(InitWizardModel::PasswordField, passwordEdit );
passwordEdit->setEchoMode(Wt::WLineEdit::Password);
auto passwordEdit = std::make_unique<Wt::WLineEdit>();
passwordEdit->setEchoMode(Wt::EchoMode::Password);
setFormWidget(InitWizardModel::PasswordField, std::move(passwordEdit) );
// Password confirmation
Wt::WLineEdit* passwordConfirmEdit = new Wt::WLineEdit();
setFormWidget(InitWizardModel::PasswordConfirmField, passwordConfirmEdit);
passwordConfirmEdit->setEchoMode(Wt::WLineEdit::Password);
auto passwordConfirmEdit = std::make_unique<Wt::WLineEdit>();
passwordConfirmEdit->setEchoMode(Wt::EchoMode::Password);
setFormWidget(InitWizardModel::PasswordConfirmField, std::move(passwordConfirmEdit));
auto saveButton = new Wt::WPushButton(Wt::WString::tr("Lms.create"));
bindWidget("create-btn", saveButton);
Wt::WPushButton* saveButton = bindNew<Wt::WPushButton>("create-btn", Wt::WString::tr("Lms.create"));
saveButton->clicked().connect(std::bind([=]
{
updateModel(model);
updateModel(model.get());
if (model->validate())
{
@@ -152,10 +145,10 @@ InitWizardView::InitWizardView(Wt::WContainerWidget *parent)
saveButton->setEnabled(false);
}
updateView(model);
updateView(model.get());
}));
updateView(model);
updateView(model.get());
}
} // namespace UserInterface
+2 -3
View File
@@ -19,15 +19,14 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WTemplateFormView>
#include <Wt/WTemplateFormView.h>
namespace UserInterface {
class InitWizardView : public Wt::WTemplateFormView
{
public:
InitWizardView(Wt::WContainerWidget *parent = 0);
InitWizardView();
};
+47 -52
View File
@@ -17,15 +17,15 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WApplication>
#include <Wt/WCheckBox>
#include <Wt/WComboBox>
#include <Wt/WLineEdit>
#include <Wt/WPushButton>
#include <Wt/WTemplateFormView>
#include <Wt/WApplication.h>
#include <Wt/WCheckBox.h>
#include <Wt/WComboBox.h>
#include <Wt/WLineEdit.h>
#include <Wt/WPushButton.h>
#include <Wt/WTemplateFormView.h>
#include <Wt/WFormModel>
#include <Wt/WStringListModel>
#include <Wt/WFormModel.h>
#include <Wt/WStringListModel.h>
#include "common/Validators.hpp"
#include "utils/Utils.hpp"
@@ -44,8 +44,8 @@ class UserModel : public Wt::WFormModel
static const Field PasswordField;
static const Field BitrateLimitField;
UserModel(boost::optional<Database::User::id_type> userId, Wt::WObject *parent = 0)
: Wt::WFormModel(parent),
UserModel(boost::optional<Database::User::id_type> userId)
: Wt::WFormModel(),
_userId(userId)
{
if (!_userId)
@@ -72,12 +72,12 @@ class UserModel : public Wt::WFormModel
if (!_userId)
return;
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( std::to_string(*_userId) );
Database::User::pointer user = DbHandler().getUser(authUser);
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) );
Database::User::pointer user = LmsApp->getDb().getUser(authUser);
if (user == CurrentUser())
if (user == LmsApp->getCurrentUser())
throw std::runtime_error("Cannot edit ourselves");
auto bitrate = getBitrateLimitRow(user->getMaxAudioBitrate());
@@ -87,13 +87,13 @@ class UserModel : public Wt::WFormModel
void saveData()
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
if (_userId)
{
// Update user
Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId( std::to_string(*_userId) );
Database::User::pointer user = DbHandler().getUser( authUser );
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*_userId) );
Database::User::pointer user = LmsApp->getDb().getUser( authUser );
// Account
if (!valueText(PasswordField).empty())
@@ -106,8 +106,8 @@ class UserModel : public Wt::WFormModel
else
{
// Create user
Wt::Auth::User authUser = DbHandler().getUserDatabase().registerNew();
Database::User::pointer user = DbHandler().createUser(authUser);
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().registerNew();
Database::User::pointer user = LmsApp->getDb().createUser(authUser);
// Account
authUser.setIdentity(Wt::Auth::Identity::LoginName, valueText(LoginField));
@@ -142,35 +142,35 @@ class UserModel : public Wt::WFormModel
std::size_t bitrateLimit(int row)
{
return boost::any_cast<std::size_t>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::UserRole));
return Wt::cpp17::any_cast<std::size_t>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::ItemDataRole::User));
}
Wt::WString bitrateLimitString(int row)
{
return boost::any_cast<Wt::WString>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::DisplayRole));
return Wt::cpp17::any_cast<Wt::WString>
(_bitrateModel->data(_bitrateModel->index(row, 0), Wt::ItemDataRole::Display));
}
Wt::WAbstractItemModel *bitrateModel() { return _bitrateModel; }
std::shared_ptr<Wt::WAbstractItemModel> bitrateModel() { return _bitrateModel; }
private:
void initializeModels()
{
_bitrateModel = new Wt::WStringListModel(this);
_bitrateModel = std::make_shared<Wt::WStringListModel>();
std::size_t id = 0;
for (auto bitrate : Database::User::audioBitrates)
{
_bitrateModel->addString( Wt::WString::fromUTF8(std::to_string(bitrate / 1000)) );
_bitrateModel->setData( id++, 0, bitrate, Wt::UserRole);
_bitrateModel->setData( id++, 0, bitrate, Wt::ItemDataRole::User);
}
}
Wt::WStringListModel* _bitrateModel;
std::shared_ptr<Wt::WStringListModel> _bitrateModel;
boost::optional<Database::User::id_type> _userId;
};
@@ -178,8 +178,7 @@ const Wt::WFormModel::Field UserModel::LoginField = "login";
const Wt::WFormModel::Field UserModel::PasswordField = "password";
const Wt::WFormModel::Field UserModel::BitrateLimitField = "audio-bitrate-limit";
UserView::UserView(Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent)
UserView::UserView()
{
wApp->internalPathChanged().connect(std::bind([=]
{
@@ -195,47 +194,44 @@ UserView::refreshView()
if (!wApp->internalPathMatches("/admin/user"))
return;
auto userId = readLong(wApp->internalPathNextPart("/admin/user/"));
auto userId = readAs<Database::User::id_type>(wApp->internalPathNextPart("/admin/user/"));
LMS_LOG(UI, DEBUG) << "userId = " << (userId ? std::to_string(*userId) : "none");
clear();
auto t = new Wt::WTemplateFormView(Wt::WString::tr("Lms.Admin.User.template"), this);
Wt::WTemplateFormView* t = addNew<Wt::WTemplateFormView>(Wt::WString::tr("Lms.Admin.User.template"));
t->addFunction("tr", &Wt::WTemplate::Functions::tr);
t->addFunction("id", &Wt::WTemplate::Functions::id);
auto model = new UserModel(userId ? boost::make_optional<Database::User::id_type>(*userId) : boost::none, this);
auto model = std::make_shared<UserModel>(userId);
if (userId)
{
auto authUser = DbHandler().getUserDatabase().findWithId( std::to_string(*userId) );
auto authUser = LmsApp->getDb().getUserDatabase().findWithId( std::to_string(*userId) );
auto name = authUser.identity(Wt::Auth::Identity::LoginName);
t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-edit").arg(name), Wt::PlainText);
t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-edit").arg(name), Wt::TextFormat::Plain);
}
else
{
// Login
t->setCondition("if-has-login", true);
t->setFormWidget(UserModel::LoginField, new Wt::WLineEdit());
t->setFormWidget(UserModel::LoginField, std::make_unique<Wt::WLineEdit>());
t->bindString("title", Wt::WString::tr("Lms.Admin.User.user-create"));
}
// Password
Wt::WLineEdit* passwordEdit = new Wt::WLineEdit();
t->setFormWidget(UserModel::PasswordField, passwordEdit );
passwordEdit->setEchoMode(Wt::WLineEdit::Password);
auto passwordEdit = std::make_unique<Wt::WLineEdit>();
passwordEdit->setEchoMode(Wt::EchoMode::Password);
t->setFormWidget(UserModel::PasswordField, std::move(passwordEdit));
// AudioBitrate
Wt::WComboBox *bitrateCB = new Wt::WComboBox();
t->setFormWidget(UserModel::BitrateLimitField, bitrateCB);
bitrateCB->setModel(model->bitrateModel());
// Bitrate
auto bitrate = std::make_unique<Wt::WComboBox>();
bitrate->setModel(model->bitrateModel());
t->setFormWidget(UserModel::BitrateLimitField, std::move(bitrate));
auto saveBtn = new Wt::WPushButton(Wt::WString::tr(userId ? "Lms.save" : "Lms.create"));
t->bindWidget("save-btn", saveBtn);
Wt::WPushButton* saveBtn = t->bindNew<Wt::WPushButton>("save-btn", Wt::WString::tr(userId ? "Lms.save" : "Lms.create"));
saveBtn->clicked().connect(std::bind([=]
{
t->updateModel(model);
t->updateModel(model.get());
if (model->validate())
{
@@ -245,12 +241,11 @@ UserView::refreshView()
}
else
{
t->updateView(model);
t->updateView(model.get());
}
}));
t->updateView(model);
t->updateView(model.get());
}
} // namespace UserInterface
+2 -2
View File
@@ -19,14 +19,14 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WContainerWidget.h>
namespace UserInterface {
class UserView : public Wt::WContainerWidget
{
public:
UserView(Wt::WContainerWidget *parent = 0);
UserView();
private:
void refreshView();
+19 -26
View File
@@ -17,8 +17,8 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WTemplate>
#include <Wt/WPushButton>
#include <Wt/WTemplate.h>
#include <Wt/WPushButton.h>
#include "database/Types.hpp"
#include "utils/Logger.hpp"
@@ -28,18 +28,14 @@
namespace UserInterface {
UsersView::UsersView(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent)
UsersView::UsersView()
: Wt::WTemplate(Wt::WString::tr("Lms.Admin.Users.template"))
{
auto t = new Wt::WTemplate(Wt::WString::tr("Lms.Admin.Users.template"), this);
t->addFunction("tr", &Wt::WTemplate::Functions::tr);
addFunction("tr", &Wt::WTemplate::Functions::tr);
_container = new Wt::WContainerWidget();
t->bindWidget("users", _container);
auto addBtn = new Wt::WPushButton(Wt::WString::tr("Lms.Admin.Users.add"));
t->bindWidget("add-btn", addBtn);
_container = bindNew<Wt::WContainerWidget>("users");
Wt::WPushButton* addBtn = bindNew<Wt::WPushButton>("add-btn", Wt::WString::tr("Lms.Admin.Users.add"));
addBtn->clicked().connect(std::bind([=]
{
LmsApp->setInternalPath("/admin/user", true);
@@ -61,44 +57,41 @@ UsersView::refreshView()
_container->clear();
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto users = Database::User::getAll(DboSession());
auto users = Database::User::getAll(LmsApp->getDboSession());
for (auto user : users)
{
auto userId = std::to_string(user.id());
auto entry = new Wt::WTemplate(Wt::WString::tr("Lms.Admin.Users.template.entry"), _container);
Wt::Auth::User authUser = DbHandler().getUserDatabase().findWithId(userId);
Wt::WTemplate* entry = _container->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Admin.Users.template.entry"));
Wt::Auth::User authUser = LmsApp->getDb().getUserDatabase().findWithId(userId);
if (!authUser.isValid()) {
LMS_LOG(UI, ERROR) << "Skipping invalid userId = " << user.id();
continue;
}
entry->bindString("name", authUser.identity(Wt::Auth::Identity::LoginName), Wt::PlainText);
entry->bindString("name", authUser.identity(Wt::Auth::Identity::LoginName), Wt::TextFormat::Plain);
// Don't edit ourself this way
if (CurrentUser() == user)
if (LmsApp->getCurrentUser() == user)
continue;
entry->setCondition("if-edit", true);
auto editBtn = new Wt::WPushButton(Wt::WString::tr("Lms.Admin.Users.edit"));
entry->bindWidget("edit-btn", editBtn);
Wt::WPushButton* editBtn = entry->bindNew<Wt::WPushButton>("edit-btn", Wt::WString::tr("Lms.Admin.Users.edit"));
editBtn->clicked().connect(std::bind([=]
{
LmsApp->setInternalPath("/admin/user/" + std::to_string(user.id()), true);
}));
auto delBtn = new Wt::WPushButton(Wt::WString::tr("Lms.Admin.Users.del"));
entry->bindWidget("del-btn", delBtn);
Wt::WPushButton* delBtn = entry->bindNew<Wt::WPushButton>("del-btn", Wt::WString::tr("Lms.Admin.Users.del"));
delBtn->clicked().connect(std::bind([=]
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto authUser = DbHandler().getUserDatabase().findWithId(userId);
auto user = DbHandler().getUser(authUser);
DbHandler().getUserDatabase().deleteUser( authUser );
auto authUser = LmsApp->getDb().getUserDatabase().findWithId(userId);
auto user = LmsApp->getDb().getUser(authUser);
LmsApp->getDb().getUserDatabase().deleteUser( authUser );
user.remove();
_container->removeWidget(entry);
}));
+3 -3
View File
@@ -19,14 +19,14 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WContainerWidget.h>
namespace UserInterface {
class UsersView : public Wt::WContainerWidget
class UsersView : public Wt::WTemplate
{
public:
UsersView(Wt::WContainerWidget *parent = 0);
UsersView();
private:
void refreshView();
-56
View File
@@ -1,56 +0,0 @@
/*
* Copyright (C) 2015 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/WConfig.h>
#if WT_VERSION < 0X03030500
#include <Wt/WTemplate>
#include <Wt/WText>
#endif
#include "InputRange.hpp"
#if WT_VERSION >= 0X03030500
InputRange::InputRange(Wt::WContainerWidget *parent)
: Wt::WWebWidget(parent)
{
setHtmlTagName("input");
setAttributeValue("type", "range");
}
std::string
InputRange::jsRef(void) const
{
return Wt::WWebWidget::jsRef();
}
#else
InputRange::InputRange(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent)
{
addWidget(new Wt::WTemplate("<input type=\"range\"></input>"));
}
std::string
InputRange::jsRef(void) const
{
return Wt::WWebWidget::jsRef() + ".getElementsByTagName(\"input\")[0]";
}
#endif
-51
View File
@@ -1,51 +0,0 @@
/*
* Copyright (C) 2015 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WWebWidget>
#include <Wt/WConfig.h>
#if WT_VERSION >= 0X03030500
class InputRange : public Wt::WWebWidget
{
public:
InputRange(Wt::WContainerWidget *parent = 0);
Wt::DomElementType domElementType() const
{
return Wt::DomElement_INPUT;
}
std::string jsRef() const;
};
#else
class InputRange : public Wt::WContainerWidget
{
public:
InputRange(Wt::WContainerWidget *parent = 0);
std::string jsRef() const;
};
#endif
-55
View File
@@ -1,55 +0,0 @@
/*
* Copyright (C) 2015 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/WTimer>
#include "LineEdit.hpp"
namespace UserInterface {
LineEdit::LineEdit(std::size_t ms, Wt::WContainerWidget* parent)
: Wt::WLineEdit(parent)
{
Wt::WTimer *timer = new Wt::WTimer(this);
timer->setSingleShot(true);
timer->setInterval(ms);
this->keyWentUp().connect(std::bind([=] (Wt::WKeyEvent keyEvent)
{
if (timer->isActive())
timer->stop();
if (keyEvent.key() == Wt::Key_Enter)
_sigTimedChanged.emit(this->text());
else
timer->start();
}, std::placeholders::_1));
timer->timeout().connect(std::bind([=] ()
{
_sigTimedChanged.emit(this->text());
}));
}
} // namespace UserInterface
-44
View File
@@ -1,44 +0,0 @@
/*
* Copyright (C) 2015 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_LINE_EDIT_HPP
#define UI_LINE_EDIT_HPP
#include <Wt/WLineEdit>
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
namespace UserInterface {
class LineEdit : public Wt::WLineEdit
{
public:
LineEdit(std::size_t ms, Wt::WContainerWidget* parent = 0);
Wt::Signal<Wt::WString>& timedChanged() { return _sigTimedChanged; }
private:
Wt::Signal<Wt::WString> _sigTimedChanged;
};
} // namespace UserInterface
#endif
+12 -9
View File
@@ -19,28 +19,31 @@
#include <boost/filesystem.hpp>
#include <Wt/WLengthValidator.h>
#include "Validators.hpp"
namespace UserInterface {
Wt::WValidator* createNameValidator()
std::shared_ptr<Wt::WValidator>
createNameValidator()
{
Wt::WLengthValidator *v = new Wt::WLengthValidator();
auto v = std::make_shared<Wt::WLengthValidator>();
v->setMandatory(true);
v->setMinimumLength(::Database::User::MinNameLength);
v->setMaximumLength(::Database::User::MaxNameLength);
return v;
}
Wt::WValidator* createMandatoryValidator()
std::shared_ptr<Wt::WValidator>
createMandatoryValidator()
{
auto v = new Wt::WValidator();
auto v = std::make_shared<Wt::WValidator>();
v->setMandatory(true);
return v;
}
DirectoryValidator::DirectoryValidator(Wt::WObject *parent)
: Wt::WValidator(parent)
DirectoryValidator::DirectoryValidator() : Wt::WValidator()
{
}
@@ -56,11 +59,11 @@ DirectoryValidator::validate(const Wt::WString& input) const
// TODO check rights
bool res = boost::filesystem::is_directory(p, ec);
if (ec)
return Wt::WValidator::Result(Wt::WValidator::Invalid, ec.message()); // TODO translate common errors
return Wt::WValidator::Result(Wt::ValidationState::Invalid, ec.message()); // TODO translate common errors
else if (res)
return Wt::WValidator::Result(Wt::WValidator::Valid);
return Wt::WValidator::Result(Wt::ValidationState::Valid);
else
return Wt::WValidator::Result(Wt::WValidator::Invalid, Wt::WString::tr("Lms.not-a-directory"));
return Wt::WValidator::Result(Wt::ValidationState::Invalid, Wt::WString::tr("Lms.not-a-directory"));
}
+4 -4
View File
@@ -19,19 +19,19 @@
#pragma once
#include <Wt/WLengthValidator>
#include <Wt/WValidator.h>
#include "database/User.hpp"
namespace UserInterface {
Wt::WValidator* createNameValidator();
Wt::WValidator* createMandatoryValidator();
std::shared_ptr<Wt::WValidator> createNameValidator();
std::shared_ptr<Wt::WValidator> createMandatoryValidator();
class DirectoryValidator : public Wt::WValidator
{
public:
DirectoryValidator(Wt::WObject *parent = 0);
DirectoryValidator();
Wt::WValidator::Result validate(const Wt::WString& input) const override;
+28 -40
View File
@@ -17,11 +17,10 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WApplication>
#include <Wt/WAnchor>
#include <Wt/WImage>
#include <Wt/WTemplate>
#include <Wt/WText>
#include <Wt/WAnchor.h>
#include <Wt/WImage.h>
#include <Wt/WTemplate.h>
#include <Wt/WText.h>
#include "database/Types.hpp"
@@ -36,9 +35,8 @@
namespace UserInterface {
Artist::Artist(Filters* filters, Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent),
_filters(filters)
Artist::Artist(Filters* filters)
: _filters(filters)
{
wApp->internalPathChanged().connect(std::bind([=]
{
@@ -60,81 +58,73 @@ Artist::refresh()
clear();
Database::Artist::id_type artistId;
if (!readAs(wApp->internalPathNextPart("/artist/"), artistId))
auto artistId = readAs<Database::Artist::id_type>(wApp->internalPathNextPart("/artist/"));
if (!artistId)
return;
Wt::Dbo::Transaction transaction(DboSession());
auto artist = Database::Artist::getById(DboSession(), artistId);
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto artist = Database::Artist::getById(LmsApp->getDboSession(), *artistId);
if (!artist)
{
LmsApp->goHome();
return;
}
auto t = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Artist.template"), this);
Wt::WTemplate* t = addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artist.template"));
t->addFunction("tr", &Wt::WTemplate::Functions::tr);
auto clusterContainers = new Wt::WContainerWidget();
t->bindWidget("clusters", clusterContainers);
Wt::WContainerWidget* clusterContainers = t->bindNew<Wt::WContainerWidget>("clusters");
{
auto clusters = artist->getClusters(3);
for (auto cluster : clusters)
{
auto entry = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Artist.template.cluster-entry"), clusterContainers);
entry->bindString("name", Wt::WString::fromUTF8(cluster->getName()), Wt::PlainText);
Wt::WTemplate* entry = clusterContainers->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artist.template.cluster-entry"));
entry->bindString("name", Wt::WString::fromUTF8(cluster->getName()), Wt::TextFormat::Plain);
}
}
t->bindString("name", Wt::WString::fromUTF8(artist->getName()), Wt::PlainText);
t->bindString("name", Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain);
{
auto playBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Artist.play"), Wt::XHTMLText);
t->bindWidget("play-btn", playBtn);
Wt::WText* playBtn = t->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.Artist.play"), Wt::TextFormat::XHTML);
playBtn->clicked().connect(std::bind([=]
{
artistPlay.emit(artistId);
artistPlay.emit(*artistId);
}));
}
{
auto addBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Artist.add"), Wt::XHTMLText);
t->bindWidget("add-btn", addBtn);
Wt::WText* addBtn = t->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.Artist.add"), Wt::TextFormat::XHTML);
addBtn->clicked().connect(std::bind([=]
{
artistAdd.emit(artistId);
artistAdd.emit(*artistId);
}));
}
auto releasesContainer = new Wt::WContainerWidget();
t->bindWidget("releases", releasesContainer);
Wt::WContainerWidget* releasesContainer = t->bindNew<Wt::WContainerWidget>("releases");
auto releases = artist->getReleases(_filters->getClusterIds());
for (auto release : releases)
{
auto releaseId = release.id();
Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Artist.template.entry"), releasesContainer);
Wt::WTemplate* entry = releasesContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artist.template.entry"));
entry->addFunction("tr", Wt::WTemplate::Functions::tr);
{
Wt::WAnchor* coverAnchor = LmsApplication::createReleaseAnchor(release, false);
Wt::WImage* cover = new Wt::WImage(coverAnchor);
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
auto cover = std::make_unique<Wt::WImage>();
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 128));
// Some images may not be square
cover->setWidth(128);
entry->bindWidget("cover", coverAnchor);
anchor->setImage(std::move(cover));
}
{
Wt::WAnchor* releaseAnchor = LmsApplication::createReleaseAnchor(release);
entry->bindWidget("name", releaseAnchor);
}
entry->bindWidget("name", LmsApplication::createReleaseAnchor(release));
if (release->hasVariousArtists())
entry->setCondition("if-has-various-artists", true);
@@ -153,15 +143,13 @@ Artist::refresh()
}
}
auto playBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Artist.play"), Wt::XHTMLText);
entry->bindWidget("play-btn", playBtn);
Wt::WText* playBtn = entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.Artist.play"), Wt::TextFormat::XHTML);
playBtn->clicked().connect(std::bind([=]
{
releasePlay.emit(releaseId);
}));
auto addBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Artist.add"), Wt::XHTMLText);
entry->bindWidget("add-btn", addBtn);
Wt::WText* addBtn = entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.Artist.add"), Wt::TextFormat::XHTML);
addBtn->clicked().connect(std::bind([=]
{
releaseAdd.emit(releaseId);
+3 -3
View File
@@ -19,8 +19,8 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
#include <Wt/WContainerWidget.h>
#include <Wt/WSignal.h>
namespace UserInterface {
@@ -29,7 +29,7 @@ class Filters;
class Artist : public Wt::WContainerWidget
{
public:
Artist(Filters* filters, Wt::WContainerWidget* parent = 0);
Artist(Filters* filters);
Wt::Signal<Database::id_type> artistAdd;
Wt::Signal<Database::id_type> artistPlay;
+13 -21
View File
@@ -17,9 +17,9 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WAnchor>
#include <Wt/WTemplate>
#include <Wt/WLineEdit>
#include <Wt/WAnchor.h>
#include <Wt/WTemplate.h>
#include <Wt/WLineEdit.h>
#include "database/Types.hpp"
@@ -34,24 +34,20 @@ namespace UserInterface {
using namespace Database;
Artists::Artists(Filters* filters, Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent),
Artists::Artists(Filters* filters)
: Wt::WTemplate(Wt::WString::tr("Lms.Explore.Artists.template")),
_filters(filters)
{
auto container = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Artists.template"), this);
container->addFunction("tr", &Wt::WTemplate::Functions::tr);
addFunction("tr", &Wt::WTemplate::Functions::tr);
_search = new Wt::WLineEdit();
container->bindWidget("search", _search);
_search = bindNew<Wt::WLineEdit>("search");
_search->setPlaceholderText(Wt::WString::tr("Lms.Explore.search-placeholder"));
_search->textInput().connect(this, &Artists::refresh);
_artistsContainer = new Wt::WContainerWidget();
container->bindWidget("artists", _artistsContainer);
_artistsContainer = bindNew<Wt::WContainerWidget>("artists");
_showMore = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.template.show-more"));
_showMore = bindNew<Wt::WTemplate>("show-more", Wt::WString::tr("Lms.Explore.template.show-more"));
_showMore->addFunction("tr", &Wt::WTemplate::Functions::tr);
container->bindWidget("show-more", _showMore);
_showMore->clicked().connect(std::bind([=]
{
@@ -77,24 +73,20 @@ Artists::addSome()
auto clusterIds = _filters->getClusterIds();
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
bool moreResults;
auto artists = Artist::getByFilter(DboSession(),
auto artists = Artist::getByFilter(LmsApp->getDboSession(),
clusterIds,
searchKeywords,
_artistsContainer->count(), 20, moreResults);
for (auto artist : artists)
{
auto entry = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Artists.template.entry"), _artistsContainer);
Wt::WTemplate* entry = _artistsContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Artists.template.entry"));
entry->bindInt("nb-release", artist->getReleases(clusterIds).size());
{
Wt::WAnchor *artistAnchor = LmsApplication::createArtistAnchor(artist);
entry->bindWidget("name", artistAnchor);
}
entry->bindWidget("name", LmsApplication::createArtistAnchor(artist));
}
_showMore->setHidden(!moreResults);
+6 -5
View File
@@ -19,18 +19,19 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WLineEdit>
#include <Wt/WSignal>
#include <Wt/WContainerWidget.h>
#include <Wt/WTemplate.h>
#include <Wt/WLineEdit.h>
#include <Wt/WSignal.h>
namespace UserInterface {
class Filters;
class Artists : public Wt::WContainerWidget
class Artists : public Wt::WTemplate
{
public:
Artists(Filters* filters, Wt::WContainerWidget* parent = 0);
Artists(Filters* filters);
Wt::Signal<Database::id_type> artistAdd;
Wt::Signal<Database::id_type> artistPlay;
+29 -39
View File
@@ -17,9 +17,9 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WStackedWidget>
#include <Wt/WTemplate>
#include <Wt/WText>
#include <Wt/WStackedWidget.h>
#include <Wt/WTemplate.h>
#include <Wt/WText.h>
#include "utils/Logger.hpp"
@@ -70,54 +70,44 @@ handlePathChange(Wt::WStackedWidget* stack)
}
}
Explore::Explore(Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent)
Explore::Explore()
: Wt::WTemplate(Wt::WString::tr("Lms.Explore.template"))
{
auto container = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.template"), this);
_filters = new Filters();
container->bindWidget("filters", _filters);
_filters = bindNew<Filters>("filters");
// Contents
Wt::WStackedWidget* stack = new Wt::WStackedWidget();
container->bindWidget("contents", stack);
auto artists = new Artists(_filters);
stack->addWidget(artists);
Wt::WStackedWidget* stack = bindNew<Wt::WStackedWidget>("contents");
auto artists = std::make_unique<Artists>(_filters);
artists->artistAdd.connect(this, &Explore::handleArtistAdd);
artists->artistPlay.connect(this, &Explore::handleArtistPlay);
stack->addWidget(std::move(artists));
auto artist = new Artist(_filters);
stack->addWidget(artist);
auto artist = std::make_unique<Artist>(_filters);
artist->artistAdd.connect(this, &Explore::handleArtistAdd);
artist->artistPlay.connect(this, &Explore::handleArtistPlay);
artist->releaseAdd.connect(this, &Explore::handleReleaseAdd);
artist->releasePlay.connect(this, &Explore::handleReleasePlay);
stack->addWidget(std::move(artist));
auto releases = new Releases(_filters);
stack->addWidget(releases);
auto releases = std::make_unique<Releases>(_filters);
releases->releaseAdd.connect(this, &Explore::handleReleaseAdd);
releases->releasePlay.connect(this, &Explore::handleReleasePlay);
stack->addWidget(std::move(releases));
auto release = new Release(_filters);
stack->addWidget(release);
auto release = std::make_unique<Release>(_filters);
release->releaseAdd.connect(this, &Explore::handleReleaseAdd);
release->releasePlay.connect(this, &Explore::handleReleasePlay);
release->trackAdd.connect(this, &Explore::handleTrackAdd);
release->trackPlay.connect(this, &Explore::handleTrackPlay);
stack->addWidget(std::move(release));
auto tracks = new Tracks(_filters);
stack->addWidget(tracks);
auto tracks = std::make_unique<Tracks>(_filters);
tracks->trackAdd.connect(this, &Explore::handleTrackAdd);
tracks->trackPlay.connect(this, &Explore::handleTrackPlay);
tracks->tracksAdd.connect(this, &Explore::handleTracksAdd);
tracks->tracksPlay.connect(this, &Explore::handleTracksPlay);
stack->addWidget(std::move(tracks));
wApp->internalPathChanged().connect(std::bind([=]
{
@@ -175,49 +165,49 @@ static std::vector<Database::Track::pointer> getTrack(Wt::Dbo::Session& session,
void
Explore::handleArtistAdd(Database::id_type id)
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksAdd.emit(getArtistTracks(DboSession(), id, _filters->getClusterIds()));
tracksAdd.emit(getArtistTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
}
void
Explore::handleArtistPlay(Database::id_type id)
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksPlay.emit(getArtistTracks(DboSession(), id, _filters->getClusterIds()));
tracksPlay.emit(getArtistTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
}
void
Explore::handleReleaseAdd(Database::id_type id)
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksAdd.emit(getReleaseTracks(DboSession(), id, _filters->getClusterIds()));
tracksAdd.emit(getReleaseTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
}
void
Explore::handleReleasePlay(Database::id_type id)
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksPlay.emit(getReleaseTracks(DboSession(), id, _filters->getClusterIds()));
tracksPlay.emit(getReleaseTracks(LmsApp->getDboSession(), id, _filters->getClusterIds()));
}
void
Explore::handleTrackAdd(Database::id_type id)
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksAdd.emit(getTrack(DboSession(), id));
tracksAdd.emit(getTrack(LmsApp->getDboSession(), id));
}
void
Explore::handleTrackPlay(Database::id_type id)
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksPlay.emit(getTrack(DboSession(), id));
tracksPlay.emit(getTrack(LmsApp->getDboSession(), id));
}
void
+9 -9
View File
@@ -19,7 +19,7 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WTemplate.h>
#include "database/Types.hpp"
@@ -27,22 +27,22 @@ namespace UserInterface {
class Filters;
class Explore : public Wt::WContainerWidget
class Explore : public Wt::WTemplate
{
public:
Explore(Wt::WContainerWidget *parent = 0);
Explore();
Wt::Signal<std::vector<Database::Track::pointer>> tracksAdd;
Wt::Signal<std::vector<Database::Track::pointer>> tracksPlay;
private:
void handleArtistAdd(Database::id_type id);
void handleArtistPlay(Database::id_type id);
void handleReleaseAdd(Database::id_type id);
void handleReleasePlay(Database::id_type id);
void handleTrackAdd(Database::id_type id);
void handleTrackPlay(Database::id_type id);
void handleArtistAdd(Database::Artist::id_type id);
void handleArtistPlay(Database::Artist::id_type id);
void handleReleaseAdd(Database::Release::id_type id);
void handleReleasePlay(Database::Release::id_type id);
void handleTrackAdd(Database::Track::id_type id);
void handleTrackPlay(Database::Track::id_type id);
void handleTracksAdd(std::vector<Database::Track::pointer> tracks);
void handleTracksPlay(std::vector<Database::Track::pointer> tracks);
+25 -44
View File
@@ -17,10 +17,10 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WComboBox>
#include <Wt/WDialog>
#include <Wt/WPushButton>
#include <Wt/WTemplate>
#include <Wt/WComboBox.h>
#include <Wt/WDialog.h>
#include <Wt/WPushButton.h>
#include <Wt/WTemplate.h>
#include "Filters.hpp"
@@ -31,32 +31,25 @@ namespace UserInterface {
void
Filters::showDialog()
{
auto dialog = new Wt::WDialog(Wt::WString::tr("Lms.Explore.add-filter"));
auto dialog = std::make_shared<Wt::WDialog>(Wt::WString::tr("Lms.Explore.add-filter"));
auto container = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.template.add-filter"));
Wt::WTemplate* container = dialog->contents()->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.template.add-filter"));
container->addFunction("tr", &Wt::WTemplate::Functions::tr);
dialog->contents()->addWidget(container);
Wt::WComboBox* typeCombo = container->bindNew<Wt::WComboBox>("type");
Wt::WComboBox* valueCombo = container->bindNew<Wt::WComboBox>("value");
auto typeCombo = new Wt::WComboBox();
container->bindWidget("type", typeCombo);
Wt::WPushButton* addBtn = container->bindNew<Wt::WPushButton>("add-btn", Wt::WString::tr("Lms.add"));
addBtn->clicked().connect(dialog.get(), &Wt::WDialog::accept);
auto valueCombo = new Wt::WComboBox();
container->bindWidget("value", valueCombo);
auto addBtn = new Wt::WPushButton(Wt::WString::tr("Lms.add"));
container->bindWidget("add-btn", addBtn);
addBtn->clicked().connect(dialog, &Wt::WDialog::accept);
auto cancelBtn = new Wt::WPushButton(Wt::WString::tr("Lms.cancel"));
container->bindWidget("cancel-btn", cancelBtn);
cancelBtn->clicked().connect(dialog, &Wt::WDialog::reject);
Wt::WPushButton* cancelBtn = container->bindNew<Wt::WPushButton>("cancel-btn", Wt::WString::tr("Lms.cancel"));
cancelBtn->clicked().connect(dialog.get(), &Wt::WDialog::reject);
// Populate data
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto types = Database::ClusterType::getAll(DboSession());
auto types = Database::ClusterType::getAll(LmsApp->getDboSession());
for (auto type : types)
typeCombo->addItem(Wt::WString::fromUTF8(type->getName()));
@@ -81,9 +74,9 @@ Filters::showDialog()
valueCombo->clear();
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto clusterType = Database::ClusterType::getByName(DboSession(), name);
auto clusterType = Database::ClusterType::getByName(LmsApp->getDboSession(), name);
auto values = clusterType->getClusters();
for (auto value : values)
@@ -94,24 +87,22 @@ Filters::showDialog()
}));
dialog->setModal(true);
#if WT_VERSION >= 0x03030700
dialog->setMovable(false);
#endif
dialog->setResizable(false);
dialog->setClosable(false);
dialog->finished().connect(std::bind([=]
{
if (dialog->result() != Wt::WDialog::Accepted)
if (dialog->result() != Wt::DialogCode::Accepted)
return;
auto type = typeCombo->valueText().toUTF8();
auto value = valueCombo->valueText().toUTF8();
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto clusterType = Database::ClusterType::getByName(DboSession(), type);
auto clusterType = Database::ClusterType::getByName(LmsApp->getDboSession(), type);
if (!clusterType)
return;
@@ -123,8 +114,7 @@ Filters::showDialog()
_filterIds.insert(clusterId);
_sigUpdated.emit();
auto filterBtn = new Wt::WPushButton(Wt::WString::fromUTF8(value));
_filters->addWidget(filterBtn);
Wt::WPushButton* filterBtn = _filters->addNew<Wt::WPushButton>(Wt::WString::fromUTF8(value), Wt::TextFormat::Plain);
filterBtn->clicked().connect(std::bind([=]
{
@@ -137,23 +127,14 @@ Filters::showDialog()
dialog->show();
}
Filters::Filters(Wt::WContainerWidget *parent)
: Wt::WContainerWidget(parent)
Filters::Filters()
: Wt::WTemplate(Wt::WString::tr("Lms.Explore.template.filters"))
{
auto container = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.template.filters"), this);
// Filters
Wt::WPushButton *addFilterBtn = new Wt::WPushButton(Wt::WText::tr("Lms.Explore.add-filter"));
container->bindWidget("add-filter", addFilterBtn);
_filters = new Wt::WContainerWidget();
container->bindWidget("filters", _filters);
addFilterBtn->clicked().connect(std::bind([this]
{
showDialog();
}));
Wt::WPushButton *addFilterBtn = bindNew<Wt::WPushButton>("add-filter", Wt::WText::tr("Lms.Explore.add-filter"));
addFilterBtn->clicked().connect(this, &Filters::showDialog);
_filters = bindNew<Wt::WContainerWidget>("filters");
}
+7 -6
View File
@@ -19,8 +19,9 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WSignal>
#include <Wt/WContainerWidget.h>
#include <Wt/WSignal.h>
#include <Wt/WTemplate.h>
#include "database/Types.hpp"
@@ -28,21 +29,21 @@
namespace UserInterface {
class Filters : public Wt::WContainerWidget
class Filters : public Wt::WTemplate
{
public:
Filters(Wt::WContainerWidget *parent = 0);
Filters();
std::set<Database::Cluster::id_type> getClusterIds() const { return _filterIds; }
Wt::Signal<void>& updated() { return _sigUpdated; }
Wt::Signal<>& updated() { return _sigUpdated; }
private:
void showDialog();
Wt::WContainerWidget *_filters;
Wt::Signal<void> _sigUpdated;
Wt::Signal<> _sigUpdated;
std::set<Database::Cluster::id_type> _filterIds;
};
+28 -43
View File
@@ -17,11 +17,11 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WApplication>
#include <Wt/WAnchor>
#include <Wt/WImage>
#include <Wt/WTemplate>
#include <Wt/WText>
#include <Wt/WApplication.h>
#include <Wt/WAnchor.h>
#include <Wt/WImage.h>
#include <Wt/WTemplate.h>
#include <Wt/WText.h>
#include "database/Types.hpp"
@@ -36,9 +36,8 @@
namespace UserInterface {
Release::Release(Filters* filters, Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent),
_filters(filters)
Release::Release(Filters* filters)
: _filters(filters)
{
wApp->internalPathChanged().connect(std::bind([=]
{
@@ -60,24 +59,23 @@ Release::refresh()
clear();
Database::Release::id_type releaseId;
if (!readAs(wApp->internalPathNextPart("/release/"), releaseId))
auto releaseId = readAs<Database::Release::id_type>(wApp->internalPathNextPart("/release/"));
if (!releaseId)
return;
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
auto release = Database::Release::getById(DboSession(), releaseId);
auto release = Database::Release::getById(LmsApp->getDboSession(), *releaseId);
if (!release)
{
LmsApp->goHome();
return;
}
auto t = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Release.template"), this);
Wt::WTemplate* t = addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template"));
t->addFunction("tr", &Wt::WTemplate::Functions::tr);
t->bindString("name", Wt::WString::fromUTF8(release->getName()), Wt::PlainText);
t->bindString("name", Wt::WString::fromUTF8(release->getName()), Wt::TextFormat::Plain);
boost::optional<int> year = release->getReleaseYear();
if (year)
@@ -103,49 +101,40 @@ Release::refresh()
else if (artists.size() == 1)
{
t->setCondition("if-has-artist", true);
Wt::WAnchor *artistAnchor = LmsApplication::createArtistAnchor(artists.front());
t->bindWidget("artist-name", artistAnchor);
t->bindWidget("artist-name", LmsApplication::createArtistAnchor(artists.front()));
}
}
Wt::WImage *cover = new Wt::WImage();
cover->setImageLink(Wt::WLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 512)));
t->bindWidget("cover", cover);
auto clusterContainers = new Wt::WContainerWidget();
t->bindWidget("clusters", clusterContainers);
t->bindNew<Wt::WImage>("cover", Wt::WLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 512)));
Wt::WContainerWidget* clusterContainers = t->bindNew<Wt::WContainerWidget>("clusters");
{
auto clusters = release->getClusters(3);
for (auto cluster : clusters)
{
auto entry = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Release.template.cluster-entry"), clusterContainers);
entry->bindString("name", Wt::WString::fromUTF8(cluster->getName()), Wt::PlainText);
Wt::WTemplate* entry = clusterContainers->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.cluster-entry"));
entry->bindString("name", Wt::WString::fromUTF8(cluster->getName()), Wt::TextFormat::Plain);
}
}
{
auto playBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Release.play"), Wt::XHTMLText);
t->bindWidget("play-btn", playBtn);
Wt::WText* playBtn = t->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.Release.play"), Wt::TextFormat::XHTML);
playBtn->clicked().connect(std::bind([=]
{
releasePlay.emit(releaseId);
releasePlay.emit(*releaseId);
}));
}
{
auto addBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Release.add"), Wt::XHTMLText);
t->bindWidget("add-btn", addBtn);
Wt::WText* addBtn = t->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.Release.add"), Wt::TextFormat::XHTML);
addBtn->clicked().connect(std::bind([=]
{
releaseAdd.emit(releaseId);
releaseAdd.emit(*releaseId);
}));
}
auto tracksContainer = new Wt::WContainerWidget();
t->bindWidget("tracks", tracksContainer);
Wt::WContainerWidget* tracksContainer = t->bindNew<Wt::WContainerWidget>("tracks");
auto clusterIds = _filters->getClusterIds();
auto tracks = release->getTracks(clusterIds);
@@ -156,15 +145,13 @@ Release::refresh()
{
auto trackId = track.id();
Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Release.template.entry"), tracksContainer);
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText);
Wt::WTemplate* entry = tracksContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.entry"));
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
if (variousArtists && track->getArtist())
{
entry->setCondition("if-has-artist", true);
Wt::WAnchor *artistAnchor = LmsApplication::createArtistAnchor(track->getArtist());
entry->bindWidget("artist-name", artistAnchor);
entry->bindWidget("artist-name", LmsApplication::createArtistAnchor(track->getArtist()));
}
auto trackNumber = track->getTrackNumber();
@@ -182,15 +169,13 @@ Release::refresh()
entry->bindInt("disc-number", *discNumber);
}
auto playBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Release.play"), Wt::XHTMLText);
entry->bindWidget("play-btn", playBtn);
Wt::WText* playBtn = entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.Release.play"), Wt::TextFormat::XHTML);
playBtn->clicked().connect(std::bind([=]
{
trackPlay.emit(trackId);
}));
auto addBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Release.add"), Wt::XHTMLText);
entry->bindWidget("add-btn", addBtn);
Wt::WText* addBtn = entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.Release.add"), Wt::TextFormat::XHTML);
addBtn->clicked().connect(std::bind([=]
{
trackAdd.emit(trackId);
+2 -2
View File
@@ -19,7 +19,7 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WSignal.h>
namespace UserInterface {
@@ -27,7 +27,7 @@ class Filters;
class Release : public Wt::WContainerWidget
{
public:
Release(Filters* filters, Wt::WContainerWidget* parent = 0);
Release(Filters* filters);
Wt::Signal<Database::id_type> releaseAdd;
Wt::Signal<Database::id_type> releasePlay;
+23 -36
View File
@@ -17,11 +17,11 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WApplication>
#include <Wt/WAnchor>
#include <Wt/WImage>
#include <Wt/WText>
#include <Wt/WTemplate>
#include <Wt/WApplication.h>
#include <Wt/WAnchor.h>
#include <Wt/WImage.h>
#include <Wt/WText.h>
#include <Wt/WTemplate.h>
#include "database/Types.hpp"
@@ -38,25 +38,20 @@ namespace UserInterface {
using namespace Database;
Releases::Releases(Filters* filters, Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent),
_filters(filters)
Releases::Releases(Filters* filters)
: Wt::WTemplate(Wt::WString::tr("Lms.Explore.Releases.template")),
_filters(filters)
{
auto container = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Releases.template"), this);
container->addFunction("tr", &Wt::WTemplate::Functions::tr);
addFunction("tr", &Wt::WTemplate::Functions::tr);
_search = new Wt::WLineEdit();
container->bindWidget("search", _search);
_search = bindNew<Wt::WLineEdit>("search");
_search->setPlaceholderText(Wt::WString::tr("Lms.Explore.search-placeholder"));
_search->textInput().connect(this, &Releases::refresh);
_releasesContainer = new Wt::WContainerWidget();
container->bindWidget("releases", _releasesContainer);
_releasesContainer = bindNew<Wt::WContainerWidget>("releases");
_showMore = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.show-more"));
_showMore = bindNew<Wt::WTemplate>("show-more", Wt::WString::tr("Lms.Explore.show-more"));
_showMore->addFunction("tr", &Wt::WTemplate::Functions::tr);
container->bindWidget("show-more", _showMore);
_showMore->clicked().connect(std::bind([=]
{
addSome();
@@ -81,29 +76,24 @@ Releases::addSome()
auto clusterIds = _filters->getClusterIds();
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
bool moreResults;
auto releases = Release::getByFilter(DboSession(), clusterIds, searchKeywords, _releasesContainer->count(), 20, moreResults);
auto releases = Release::getByFilter(LmsApp->getDboSession(), clusterIds, searchKeywords, _releasesContainer->count(), 20, moreResults);
for (auto release : releases)
{
auto releaseId = release.id();
Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Releases.template.entry"), _releasesContainer);
Wt::WTemplate* entry = _releasesContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Releases.template.entry"));
entry->addFunction("tr", Wt::WTemplate::Functions::tr);
Wt::WAnchor* coverAnchor = LmsApplication::createReleaseAnchor(release, false);
Wt::WImage* cover = new Wt::WImage(coverAnchor);
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(releaseId, 128));
// Some images may not be square
cover->setWidth(128);
entry->bindWidget("cover", coverAnchor);
Wt::WAnchor* anchor = entry->bindWidget("cover", LmsApplication::createReleaseAnchor(release, false));
auto cover = std::make_unique<Wt::WImage>();
cover->setImageLink(LmsApp->getImageResource()->getReleaseUrl(release.id(), 128));
anchor->setImage(std::move(cover));
{
Wt::WAnchor* releaseAnchor = LmsApplication::createReleaseAnchor(release);
entry->bindWidget("release-name", releaseAnchor);
}
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(release));
auto artists = release->getArtists();
if (artists.size() > 1)
@@ -114,19 +104,16 @@ Releases::addSome()
else if (artists.size() == 1)
{
entry->setCondition("if-has-artist", true);
Wt::WAnchor* artistAnchor = LmsApplication::createArtistAnchor(artists.front());
entry->bindWidget("artist-name", artistAnchor);
entry->bindWidget("artist-name", LmsApplication::createArtistAnchor(artists.front()));
}
auto playBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Releases.play"), Wt::XHTMLText);
entry->bindWidget("play-btn", playBtn);
Wt::WText* playBtn = entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.Releases.play"), Wt::TextFormat::XHTML);
playBtn->clicked().connect(std::bind([=]
{
releasePlay.emit(releaseId);
}));
auto addBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Releases.add"), Wt::XHTMLText);
entry->bindWidget("add-btn", addBtn);
Wt::WText* addBtn = entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.Releases.add"), Wt::TextFormat::XHTML);
addBtn->clicked().connect(std::bind([=]
{
releaseAdd.emit(releaseId);
+6 -5
View File
@@ -19,18 +19,19 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WLineEdit>
#include <Wt/WText>
#include <Wt/WContainerWidget.h>
#include <Wt/WLineEdit.h>
#include <Wt/WText.h>
#include <Wt/WTemplate.h>
namespace UserInterface {
class Filters;
class Releases : public Wt::WContainerWidget
class Releases : public Wt::WTemplate
{
public:
Releases(Filters* filters, Wt::WContainerWidget* parent = 0);
Releases(Filters* filters);
Wt::Signal<Database::id_type> releaseAdd;
Wt::Signal<Database::id_type> releasePlay;
+25 -38
View File
@@ -17,10 +17,10 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WAnchor>
#include <Wt/WImage>
#include <Wt/WLineEdit>
#include <Wt/WText>
#include <Wt/WAnchor.h>
#include <Wt/WImage.h>
#include <Wt/WLineEdit.h>
#include <Wt/WText.h>
#include "database/Types.hpp"
@@ -37,41 +37,34 @@ namespace UserInterface {
using namespace Database;
Tracks::Tracks(Filters* filters, Wt::WContainerWidget* parent)
: Wt::WContainerWidget(parent),
_filters(filters)
Tracks::Tracks(Filters* filters)
: Wt::WTemplate(Wt::WString::tr("Lms.Explore.Tracks.template")),
_filters(filters)
{
auto container = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Tracks.template"), this);
container->addFunction("tr", &Wt::WTemplate::Functions::tr);
addFunction("tr", &Wt::WTemplate::Functions::tr);
_search = new Wt::WLineEdit();
container->bindWidget("search", _search);
_search = bindNew<Wt::WLineEdit>("search");
_search->setPlaceholderText(Wt::WString::tr("Lms.Explore.search-placeholder"));
_search->textInput().connect(this, &Tracks::refresh);
auto playBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Tracks.play"), Wt::XHTMLText);
container->bindWidget("play-btn", playBtn);
Wt::WText* playBtn = bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.Tracks.play"), Wt::TextFormat::XHTML);
playBtn->clicked().connect(std::bind([=]
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksPlay.emit(getTracks());
}));
auto addBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Tracks.add"), Wt::XHTMLText);
container->bindWidget("add-btn", addBtn);
Wt::WText* addBtn = bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.Tracks.add"), Wt::TextFormat::XHTML);
addBtn->clicked().connect(std::bind([=]
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
tracksAdd.emit(getTracks());
}));
_tracksContainer = new Wt::WContainerWidget();
container->bindWidget("tracks", _tracksContainer);
_tracksContainer = bindNew<Wt::WContainerWidget>("tracks");
_showMore = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.template.show-more"));
_showMore = bindNew<Wt::WTemplate>("show-more", Wt::WString::tr("Lms.Explore.template.show-more"));
_showMore->addFunction("tr", &Wt::WTemplate::Functions::tr);
container->bindWidget("show-more", _showMore);
_showMore->clicked().connect(std::bind([=]
{
addSome();
@@ -88,9 +81,9 @@ Tracks::getTracks(int offset, int size, bool& moreResults)
auto searchKeywords = splitString(_search->text().toUTF8(), " ");
auto clusterIds = _filters->getClusterIds();
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
return Track::getByFilter(DboSession(), clusterIds, searchKeywords, offset, size, moreResults);
return Track::getByFilter(LmsApp->getDboSession(), clusterIds, searchKeywords, offset, size, moreResults);
}
std::vector<Database::Track::pointer>
@@ -110,7 +103,7 @@ Tracks::refresh()
void
Tracks::addSome()
{
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::Transaction transaction(LmsApp->getDboSession());
bool moreResults;
auto tracks = getTracks(_tracksContainer->count(), 20, moreResults);
@@ -118,41 +111,35 @@ Tracks::addSome()
for (auto track : tracks)
{
auto trackId = track.id();
Wt::WTemplate* entry = new Wt::WTemplate(Wt::WString::tr("Lms.Explore.Tracks.template.entry"), _tracksContainer);
Wt::WTemplate* entry = _tracksContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Tracks.template.entry"));
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::PlainText);
entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain);
auto artist = track->getArtist();
if (artist)
{
entry->setCondition("if-has-artist", true);
Wt::WAnchor *artistAnchor = LmsApplication::createArtistAnchor(track->getArtist());
entry->bindWidget("artist-name", artistAnchor);
entry->bindWidget("artist-name", LmsApplication::createArtistAnchor(track->getArtist()));
}
auto release = track->getRelease();
if (release)
{
entry->setCondition("if-has-release", true);
Wt::WAnchor *releaseAnchor = LmsApplication::createReleaseAnchor(track->getRelease());
entry->bindWidget("release-name", releaseAnchor);
entry->bindWidget("release-name", LmsApplication::createReleaseAnchor(track->getRelease()));
}
Wt::WImage *cover = new Wt::WImage();
cover->setImageLink(LmsApp->getImageResource()->getTrackUrl(track.id(), 64));
Wt::WImage* cover = entry->bindNew<Wt::WImage>("cover", LmsApp->getImageResource()->getTrackUrl(track.id(), 64));
// Some images may not be square
cover->setWidth(64);
entry->bindWidget("cover", cover);
auto playBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Tracks.play"), Wt::XHTMLText);
entry->bindWidget("play-btn", playBtn);
Wt::WText* playBtn = entry->bindNew<Wt::WText>("play-btn", Wt::WString::tr("Lms.Explore.Tracks.play"), Wt::TextFormat::XHTML);
playBtn->clicked().connect(std::bind([=]
{
trackPlay.emit(trackId);
}));
auto addBtn = new Wt::WText(Wt::WString::tr("Lms.Explore.Tracks.add"), Wt::XHTMLText);
entry->bindWidget("add-btn", addBtn);
Wt::WText* addBtn = entry->bindNew<Wt::WText>("add-btn", Wt::WString::tr("Lms.Explore.Tracks.add"), Wt::TextFormat::XHTML);
addBtn->clicked().connect(std::bind([=]
{
trackAdd.emit(trackId);
+5 -5
View File
@@ -19,17 +19,17 @@
#pragma once
#include <Wt/WContainerWidget>
#include <Wt/WLineEdit>
#include <Wt/WTemplate>
#include <Wt/WContainerWidget.h>
#include <Wt/WLineEdit.h>
#include <Wt/WTemplate.h>
namespace UserInterface {
class Filters;
class Tracks : public Wt::WContainerWidget
class Tracks : public Wt::WTemplate
{
public:
Tracks(Filters* filters, Wt::WContainerWidget* parent = 0);
Tracks(Filters* filters);
Wt::Signal<Database::id_type> trackAdd;
Wt::Signal<Database::id_type> trackPlay;
+17 -22
View File
@@ -17,8 +17,8 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/WApplication>
#include <Wt/Http/Response>
#include <Wt/WApplication.h>
#include <Wt/Http/Response.h>
#include "utils/Logger.hpp"
#include "utils/Utils.hpp"
@@ -34,9 +34,8 @@ namespace UserInterface {
static const std::string unknownCoverPath = "/images/unknown-cover.jpg";
static const std::string unknownArtistImagePath = "/images/unknown-artist.jpg";
ImageResource::ImageResource(Database::Handler& db, Wt::WObject *parent)
: Wt::WResource(parent),
_db(db)
ImageResource::ImageResource(Database::Handler& db)
: _db(db)
{
}
@@ -154,14 +153,14 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
if (!sizeStr)
return;
std::size_t size;
if (!readAs(*sizeStr, size) || size > maxSize)
auto size = readAs<std::size_t>(*sizeStr);
if (!size || *size > maxSize)
return;
if (trackIdStr)
{
Database::Track::id_type trackId;
if (!readAs(*trackIdStr, trackId))
auto trackId = readAs<Database::Track::id_type>(*trackIdStr);
if (!trackId)
return;
boost::filesystem::path path;
@@ -172,7 +171,7 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
Wt::Dbo::Transaction transaction(_db.getSession());
Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId);
Database::Track::pointer track = Database::Track::getById(_db.getSession(), *trackId);
if (track)
{
coverType = track->getCoverType();
@@ -195,17 +194,16 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
break;
}
putCover(response, covers, size);
putCover(response, covers, *size);
return;
}
putImage(response, getDefaultCover(size));
return;
putImage(response, getDefaultCover(*size));
}
else if (releaseIdStr)
{
Database::Release::id_type releaseId;
if (!readAs(*releaseIdStr, releaseId))
auto releaseId = readAs<Database::Release::id_type>(*releaseIdStr);
if (!releaseId)
return;
std::vector<Image::Image> covers;
@@ -213,21 +211,18 @@ ImageResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
// transactions are not thread safe
{
Wt::WApplication::UpdateLock lock(LmsApplication::instance());
covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), releaseId);
covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), *releaseId);
}
putCover(response, covers, size);
return;
putCover(response, covers, *size);
}
else if (artistIdStr)
{
putImage(response, getDefaultArtistImage(size));
return;
putImage(response, getDefaultArtistImage(*size));
}
else
{
putImage(response, getDefaultCover(size));
return;
putImage(response, getDefaultCover(*size));
}
}
+2 -2
View File
@@ -22,7 +22,7 @@
#include <mutex>
#include <Wt/WResource>
#include <Wt/WResource.h>
#include "database/DatabaseHandler.hpp"
#include "image/Image.hpp"
@@ -35,7 +35,7 @@ class ImageResource : public Wt::WResource
public:
static const std::size_t maxSize = 512;
ImageResource(Database::Handler& db, Wt::WObject *parent = 0);
ImageResource(Database::Handler& db);
~ImageResource();
std::string getReleaseUrl(Database::Release::id_type releaseId, size_t size) const;
+3 -4
View File
@@ -17,7 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Wt/Http/Response>
#include <Wt/Http/Response.h>
#include "utils/Logger.hpp"
@@ -27,9 +27,8 @@
namespace UserInterface {
TranscodeResource::TranscodeResource(Database::Handler& db, Wt::WObject *parent)
: Wt::WResource(parent),
_db(db)
TranscodeResource::TranscodeResource(Database::Handler& db)
: _db(db)
{
LMS_LOG(UI, DEBUG) << "CONSTRUCTING RESOURCE";
}
+2 -2
View File
@@ -21,7 +21,7 @@
#include <mutex>
#include <Wt/WResource>
#include <Wt/WResource.h>
#include "av/AvTranscoder.hpp"
@@ -32,7 +32,7 @@ namespace UserInterface {
class TranscodeResource : public Wt::WResource
{
public:
TranscodeResource(Database::Handler& db, Wt::WObject *parent);
TranscodeResource(Database::Handler& db);
~TranscodeResource();
std::string getUrl(Database::Track::id_type trackId, Av::Encoding encoding, boost::posix_time::time_duration offset) const;
+2 -4
View File
@@ -19,12 +19,10 @@
#pragma once
#include <Wt/WServer>
#include <Wt/WApplication>
#include <Wt/WLogger>
#include <string>
#include <Wt/WApplication.h>
#include <Wt/WLogger.h>
enum class Severity
{
+1 -1
View File
@@ -73,7 +73,7 @@ void computeCrc(const boost::filesystem::path& p, std::vector<unsigned char>& cr
}
else
{
LMS_LOG(DBUPDATER, ERROR) << "Failed to open file '" << p << "'";
LMS_LOG(DBUPDATER, ERROR) << "Failed to open file '" << p.string() << "'";
throw std::runtime_error("Failed to open file '" + p.string() + "'" );
}
+20 -41
View File
@@ -19,6 +19,7 @@
#include <string>
#include <sstream>
#include <iomanip>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/algorithm/string/split.hpp>
@@ -26,42 +27,28 @@
#include "Utils.hpp"
boost::optional<long>
readLong(const std::string& str)
template<>
boost::optional<Wt::WDate> readAs(const std::string& str)
{
try
{
return std::stol(str);
}
catch (std::exception& e)
{
return boost::none;
}
}
bool readAsPosixTime(const std::string& str, boost::posix_time::ptime& time)
{
const std::locale formats[] = {
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%m-%d")),
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%b-%d")),
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%B-%d")),
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y/%m/%d")),
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%d.%m.%Y")),
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y-%m")),
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y/%m")),
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y.%m")),
std::locale(std::locale::classic(), new boost::posix_time::time_input_facet("%Y")),
const std::vector<std::string> formats = {
"yyyy-MM-dd",
"yyyy/MM/dd",
"yyyy-MM",
"yyyy/MM",
"yyyy"
};
for(size_t i=0; i < sizeof(formats)/sizeof(formats[0]); ++i)
for (auto format : formats)
{
std::istringstream iss(str);
iss.imbue(formats[i]);
if (iss >> time)
return true;
auto date = Wt::WDate::fromString(str, format);
if (!date.isValid())
continue;
return date;
}
return false;
return boost::none;
}
bool readList(const std::string& str, const std::string& separators, std::list<std::string>& results)
@@ -72,7 +59,7 @@ bool readList(const std::string& str, const std::string& separators, std::list<s
{
if (separators.find(c) != std::string::npos) {
if (!curStr.empty()) {
results.push_back(stringToUTF8(curStr));
results.push_back(curStr);
curStr.clear();
}
}
@@ -85,7 +72,7 @@ bool readList(const std::string& str, const std::string& separators, std::list<s
}
if (!curStr.empty())
results.push_back(stringToUTF8(curStr));
results.push_back(curStr);
return !str.empty();
}
@@ -131,14 +118,6 @@ stringTrimEnd(const std::string& str, const std::string& whitespace)
return str.substr(0, str.find_last_not_of(whitespace)+1);
}
std::string
stringToUTF8(const std::string& str)
{
return boost::locale::conv::to_utf<char>(str, "UTF-8");
}
std::string
bufferToString(const std::vector<unsigned char>& data)
{
+19 -18
View File
@@ -19,26 +19,21 @@
#pragma once
#include <string>
#include <vector>
#include <chrono>
#include <list>
#include <string>
#include <sstream>
#include <vector>
#include <boost/locale.hpp>
#include <boost/optional.hpp>
#include <boost/date_time/posix_time/posix_time_types.hpp>
boost::optional<long>
readLong(const std::string& str);
bool
readAsPosixTime(const std::string& str, boost::posix_time::ptime& time);
#include <Wt/WDate.h>
bool
readList(const std::string& str, const std::string& separators, std::list<std::string>& results);
std::string
durationToString(boost::posix_time::time_duration duration, std::string format);
//std::string
//durationToString(boost::posix_time::time_duration duration, std::string format);
std::vector<std::string>
splitString(std::string string, std::string separators);
@@ -49,17 +44,23 @@ stringTrim(const std::string& str, const std::string& whitespaces = " \t");
std::string
stringTrimEnd(const std::string& str, const std::string& whitespaces = " \t");
std::string
stringToUTF8(const std::string& str);
std::string
bufferToString(const std::vector<unsigned char>& data);
template<typename T>
static inline bool readAs(const std::string& str, T& data)
boost::optional<T> readAs(const std::string& str)
{
T res;
std::istringstream iss ( str );
iss >> data;
return !iss.fail();
iss >> res;
if (iss.fail())
return boost::none;
return res;
}
template<>
boost::optional<Wt::WDate> readAs(const std::string& str);
+7 -7
View File
@@ -1,11 +1,11 @@
#include <stdlib.h>
#include <boost/date_time/time_duration.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <chrono>
#include <stdexcept>
#include <iostream>
#include <Wt/WDate.h>
#include "av/AvInfo.hpp"
#include "metadata/AvFormat.hpp"
#include "metadata/TagLibParser.hpp"
@@ -67,7 +67,7 @@ int main(int argc, char *argv[])
break;
case MetaData::Type::Duration:
std::cout << "Duration: " << boost::posix_time::to_simple_string(boost::any_cast<boost::posix_time::time_duration>(item.second)) << std::endl;
std::cout << "Duration: " << boost::any_cast<std::chrono::milliseconds>(item.second).count() / 1000 << "s" << std::endl;
break;
case MetaData::Type::TrackNumber:
@@ -87,11 +87,11 @@ int main(int argc, char *argv[])
break;
case MetaData::Type::Date:
std::cout << "Date: " << boost::posix_time::to_simple_string(boost::any_cast<boost::posix_time::ptime>(item.second)) << std::endl;
std::cout << "Date: " << boost::any_cast<Wt::WDate>(item.second).toString() << std::endl;
break;
case MetaData::Type::OriginalDate:
std::cout << "Original date: " << boost::posix_time::to_simple_string(boost::any_cast<boost::posix_time::ptime>(item.second)) << std::endl;
std::cout << "Original date: " << boost::any_cast<Wt::WDate>(item.second).toString() << std::endl;
break;
case MetaData::Type::HasCover:
@@ -100,7 +100,7 @@ int main(int argc, char *argv[])
case MetaData::Type::AudioStreams:
for (auto& audioStream : boost::any_cast<std::vector<MetaData::AudioStream> >(item.second))
std::cout << "Audio stream '" << audioStream.desc << "' - " << audioStream.bitRate << " bps" << std::endl;
std::cout << "Audio stream: " << audioStream.bitRate << " bps" << std::endl;
break;
case MetaData::Type::MusicBrainzArtistID: