Merge branch 'develop'

This commit is contained in:
emeric
2015-08-06 12:37:01 +02:00
40 changed files with 406 additions and 419 deletions
+23 -15
View File
@@ -1,19 +1,12 @@
[ServiceManager]
- Rework the whole start/stop/try/cach/thread/interrupts things
- Use our own WIOService
[Services]
- consider adding support for external web servers (new FCGI service?)
[Users]
- Handle login lifetime?
- Limit multi login from UI and remote interfaces (limit per interface is acceptable)
[Book]
- Make the feature
[Cover]
- Scaling: find something more "reliable" than GIL and its custom extensions (adobe work, io_new)?
- Handle several file formats (not only jpg)
- Handle preferred cover file names
- Implement a cache and a grabber from some web service (mandatory for artists)
[Database]
- Optim, use SQL query to get the "genre" orphans
@@ -24,8 +17,22 @@
[Metadata]
- OGG metadata -> properly handle metadata nested in the audio stream
[Playlist]
- Make public playlists so that users can see what other people are listening to
[Users]
- Handle login lifetime?
- Limit multi login from UI and remote interfaces (limit per interface is acceptable)
[ServiceManager]
- Rework the whole start/stop/try/cach/thread/interrupts things
- Use our own WIOService
[Services]
- consider adding support for external web servers (new FCGI service?)
[Transcode]
- some early end of playback spotted on flac files. Spotted on old firefox versions?
- some early end of playback spotted on files. Maybe this is because the stderr of the forked process is not properly closed?
[UI]
- handle internationalization
@@ -44,9 +51,10 @@
[Audio]
- Style eveything nicely...
- MediaPlayer: move the slider using js (http://redmine.webtoolkit.eu/boards/2/topics/7924?r=8478)
- TrackView : handle durations > 1 hour
- TrackView : Reselect the current selected item when displaying the updated search results
- MediaPlayer: move the slider using js (http://redmine.webtoolkit.eu/boards/2/topics/7924?r=8478, http://redmine.emweb.be/boards/2/topics/10994)
- TrackView: handle durations > 1 hour
- TrackView: Reselect the current selected item when displaying the updated search results
- TrackView: display the total duration of the track query
- Add covers in the release filter?
- Add a download button to get the current playlist in a streamed zip file
- Add a upload button to upload media files in a dedicated directory
@@ -65,7 +73,7 @@
- Implement partial text search options in the GetXXX messages
[REST API]
- Make another dedicated REST API. Maybe use the SubSonic API?
- Make another dedicated REST API. Maybe use the SubSonic API or Ampache API?
[Logs]
- Capture transcoder log output to get more information on errors
+9 -8
View File
@@ -2,9 +2,14 @@
main = {
logger = {
file = "/var/lms/lms.log" # comment to disable file logging
console = true;
level = 7;
level = 7; # level common for all loggers
file = {
enable = true;
path = "/var/lms/lms.log"; # comment to disable file logging
}
console = {
enable = true;
}
}
database = {
@@ -16,15 +21,13 @@ main = {
cover = {
file_extensions = "jpg jpeg";
file_max_size = 500000;
file_max_size = 5000000;
file_preferred_names = "cover front";
}
}
ui = {
enable = true;
resources = {
docroot = "/var/lms/docroot"
approot = "/var/lms/approot"
@@ -44,8 +47,6 @@ ui = {
}
remote = {
enable = true;
nb-threads = 1;
listen-endpoint = {
View File
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
import "common.proto";
package LmsAPI;
message AuthRequest
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
import "common.proto";
package LmsAPI;
message AudioCollectionRequest
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
import "common.proto";
package LmsAPI;
message MediaRequest
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
import "common.proto";
import "auth.proto";
import "collection.proto";
import "media.proto";
+2 -8
View File
@@ -81,8 +81,6 @@ nodist_lms_SOURCES = \
$(builddir)/auth.pb.h \
$(builddir)/collection.pb.cc \
$(builddir)/collection.pb.h \
$(builddir)/common.pb.cc \
$(builddir)/common.pb.h \
$(builddir)/media.pb.cc \
$(builddir)/media.pb.h \
$(builddir)/messages.pb.cc \
@@ -93,8 +91,6 @@ BUILT_SOURCES = \
$(builddir)/auth.pb.h \
$(builddir)/collection.pb.cc \
$(builddir)/collection.pb.h \
$(builddir)/common.pb.cc \
$(builddir)/common.pb.h \
$(builddir)/media.pb.cc \
$(builddir)/media.pb.h \
$(builddir)/messages.pb.cc \
@@ -105,15 +101,13 @@ MOSTLYCLEANFILES = \
$(builddir)/auth.pb.h \
$(builddir)/collection.pb.cc \
$(builddir)/collection.pb.h \
$(builddir)/common.pb.cc \
$(builddir)/common.pb.h \
$(builddir)/media.pb.cc \
$(builddir)/media.pb.h \
$(builddir)/messages.pb.cc \
$(builddir)/messages.pb.h
%.pb.cc %.pb.h: $(srcdir)/lms-api/proto/%.proto
$(PROTOC) --proto_path=$(srcdir)/lms-api/proto/ --cpp_out=$(builddir)/ $^
%.pb.cc %.pb.h: $(top_srcdir)/lms-api/proto/%.proto
$(PROTOC) --proto_path=$(top_srcdir)/lms-api/proto/ --cpp_out=$(builddir)/ $^
endif
+31 -68
View File
@@ -23,89 +23,52 @@
namespace {
void splitStrings(const std::string& source, std::vector<std::string>& res)
{
std::istringstream oss(source);
std::string str;
while(oss >> str)
res.push_back(str);
}
}
ConfigReader::ConfigReader(boost::filesystem::path p)
ConfigReader::ConfigReader()
: _config (nullptr)
{
_config.readFile(p.string().c_str());
}
ConfigReader&
ConfigReader::instance()
{
static ConfigReader instance;
return instance;
}
void
ConfigReader::getLoggerConfig(Logger::Config& config)
ConfigReader::setFile(boost::filesystem::path p)
{
config.enableFileLogging = _config.lookupValue("main.logger.file", config.logPath);
config.enableConsoleLogging = _config.lookup("main.logger.console");
config.minSeverity = static_cast<Severity>((int)_config.lookup("main.logger.level"));
if (_config != nullptr)
delete _config;
_config = new libconfig::Config();
_config->readFile(p.string().c_str());
}
void
ConfigReader::getCoverGrabberConfig(CoverArt::Grabber::Config& config)
std::string
ConfigReader::getString(std::string setting)
{
std::string extensions = _config.lookup("main.cover.file_extensions");
config.maxFileSize = static_cast<unsigned int>(_config.lookup("main.cover.file_max_size"));
std::string filenames = _config.lookup("main.cover.file_preferred_names");
splitStrings(extensions, config.fileExtensions);
splitStrings(filenames, config.preferredFileNames);
return _config->lookup(setting);
}
void
ConfigReader::getUserInterfaceConfig(Service::UserInterfaceService::Config& config)
unsigned long
ConfigReader::getULong(std::string setting)
{
config.enable = _config.lookup("ui.enable");
if (!config.enable)
return;
config.docRootPath = _config.lookup("ui.resources.docroot");
config.appRootPath = _config.lookup("ui.resources.approot");
config.httpsPort = static_cast<unsigned int>(_config.lookup("ui.listen-endpoint.port"));
config.httpsAddress = boost::asio::ip::address::from_string((const char*)_config.lookup("ui.listen-endpoint.addr"));
config.sslCertificatePath = _config.lookup("ui.ssl-crypto.cert");
config.sslPrivateKeyPath = _config.lookup("ui.ssl-crypto.key");
config.sslTempDhPath = _config.lookup("ui.ssl-crypto.dh");
config.dbPath = _config.lookup("main.database.path");
return static_cast<unsigned int>(_config->lookup(setting));
}
#if defined HAVE_LMSAPI
void
ConfigReader::getLmsAPIConfig(Service::LmsAPIService::Config& config)
long
ConfigReader::getLong(std::string setting)
{
config.enable = _config.lookup("remote.enable");
if (!config.enable)
return;
config.port = static_cast<unsigned int>(_config.lookup("remote.listen-endpoint.port"));
config.address = boost::asio::ip::address::from_string((const char*)_config.lookup("remote.listen-endpoint.addr"));
config.sslCertificatePath = _config.lookup("remote.ssl-crypto.cert");
config.sslPrivateKeyPath = _config.lookup("remote.ssl-crypto.key");
config.sslTempDhPath = _config.lookup("remote.ssl-crypto.dh");
config.dbPath = _config.lookup("main.database.path");
}
#endif
void
ConfigReader::getDatabaseUpdateConfig(Service::DatabaseUpdateService::Config& config)
{
config.enable = true;
config.dbPath = _config.lookup("main.database.path");
std::string audioExtensions = _config.lookup("main.database.audio_extensions");
std::string videoExtensions = _config.lookup("main.database.video_extensions");
splitStrings(audioExtensions, config.audioExtensions);
splitStrings(videoExtensions, config.videoExtensions);
return _config->lookup(setting);
}
bool
ConfigReader::getBool(std::string setting)
{
return _config->lookup(setting);
}
+11 -24
View File
@@ -23,40 +23,27 @@
#include <boost/filesystem.hpp>
#include <libconfig.h++>
#include "config/config.h"
#include "cover/CoverArtGrabber.hpp"
#include "logger/Logger.hpp"
#include "service/UserInterfaceService.hpp"
#include "service/DatabaseUpdateService.hpp"
#if defined HAVE_LMSAPI
#include "service/LmsAPIServerService.hpp"
#endif
class ConfigReader
{
public:
ConfigReader(boost::filesystem::path p);
ConfigReader(const ConfigReader&) = delete;
ConfigReader& operator=(const ConfigReader&) = delete;
// Logger configuration
void getLoggerConfig(Logger::Config& config);
static ConfigReader& instance();
// Covers
void getCoverGrabberConfig(CoverArt::Grabber::Config& config);
void setFile(boost::filesystem::path p);
// Service configurations
void getUserInterfaceConfig(Service::UserInterfaceService::Config& config);
void getDatabaseUpdateConfig(Service::DatabaseUpdateService::Config& config);
#if defined HAVE_LMSAPI
void getLmsAPIConfig(Service::LmsAPIService::Config& config);
#endif
std::string getString(std::string setting);
unsigned long getULong(std::string setting);
long getLong(std::string setting);
bool getBool(std::string setting);
private:
libconfig::Config _config;
ConfigReader();
libconfig::Config *_config;
};
#endif
+17 -7
View File
@@ -17,10 +17,8 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include "logger/Logger.hpp"
#include "config/ConfigReader.hpp"
#include "av/InputFormatContext.hpp"
#include "CoverArtGrabber.hpp"
@@ -28,6 +26,18 @@
namespace {
std::vector<std::string> splitStrings(const std::string& source)
{
std::vector<std::string> res;
std::istringstream oss(source);
std::string str;
while(oss >> str)
res.push_back(str);
return res;
}
bool
isFileSupported(const boost::filesystem::path& file, const std::vector<boost::filesystem::path> extensions)
{
@@ -58,12 +68,12 @@ Grabber::instance()
}
void
Grabber::init(const Config& config)
Grabber::init()
{
for (auto extension : config.fileExtensions)
for (const std::string& extension : splitStrings( ConfigReader::instance().getString("main.cover.file_extensions")))
_fileExtensions.push_back("." + extension);
_maxFileSize = config.maxFileSize;
_maxFileSize = ConfigReader::instance().getULong("main.cover.file_max_size");
}
std::vector<CoverArt>
@@ -75,7 +85,7 @@ Grabber::getFromInputFormatContext(const Av::InputFormatContext& input, std::siz
{
std::vector<Av::Picture> pictures = input.getPictures(nbMaxCovers);
BOOST_FOREACH(const Av::Picture& picture, pictures)
for (Av::Picture& picture : pictures)
res.push_back( CoverArt(picture.mimeType, picture.data) );
}
+1 -8
View File
@@ -38,14 +38,7 @@ class Grabber
static Grabber& instance();
struct Config
{
std::vector<std::string> fileExtensions;
std::size_t maxFileSize;
std::vector<std::string> preferredFileNames;
};
void init(const Config& config);
void init();
std::vector<boost::filesystem::path> getCoverPaths(const boost::filesystem::path& directoryPath, std::size_t nbMaxCovers = 1) const;
std::vector<CoverArt> getFromDirectory(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const;
+27 -16
View File
@@ -493,6 +493,7 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats)
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() );
{
std::string trackGenreList;
@@ -630,19 +631,15 @@ void
Updater::checkAudioFiles( Stats& stats )
{
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking audio files...";
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Checking audio files...";
Wt::Dbo::Transaction transaction(_db.getSession());
std::vector<boost::filesystem::path> rootDirs = getRootDirectoriesByType(_db.getSession(), Database::MediaDirectory::Audio);
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking tracks...";
typedef Wt::Dbo::collection< Wt::Dbo::ptr<Track> > Tracks;
Tracks tracks = Track::getAll(_db.getSession());
for (Tracks::iterator it = tracks.begin(); it != tracks.end(); ++it)
auto tracks = Track::getAll(_db.getSession());
for (auto track : tracks)
{
Track::pointer track = (*it);
if (!checkFile(track->getPath(), rootDirs, _audioExtensions))
{
track.remove();
@@ -651,20 +648,34 @@ Updater::checkAudioFiles( Stats& stats )
}
// Now process orphan Genre (no track)
/* LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Genres...";
typedef Wt::Dbo::collection< Wt::Dbo::ptr<Genre> > Genres;
Genres genres = Genre::getAll(_db.getSession());
for (Genres::iterator it = genres.begin(); it != genres.end(); ++it)
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Genres...";
auto genres = Genre::getAll(_db.getSession());
for (auto genre : genres)
{
Genre::pointer genre = (*it);
if (genre->getTracks().size() == 0)
{
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan genre '" << genre->getName() << "'";
genre.remove();
}
}
*/
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Check audio files done!";
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking artists...";
auto artists = Artist::getAllOrphans(_db.getSession());
for (auto artist : artists)
{
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan artist '" << artist->getName() << "'";
artist.remove();
}
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking releases...";
auto releases = Release::getAllOrphans(_db.getSession());
for (auto release : releases)
{
LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Removing orphan release '" << release->getName() << "'";
release.remove();
}
LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Check audio files done!";
}
void
+12
View File
@@ -84,6 +84,18 @@ Artist::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset,
return std::vector<pointer>(res.begin(), res.end());
}
std::vector<Wt::Dbo::ptr<Release> >
Artist::getReleases() const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Artist>::invalidId() );
assert(session());
Wt::Dbo::collection< Wt::Dbo::ptr<Release> > res = session()->query<Wt::Dbo::ptr<Release> >("SELECT r FROM release r INNER JOIN artist a ON t.artist_id = a.id INNER JOIN track t ON t.release_id = r.id").where("a.id = ?").bind(id());
return std::vector< Wt::Dbo::ptr<Release> > (res.begin(), res.end());
}
Wt::Dbo::Query<Artist::pointer>
Artist::getQuery(Wt::Dbo::Session& session, SearchFilter filter)
{
+5 -1
View File
@@ -33,8 +33,9 @@ namespace Database
class Track;
class Genre;
class Release;
class Artist
class Artist : public Wt::Dbo::Dbo<Artist>
{
public:
@@ -58,6 +59,9 @@ class Artist
std::string getName(void) const { return _name; }
std::string getMBID(void) const { return _MBID; }
// Get the releases that have at least one track for this artist
std::vector<Wt::Dbo::ptr<Release> > getReleases() const;
void setMBID(std::string mbid) { _MBID = mbid; }
// Create
+13 -1
View File
@@ -71,7 +71,7 @@ Release::getAll(Wt::Dbo::Session& session, int offset, int size)
std::vector<Release::pointer>
Release::getAllOrphans(Wt::Dbo::Session& session)
{
Wt::Dbo::collection<Release::pointer> res = session.query< Wt::Dbo::ptr<Release> >("select a from artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL");
Wt::Dbo::collection<Release::pointer> res = session.query< Wt::Dbo::ptr<Release> >("select r from release r LEFT OUTER JOIN Track t ON r.id = t.release_id WHERE t.id IS NULL");
return std::vector<pointer>(res.begin(), res.end());
}
@@ -129,4 +129,16 @@ Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset,
return std::vector<pointer>(res.begin(), res.end());
}
std::vector< Wt::Dbo::ptr<Artist> >
Release::getArtists() const
{
assert(self());
assert(self()->id() != Wt::Dbo::dbo_traits<Release>::invalidId() );
assert(session());
Wt::Dbo::collection< Wt::Dbo::ptr<Artist> > res = session()->query<Wt::Dbo::ptr<Artist> >("SELECT a FROM artist a INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.release_id = r.id").where("r.id = ?").bind(id());
return std::vector< Wt::Dbo::ptr<Artist> > (res.begin(), res.end());
}
} // namespace Database
+4 -1
View File
@@ -29,8 +29,9 @@ namespace Database
{
class Track;
class Release;
class Release
class Release : public Wt::Dbo::Dbo<Release>
{
public:
@@ -64,6 +65,8 @@ class Release
std::string getMBID() const { return _MBID; }
bool isNone(void) const;
boost::posix_time::time_duration getDuration(void) const;
std::vector<Wt::Dbo::ptr<Artist> > getArtists() const; // Get the artists of this release
std::vector<Wt::Dbo::ptr<Track> > getTracks() const; // Get the tracks of this release
void setMBID(std::string mbid) { _MBID = mbid; }
+17 -4
View File
@@ -113,6 +113,20 @@ Track::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter)
return query;
}
Track::StatsQueryResult
Track::getStats(Wt::Dbo::Session& session, SearchFilter filter)
{
SqlQuery sqlQuery = generatePartialQuery(filter);
Wt::Dbo::Query<StatsQueryResult> query = session.query<StatsQueryResult>( "SELECT COUNT(DISTINCT t.id), SUM(t.duration) FROM track t INNER JOIN artist a ON t.artist_id = a.id INNER JOIN genre g ON g.id = t_g.genre_id INNER JOIN track_genre t_g ON t_g.track_id = t.id INNER JOIN release r ON r.id = t.release_id " + sqlQuery.where().get());
for (const std::string& bindArg : sqlQuery.where().getBindArgs())
query.bind(bindArg);
return query;
}
std::vector<Track::pointer>
Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size)
{
@@ -152,11 +166,10 @@ Genre::Genre(const std::string& name)
{
}
std::vector<Genre::pointer>
Genre::getAll(Wt::Dbo::Session& session, int offset, int size)
Wt::Dbo::collection<Genre::pointer>
Genre::getAll(Wt::Dbo::Session& session)
{
Wt::Dbo::collection<pointer> res = session.find<Genre>().offset(offset).limit(size);
return std::vector<Genre::pointer>(res.begin(), res.end());
return session.find<Genre>();
}
Genre::pointer
+9 -4
View File
@@ -52,7 +52,7 @@ class Genre
static pointer getByName(Wt::Dbo::Session& session, const std::string& name);
static pointer getNone(Wt::Dbo::Session& session);
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, int offset = -1, int size = -1);
static Wt::Dbo::collection<pointer> getAll(Wt::Dbo::Session& session);
// MVC models for the user interface
// Genre ID, name, track count
@@ -105,10 +105,8 @@ class Track
static pointer getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p);
static pointer getById(Wt::Dbo::Session& session, id_type id);
static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID);
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session);
// Used for remote
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1);
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session);
// Utility fonctions
// MVC models for the user interface
@@ -127,6 +125,13 @@ class Track
static Wt::Dbo::Query< UIQueryResult > getUIQuery(Wt::Dbo::Session& session, SearchFilter filter);
static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector<Wt::WString>& columnNames = std::vector<Wt::WString>());
// 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);
-27
View File
@@ -1,27 +0,0 @@
/*
* Copyright (C) 2013 Emeric Poupon
*
* This file is part of LMS.
*
* LMS is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* LMS is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
package LmsAPI;
message Error
{
required bool error = 1;
optional string message = 2;
}
+8 -9
View File
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include <boost/log/core.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/expressions.hpp>
@@ -37,6 +35,8 @@
#include <boost/date_time/posix_time/ptime.hpp>
#include "config/ConfigReader.hpp"
#include "Logger.hpp"
@@ -64,7 +64,7 @@ Logger::Logger()
MOD_UI,
};
BOOST_FOREACH(Module module, modules)
for(Module module : modules)
_loggers[module].add_attribute("Module", boost::log::attributes::constant< Module >(module));
}
@@ -74,19 +74,18 @@ Logger::get(Module module)
return _loggers[module];
}
void
Logger::init(const Config& config)
Logger::init()
{
boost::log::add_common_attributes();
boost::log::register_simple_formatter_factory< Severity, char >("Severity");
if (config.enableFileLogging)
if (ConfigReader::instance().getBool("main.logger.file.enable"))
{
boost::log::add_file_log
(
boost::log::keywords::file_name = config.logPath + std::string(".%N"),
boost::log::keywords::file_name = ConfigReader::instance().getString("main.logger.file.path") + std::string(".%N"),
boost::log::keywords::rotation_size = 10 * 1024 * 1024,
boost::log::keywords::open_mode = std::ios_base::app,
boost::log::keywords::auto_flush = true,
@@ -100,7 +99,7 @@ Logger::init(const Config& config)
);
}
if (config.enableConsoleLogging)
if (ConfigReader::instance().getBool("main.logger.console.enable"))
{
boost::log::add_console_log(std::cout,
boost::log::keywords::format = (
@@ -115,7 +114,7 @@ Logger::init(const Config& config)
boost::log::core::get()->set_filter
(
boost::log::expressions::attr<Severity>("Severity") <= config.minSeverity
boost::log::expressions::attr<Severity>("Severity") <= ConfigReader::instance().getULong("main.logger.level")
);
}
+1 -8
View File
@@ -65,15 +65,8 @@ class Logger
static Logger& instance();
struct Config {
bool enableFileLogging;
bool enableConsoleLogging;
std::string logPath;
Severity minSeverity;
};
//[ example_tutorial_file_advanced
void init(const Config& config);
void init();
boost::log::sources::severity_logger< Severity >&
get(Module module);
+8 -34
View File
@@ -20,11 +20,11 @@
#include <boost/filesystem.hpp>
#include "config/config.h"
#include "logger/Logger.hpp"
#include "config/ConfigReader.hpp"
#include "transcode/AvConvTranscoder.hpp"
#include "av/Common.hpp"
#include "logger/Logger.hpp"
#include "cover/CoverArtGrabber.hpp"
#include "service/ServiceManager.hpp"
#include "service/DatabaseUpdateService.hpp"
@@ -61,28 +61,10 @@ int main(int argc, char* argv[])
return EXIT_FAILURE;
}
ConfigReader configReader(configFile);
ConfigReader::instance().setFile(configFile);
// Initializa logging facility
{
Logger::Config loggerConfig;
configReader.getLoggerConfig(loggerConfig);
Logger::instance().init(loggerConfig);
}
{
CoverArt::Grabber::Config config;
configReader.getCoverGrabberConfig(config);
CoverArt::Grabber::instance().init(config);
}
LMS_LOG(MOD_MAIN, SEV_INFO) << "Reading service configurations...";
Service::DatabaseUpdateService::Config dbUpdateConfig;
configReader.getDatabaseUpdateConfig(dbUpdateConfig);
Service::UserInterfaceService::Config uiConfig;
configReader.getUserInterfaceConfig(uiConfig);
Logger::instance().init();
CoverArt::Grabber::instance().init();
Service::ServiceManager& serviceManager = Service::ServiceManager::instance();
@@ -93,20 +75,12 @@ int main(int argc, char* argv[])
LMS_LOG(MOD_MAIN, SEV_INFO) << "Starting services...";
if (dbUpdateConfig.enable)
serviceManager.startService( std::make_shared<Service::DatabaseUpdateService>( dbUpdateConfig ) );
serviceManager.startService( std::make_shared<Service::DatabaseUpdateService>() );
serviceManager.startService( std::make_shared<Service::UserInterfaceService>(boost::filesystem::path(argv[0])));
#if defined HAVE_LMSAPI
Service::LmsAPIService::Config lmsAPIConfig;
configReader.getLmsAPIConfig(lmsAPIConfig);
if (lmsAPIConfig.enable)
serviceManager.startService( std::make_shared<Service::LmsAPIService>( lmsAPIConfig ));
serviceManager.startService( std::make_shared<Service::LmsAPIService>( ));
#endif
if (uiConfig.enable)
serviceManager.startService( std::make_shared<Service::UserInterfaceService>(boost::filesystem::path(argv[0]), uiConfig));
LMS_LOG(MOD_MAIN, SEV_NOTICE) << "Now running...";
serviceManager.run();
+18 -4
View File
@@ -19,18 +19,32 @@
#include <boost/thread.hpp>
#include "config/ConfigReader.hpp"
#include "logger/Logger.hpp"
#include "DatabaseUpdateService.hpp"
static std::vector<std::string> splitStrings(const std::string& source)
{
std::vector<std::string> res;
std::istringstream oss(source);
std::string str;
while(oss >> str)
res.push_back(str);
return res;
}
namespace Service {
DatabaseUpdateService::DatabaseUpdateService(const Config& config)
DatabaseUpdateService::DatabaseUpdateService()
: _metadataParser(),
_databaseUpdater( config.dbPath, _metadataParser)
_databaseUpdater( ConfigReader::instance().getString("main.database.path"),
_metadataParser)
{
_databaseUpdater.setAudioExtensions(config.audioExtensions);
_databaseUpdater.setVideoExtensions(config.videoExtensions);
_databaseUpdater.setAudioExtensions(splitStrings(ConfigReader::instance().getString("main.database.audio_extensions")));
_databaseUpdater.setVideoExtensions(splitStrings(ConfigReader::instance().getString("main.database.video_extensions")));
}
void
+1 -8
View File
@@ -36,14 +36,7 @@ class DatabaseUpdateService : public Service
typedef std::shared_ptr<DatabaseUpdateService> pointer;
struct Config {
bool enable;
boost::filesystem::path dbPath;
std::vector<std::string> audioExtensions;
std::vector<std::string> videoExtensions;
};
DatabaseUpdateService(const Config& config);
DatabaseUpdateService();
// Service interface
void start(void);
+12 -6
View File
@@ -17,18 +17,24 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/asio/ip/address.hpp>
#include "config/ConfigReader.hpp"
#include "logger/Logger.hpp"
#include "LmsAPIServerService.hpp"
namespace Service {
LmsAPIService::LmsAPIService(const Config& config)
: _server(boost::asio::ip::tcp::endpoint(config.address, config.port),
config.sslCertificatePath,
config.sslPrivateKeyPath,
config.sslTempDhPath,
config.dbPath)
LmsAPIService::LmsAPIService()
: _server(
boost::asio::ip::tcp::endpoint(
boost::asio::ip::address::from_string(ConfigReader::instance().getString("remote.listen-endpoint.addr")),
ConfigReader::instance().getULong("remote.listen-endpoint.port")),
ConfigReader::instance().getString("remote.ssl-crypto.cert"),
ConfigReader::instance().getString("remote.ssl-crypto.key"),
ConfigReader::instance().getString("remote.ssl-crypto.dh"),
ConfigReader::instance().getString("main.database.path"))
{
}
+1 -12
View File
@@ -21,7 +21,6 @@
#define REMOTE_SERVER_SERVICE_HPP
#include <boost/filesystem.hpp>
#include <boost/asio/ip/address.hpp>
#include "config/config.h"
@@ -35,17 +34,7 @@ class LmsAPIService : public Service
{
public:
struct Config {
bool enable;
boost::asio::ip::address address;
unsigned short port;
boost::filesystem::path sslCertificatePath;
boost::filesystem::path sslPrivateKeyPath;
boost::filesystem::path sslTempDhPath;
boost::filesystem::path dbPath;
};
LmsAPIService(const Config& config);
LmsAPIService();
void start(void);
void stop(void);
+11 -12
View File
@@ -22,24 +22,23 @@
#include "UserInterfaceService.hpp"
#include "ui/LmsApplication.hpp"
#include "config/ConfigReader.hpp"
namespace Service {
UserInterfaceService::UserInterfaceService( boost::filesystem::path runAppPath, const Config& config)
UserInterfaceService::UserInterfaceService( boost::filesystem::path runAppPath)
: _server(runAppPath.string())
{
std::vector<std::string> args;
args.push_back(runAppPath.string());
args.push_back("--docroot=" + config.docRootPath.string());
args.push_back("--approot=" + config.appRootPath.string());
{
std::ostringstream oss; oss << config.httpsPort;
args.push_back("--https-port=" + oss.str());
}
args.push_back("--https-address=" + config.httpsAddress.to_string());
args.push_back("--ssl-certificate=" + config.sslCertificatePath.string());
args.push_back("--ssl-private-key=" + config.sslPrivateKeyPath.string());
args.push_back("--ssl-tmp-dh=" + config.sslTempDhPath.string());
args.push_back("--docroot=" + ConfigReader::instance().getString("ui.resources.docroot"));
args.push_back("--approot=" + ConfigReader::instance().getString("ui.resources.approot"));
args.push_back("--https-port=" + std::to_string( ConfigReader::instance().getULong("ui.listen-endpoint.port")));
args.push_back("--https-address=" + ConfigReader::instance().getString("ui.listen-endpoint.addr"));
args.push_back("--ssl-certificate=" + ConfigReader::instance().getString("ui.ssl-crypto.cert"));
args.push_back("--ssl-private-key=" + ConfigReader::instance().getString("ui.ssl-crypto.key"));
args.push_back("--ssl-tmp-dh=" + ConfigReader::instance().getString("ui.ssl-crypto.dh"));
// Construct argc/argv
int argc = args.size();
@@ -55,7 +54,7 @@ UserInterfaceService::UserInterfaceService( boost::filesystem::path runAppPath,
_server.setServerConfiguration (argc, const_cast<char**>(argv));
// bind entry point
_server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, _1, config.dbPath));
_server.addEntryPoint(Wt::Application, boost::bind(UserInterface::LmsApplication::create, _1, ConfigReader::instance().getString("main.database.path")));
}
+1 -15
View File
@@ -21,7 +21,6 @@
#define WEB_SERVER_SERVICE_HPP
#include <boost/filesystem.hpp>
#include <boost/asio/ip/address.hpp>
#include <Wt/WServer>
@@ -33,20 +32,7 @@ class UserInterfaceService : public Service
{
public:
struct Config {
bool enable;
boost::filesystem::path docRootPath;
boost::filesystem::path appRootPath;
unsigned short httpsPort;
boost::asio::ip::address httpsAddress;
boost::filesystem::path sslCertificatePath;
boost::filesystem::path sslPrivateKeyPath;
boost::filesystem::path sslTempDhPath;
boost::filesystem::path dbPath;
};
UserInterfaceService(boost::filesystem::path runAppPath,
const Config& config);
UserInterfaceService(boost::filesystem::path runAppPath);
void start(void);
void stop(void);
+1 -3
View File
@@ -157,14 +157,12 @@ AvConvTranscoder::AvConvTranscoder(const Parameters& parameters)
// See boost process FAQ
{
boost::lock_guard<boost::mutex> lock(_mutex);
std::vector<int> ranges = { 3, 1024 }; // fd range to be closed
_child = std::make_shared<boost::process::child>( boost::process::execute(
boost::process::initializers::run_exe(_avConvPath),
boost::process::initializers::set_cmd_line(oss.str()),
boost::process::initializers::bind_stdout(sink),
boost::process::initializers::close_fd(STDIN_FILENO),
boost::process::initializers::close_fds(ranges)
boost::process::initializers::close_fds_if([](int fd) { return fd != STDOUT_FILENO;})
)
);
}
+10 -1
View File
@@ -102,8 +102,13 @@ _playQueue(nullptr)
Wt::WPushButton* addBtn = new Wt::WPushButton("Add");
addBtn->setStyleClass("btn-sm");
trackControls->addWidget(addBtn);
trackControls->addWidget(new Wt::WText("Total duration: "), 1);
Wt::WText *statsText = new Wt::WText();
statsText->setStyleClass("vertical-align");
trackControls->addWidget(statsText, 1);
_trackView->statsUpdated().connect(std::bind([=] (Wt::WString stats) {
statsText->setText(stats);
}, std::placeholders::_1));
trackLayout->addLayout(trackControls);
@@ -111,6 +116,7 @@ _playQueue(nullptr)
_filterChain.addFilter(_trackView);
_playQueue = new PlayQueue();
// Playlist/PlayQueue
@@ -216,6 +222,9 @@ _playQueue(nullptr)
playlistRefreshMenus();
// Initially, search for everything
_filterChain.searchKeyword("");
}
void
-8
View File
@@ -30,14 +30,6 @@ namespace Desktop {
class Filter
{
public:
struct Constraint {
std::vector<std::string> search;
typedef std::map<std::string, std::vector<std::string> > ColumnValues;
ColumnValues columnValues;
};
Filter() {}
virtual ~Filter() {}
+13 -6
View File
@@ -291,6 +291,8 @@ _trackSelector(new TrackSelector())
this->setColumnWidth(COLUMN_ID_NAME, 240);
this->setLayoutSizeAware(true);
this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal);
this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical);
this->setColumnHidden(COLUMN_ID_TRACK_ID, true);
@@ -501,12 +503,17 @@ PlayQueue::delSelected(void)
int minId = _model->rowCount();
Wt::WModelIndexSet indexSet = this->selectedIndexes();
BOOST_REVERSE_FOREACH(Wt::WModelIndex index, indexSet)
{
_model->removeRow(index.row());
if (index.row() < minId)
minId = index.row();
}
// Make sure to delete entries in the reverse order
std::vector<int> rowIds;
for (Wt::WModelIndex index : indexSet)
rowIds.push_back(index.row());
std::sort(rowIds.begin(), rowIds.end(), std::greater<int>());
if (!rowIds.empty())
minId = rowIds.back();
for (int rowId : rowIds)
_model->removeRow(rowId);
// If the current played track is removed, make sure to unselect it
_trackSelector->setSize(_model->rowCount());
+14 -7
View File
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include <Wt/WItemDelegate>
#include "database/Types.hpp"
@@ -50,7 +48,10 @@ TableFilterGenre::TableFilterGenre(Wt::WContainerWidget* parent)
this->selectionChanged().connect(this, &TableFilterGenre::emitUpdate);
setLayoutSizeAware(true);
this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal);
this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical);
this->setLayoutSizeAware(true);
_queryModel.setBatchSize(100);
@@ -122,7 +123,10 @@ TableFilterArtist::TableFilterArtist(Wt::WContainerWidget* parent)
this->selectionChanged().connect(this, &TableFilterArtist::emitUpdate);
setLayoutSizeAware(true);
this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal);
this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical);
this->setLayoutSizeAware(true);
_queryModel.setBatchSize(100);
@@ -163,8 +167,8 @@ TableFilterArtist::getConstraint(SearchFilter& filter)
{
Wt::WModelIndexSet indexSet = this->selectedIndexes();
BOOST_FOREACH(Wt::WModelIndex index, indexSet) {
for (Wt::WModelIndex index : indexSet)
{
if (!index.isValid())
continue;
@@ -200,7 +204,10 @@ TableFilterRelease::TableFilterRelease(Wt::WContainerWidget* parent)
this->selectionChanged().connect(this, &TableFilterRelease::emitUpdate);
setLayoutSizeAware(true);
this->setOverflow(Wt::WContainerWidget::OverflowHidden, Wt::Horizontal);
this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical);
this->setLayoutSizeAware(true);
_queryModel.setBatchSize(100);
+44 -13
View File
@@ -17,9 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/foreach.hpp>
#include <Wt/WItemDelegate>
#include <Wt/WBreak>
@@ -31,6 +28,8 @@
namespace UserInterface {
namespace Desktop {
using namespace Database;
TrackView::TrackView(Wt::WContainerWidget* parent)
: Wt::WTableView( parent )
{
@@ -48,9 +47,9 @@ TrackView::TrackView(Wt::WContainerWidget* parent)
"Genres",
};
Database::SearchFilter filter;
SearchFilter filter;
Database::Track::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
Track::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames);
_queryModel.setBatchSize(300);
@@ -68,7 +67,7 @@ TrackView::TrackView(Wt::WContainerWidget* parent)
this->setColumnWidth(6, 70); // Date
this->setColumnWidth(7, 70); // Original Date
this->setColumnWidth(8, 180); // Genres
// this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical);
this->setOverflow(Wt::WContainerWidget::OverflowScroll, Wt::Vertical);
// Duration display
{
@@ -107,15 +106,47 @@ TrackView::TrackView(Wt::WContainerWidget* parent)
}
void
TrackView::emitStats(const SearchFilter& filter)
{
Wt::Dbo::Transaction transaction (DboSession());
// Update stats on the view
Track::StatsQueryResult stats = Track::getStats(DboSession(), filter);
transaction.commit();
int nbTracks = stats.get<0>();
boost::posix_time::time_duration totalDuration = stats.get<1>();
std::ostringstream oss;
oss << nbTracks << " track" << (nbTracks > 1 ? "s" : "") << ", ";
if (totalDuration.hours() >= 24)
{
auto days = totalDuration.hours() / 24;
oss << days << " day" << (days > 1 ? "s " : " ");
}
oss << std::setw(2) << std::setfill('0') << totalDuration.hours() % 24
<< ":" << std::setw(2) << std::setfill('0') << totalDuration.minutes()
<< ":" << std::setw(2) << std::setfill('0') << totalDuration.seconds();
_sigStatsUpdated.emit(Wt::WString(oss.str()));
}
// Set constraints created by parent filters
void
TrackView::refresh(Database::SearchFilter& filter)
TrackView::refresh(SearchFilter& filter)
{
Database::Track::updateUIQueryModel(DboSession(), _queryModel, filter);
Track::updateUIQueryModel(DboSession(), _queryModel, filter);
emitStats(filter);
}
void
TrackView::getSelectedTracks(std::vector<Database::Track::id_type>& track_ids)
TrackView::getSelectedTracks(std::vector<Track::id_type>& track_ids)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting selected tracks...";
@@ -126,7 +157,7 @@ TrackView::getSelectedTracks(std::vector<Database::Track::id_type>& track_ids)
if (!index.isValid())
continue;
Database::Track::id_type id = _queryModel.resultRow( index.row() ).get<0>();
Track::id_type id = _queryModel.resultRow( index.row() ).get<0>();
track_ids.push_back(id);
}
@@ -157,16 +188,16 @@ TrackView::getFirstSelectedTrackPosition(void)
}
void
TrackView::getTracks(std::vector<Database::Track::id_type>& trackIds)
TrackView::getTracks(std::vector<Track::id_type>& trackIds)
{
LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks...";
Wt::Dbo::Transaction transaction(DboSession());
Wt::Dbo::collection<Database::Track::UIQueryResult> results = _queryModel.query();
Wt::Dbo::collection<Track::UIQueryResult> results = _queryModel.query();
for (auto it = results.begin(); it != results.end(); ++it)
{
Database::Track::id_type id = it->get<0>();
Track::id_type id = it->get<0>();
trackIds.push_back(id);
}
+5
View File
@@ -55,12 +55,17 @@ class TrackView : public Wt::WTableView, public Filter
void getTracks(std::vector<Database::Track::id_type>& track_ids);
typedef Wt::Signal<void> SigTrackDoubleClicked;
typedef Wt::Signal<Wt::WString> SigStatsUpdated;
SigTrackDoubleClicked& trackDoubleClicked() { return _sigTrackDoubleClicked; }
SigStatsUpdated& statsUpdated() { return _sigStatsUpdated; }
private:
SigTrackDoubleClicked _sigTrackDoubleClicked;
SigStatsUpdated _sigStatsUpdated;
void emitStats(const Database::SearchFilter& filter);
typedef Database::Track::UIQueryResult ResultType;
Wt::Dbo::QueryModel< ResultType > _queryModel;
@@ -47,7 +47,7 @@ AvConvTranscodeStreamResource::handleRequest(const Wt::Http::Request& request,
// see if this request is for a continuation:
Wt::Http::ResponseContinuation *continuation = request.continuation();
LMS_LOG(MOD_UI, SEV_DEBUG) << "Handling new request. Continuation = " << std::boolalpha << continuation;
LMS_LOG(MOD_UI, SEV_DEBUG) << "Handling new request. Continuation = " << continuation;
std::shared_ptr<Transcode::AvConvTranscoder> transcoder;
if (continuation)
+6
View File
@@ -157,6 +157,9 @@ int main(void)
std::vector<Artist::pointer> res = Artist::getByFilter(db.getSession(), filter, -1, -1);
assert(res.size() == 1);
assert(res.front().id() == 1);
// Make sure artist has a release
assert(res.front()->getReleases().size() == 1);
}
// Select Release by name
@@ -179,6 +182,9 @@ int main(void)
assert(res.size() == 1);
assert(res.front().id() == 1);
assert(res.front()->getName() == "release01");
// Make sure release has an artist
assert(res.front()->getArtists().size() == 1);
}
// Select genre by name
+2 -4
View File
@@ -13,21 +13,19 @@ remote_client_SOURCES = \
nodist_remote_client_SOURCES = \
$(builddir)/auth.pb.cc \
$(builddir)/collection.pb.cc \
$(builddir)/common.pb.cc \
$(builddir)/media.pb.cc \
$(builddir)/messages.pb.cc
BUILT_SOURCES = \
$(builddir)/auth.pb.h \
$(builddir)/collection.pb.h \
$(builddir)/common.pb.h \
$(builddir)/media.pb.h \
$(builddir)/messages.pb.h
remote_client_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_srcdir)/src
%.pb.cc %.pb.h: $(top_srcdir)/src/lms-api/proto/%.proto
$(PROTOC) --proto_path=$(top_srcdir)/src/lms-api/proto/ --cpp_out=$(builddir)/ $^
%.pb.cc %.pb.h: $(top_srcdir)/lms-api/proto/%.proto
$(PROTOC) --proto_path=$(top_srcdir)/lms-api/proto/ --cpp_out=$(builddir)/ $^
endif
+68 -68
View File
@@ -27,7 +27,7 @@
#include <boost/asio/ssl.hpp>
#include <boost/foreach.hpp>
#include "remote/messages/Header.hpp"
#include "lms-api/messages/Header.hpp"
#include "messages.pb.h"
#include "TestDatabase.hpp"
@@ -123,7 +123,7 @@ struct SearchFilter
std::vector<uint64_t> trackIds;
};
void SearchFilterToRequest(const SearchFilter& filter, Remote::AudioCollectionRequest_SearchFilter& request)
void SearchFilterToRequest(const SearchFilter& filter, LmsAPI::AudioCollectionRequest_SearchFilter& request)
{
for (uint64_t id : filter.artistIds)
request.add_artist_id(id);
@@ -174,11 +174,11 @@ class TestClient
std::size_t nbArtists = 0;
// Send request
Remote::ClientMessage request;
LmsAPI::ClientMessage request;
request.set_type( Remote::ClientMessage_Type_AudioCollectionRequest );
request.set_type( LmsAPI::ClientMessage_Type_AudioCollectionRequest );
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetArtistList);
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest_Type_TypeGetArtistList);
request.mutable_audio_collection_request()->mutable_get_artists()->mutable_batch_parameter()->set_size(size);
request.mutable_audio_collection_request()->mutable_get_artists()->mutable_batch_parameter()->set_offset(offset);
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_artists()->mutable_search_filter());
@@ -186,7 +186,7 @@ class TestClient
sendMsg(request);
// Receive responses
Remote::ServerMessage response;
LmsAPI::ServerMessage response;
recvMsg(response);
// Process message
@@ -198,7 +198,7 @@ class TestClient
for (int i = 0; i < response.audio_collection_response().artist_list().artists_size(); ++i)
{
const Remote::AudioCollectionResponse_Artist& respArtist = response.audio_collection_response().artist_list().artists(i);
const LmsAPI::AudioCollectionResponse_Artist& respArtist = response.audio_collection_response().artist_list().artists(i);
if (!respArtist.has_id())
throw std::runtime_error("no id!");
@@ -235,11 +235,11 @@ class TestClient
std::size_t nbAdded = 0;
// Send request
Remote::ClientMessage request;
LmsAPI::ClientMessage request;
request.set_type( Remote::ClientMessage_Type_AudioCollectionRequest );
request.set_type( LmsAPI::ClientMessage_Type_AudioCollectionRequest );
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetGenreList);
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest_Type_TypeGetGenreList);
request.mutable_audio_collection_request()->mutable_get_genres()->mutable_batch_parameter()->set_size(size);
request.mutable_audio_collection_request()->mutable_get_genres()->mutable_batch_parameter()->set_offset(offset);
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_genres()->mutable_search_filter());
@@ -247,7 +247,7 @@ class TestClient
sendMsg(request);
// Receive responses
Remote::ServerMessage response;
LmsAPI::ServerMessage response;
recvMsg(response);
// Process message
@@ -259,7 +259,7 @@ class TestClient
for (int i = 0; i < response.audio_collection_response().genre_list().genres_size(); ++i)
{
const Remote::AudioCollectionResponse_Genre& respGenre = response.audio_collection_response().genre_list().genres(i);
const LmsAPI::AudioCollectionResponse_Genre& respGenre = response.audio_collection_response().genre_list().genres(i);
if (!respGenre.has_id())
throw std::runtime_error("no genre id!");
@@ -293,11 +293,11 @@ class TestClient
std::size_t nbAdded = 0;
// Send request
Remote::ClientMessage request;
LmsAPI::ClientMessage request;
request.set_type( Remote::ClientMessage_Type_AudioCollectionRequest );
request.set_type( LmsAPI::ClientMessage_Type_AudioCollectionRequest );
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetReleaseList);
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest_Type_TypeGetReleaseList);
request.mutable_audio_collection_request()->mutable_get_releases()->mutable_batch_parameter()->set_size(size);
request.mutable_audio_collection_request()->mutable_get_releases()->mutable_batch_parameter()->set_offset(offset);
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_releases()->mutable_search_filter());
@@ -305,14 +305,14 @@ class TestClient
sendMsg(request);
// Receive responses
Remote::ServerMessage response;
LmsAPI::ServerMessage response;
recvMsg(response);
// Process message
if (!response.audio_collection_response().has_type())
throw std::runtime_error("Missing type!");
// if (!response.audio_collection_response().type() != Remote::ServerMessage::AudioCollectionResponse::TypeReleaseList)
// if (!response.audio_collection_response().type() != LmsAPI::ServerMessage::AudioCollectionResponse::TypeReleaseList)
// throw std::runtime_error("Bad type!");
if (!response.has_audio_collection_response())
@@ -323,7 +323,7 @@ class TestClient
for (int i = 0; i < response.audio_collection_response().release_list().releases_size(); ++i)
{
const Remote::AudioCollectionResponse_Release& respRelease = response.audio_collection_response().release_list().releases(i);
const LmsAPI::AudioCollectionResponse_Release& respRelease = response.audio_collection_response().release_list().releases(i);
if (!respRelease.has_id())
throw std::runtime_error("no id!");
@@ -355,11 +355,11 @@ class TestClient
std::size_t nbAdded = 0;
// Send request
Remote::ClientMessage request;
LmsAPI::ClientMessage request;
request.set_type( Remote::ClientMessage_Type_AudioCollectionRequest );
request.set_type( LmsAPI::ClientMessage_Type_AudioCollectionRequest );
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest_Type_TypeGetTrackList);
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest_Type_TypeGetTrackList);
request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_batch_parameter()->set_size(size);
request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_batch_parameter()->set_offset(offset);
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_search_filter());
@@ -367,7 +367,7 @@ class TestClient
sendMsg(request);
// Receive responses
Remote::ServerMessage response;
LmsAPI::ServerMessage response;
recvMsg(response);
// Process message
@@ -379,7 +379,7 @@ class TestClient
for (int i = 0; i < response.audio_collection_response().track_list().tracks_size(); ++i)
{
const Remote::AudioCollectionResponse_Track& respTrack = response.audio_collection_response().track_list().tracks(i);;
const LmsAPI::AudioCollectionResponse_Track& respTrack = response.audio_collection_response().track_list().tracks(i);;
TrackInfo track;
track.id = respTrack.id();
@@ -426,18 +426,18 @@ class TestClient
void getCoverTrack(std::vector<Cover>& coverArt, uint64_t trackId)
{
// Send request
Remote::ClientMessage request;
LmsAPI::ClientMessage request;
request.set_type( Remote::ClientMessage::AudioCollectionRequest );
request.set_type( LmsAPI::ClientMessage::AudioCollectionRequest );
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest::TypeGetCoverArt);
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_type( Remote::AudioCollectionRequest::GetCoverArt::TypeGetCoverArtTrack);
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest::TypeGetCoverArt);
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_type( LmsAPI::AudioCollectionRequest::GetCoverArt::TypeGetCoverArtTrack);
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_track_id( trackId );
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_size( 256 );
sendMsg(request);
// Receive responses
Remote::ServerMessage response;
LmsAPI::ServerMessage response;
recvMsg(response);
// Process message
@@ -457,19 +457,19 @@ class TestClient
void getCoverRelease(std::vector<Cover>& coverArt, uint64_t releaseId)
{
// Send request
Remote::ClientMessage request;
LmsAPI::ClientMessage request;
request.set_type( Remote::ClientMessage::AudioCollectionRequest );
request.set_type( LmsAPI::ClientMessage::AudioCollectionRequest );
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest::TypeGetCoverArt);
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_type( Remote::AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease);
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest::TypeGetCoverArt);
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_type( LmsAPI::AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease);
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_release_id( releaseId );
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_size( 256 );
sendMsg(request);
// Receive responses
Remote::ServerMessage response;
LmsAPI::ServerMessage response;
recvMsg(response);
// Process message
@@ -490,16 +490,16 @@ class TestClient
std::string getRevision(void)
{
// Send request
Remote::ClientMessage request;
LmsAPI::ClientMessage request;
request.set_type( Remote::ClientMessage::AudioCollectionRequest );
request.set_type( LmsAPI::ClientMessage::AudioCollectionRequest );
request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest::TypeGetRevision);
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest::TypeGetRevision);
sendMsg(request);
// Receive responses
Remote::ServerMessage response;
LmsAPI::ServerMessage response;
recvMsg(response);
// Process message
@@ -515,18 +515,18 @@ class TestClient
bool login(const std::string& username, const std::string& password)
{
// Send request
Remote::ClientMessage request;
LmsAPI::ClientMessage request;
request.set_type( Remote::ClientMessage::AuthRequest );
request.set_type( LmsAPI::ClientMessage::AuthRequest );
request.mutable_auth_request()->set_type( Remote::AuthRequest::TypePassword);
request.mutable_auth_request()->set_type( LmsAPI::AuthRequest::TypePassword);
request.mutable_auth_request()->mutable_password()->set_user_login( username );
request.mutable_auth_request()->mutable_password()->set_user_password( password );
sendMsg(request);
// Receive responses
Remote::ServerMessage response;
LmsAPI::ServerMessage response;
recvMsg(response);
// Process message
@@ -538,11 +538,11 @@ class TestClient
switch( response.auth_response().password_result().type())
{
case Remote::AuthResponse::PasswordResult::TypePasswordValid:
case LmsAPI::AuthResponse::PasswordResult::TypePasswordValid:
return true;
case Remote::AuthResponse::PasswordResult::TypePasswordInvalid:
case LmsAPI::AuthResponse::PasswordResult::TypePasswordInvalid:
return false;
case Remote::AuthResponse::PasswordResult::TypeLoginThrottling:
case LmsAPI::AuthResponse::PasswordResult::TypeLoginThrottling:
if (response.auth_response().password_result().has_delay())
std::cerr << "Has to wait for " << response.auth_response().password_result().delay() << " seconds" << std::endl;
@@ -571,24 +571,24 @@ class TestClient
{
// Send request
Remote::ClientMessage request;
LmsAPI::ClientMessage request;
request.set_type( Remote::ClientMessage_Type_MediaRequest);
request.set_type( LmsAPI::ClientMessage_Type_MediaRequest);
request.mutable_media_request()->set_type( Remote::MediaRequest_Type_TypeMediaPrepare);
request.mutable_media_request()->mutable_prepare()->set_type(Remote::MediaRequest_Prepare_Type_AudioRequest);
request.mutable_media_request()->set_type( LmsAPI::MediaRequest_Type_TypeMediaPrepare);
request.mutable_media_request()->mutable_prepare()->set_type(LmsAPI::MediaRequest_Prepare_Type_AudioRequest);
// Set fields
request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_track_id( audioId );
request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_codec_type( Remote::MediaRequest::Prepare::AudioCodecTypeOGA );
request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_bitrate( Remote::MediaRequest::Prepare::AudioBitrate_64_kbps );
request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_codec_type( LmsAPI::MediaRequest::Prepare::AudioCodecTypeOGA );
request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_bitrate( LmsAPI::MediaRequest::Prepare::AudioBitrate_64_kbps );
std::cout << "Sending prepare request" << std::endl;
sendMsg(request);
std::cout << "Waiting for response" << std::endl;
// Receive response
Remote::ServerMessage response;
LmsAPI::ServerMessage response;
recvMsg(response);
std::cout << "Got a response" << std::endl;
@@ -616,11 +616,11 @@ class TestClient
std::size_t mediaGetPart(uint32_t handle, std::vector<unsigned char>& data)
{
// Send request
Remote::ClientMessage request;
LmsAPI::ClientMessage request;
request.set_type( Remote::ClientMessage::MediaRequest);
request.set_type( LmsAPI::ClientMessage::MediaRequest);
request.mutable_media_request()->set_type( Remote::MediaRequest::TypeMediaGetPart);
request.mutable_media_request()->set_type( LmsAPI::MediaRequest::TypeMediaGetPart);
request.mutable_media_request()->mutable_get_part()->set_handle(handle);
request.mutable_media_request()->mutable_get_part()->set_requested_data_size(65536);
@@ -629,7 +629,7 @@ class TestClient
std::cout << "Waiting for response" << std::endl;
// Receive response
Remote::ServerMessage response;
LmsAPI::ServerMessage response;
recvMsg(response);
std::cout << "Got a response" << std::endl;
@@ -638,7 +638,7 @@ class TestClient
throw std::runtime_error("not an media response");
// Process message
if (response.media_response().type() != Remote::MediaResponse::TypePartResult)
if (response.media_response().type() != LmsAPI::MediaResponse::TypePartResult)
throw std::runtime_error("GetPart: not a Part response!");
if (!response.media_response().has_part_result())
@@ -652,17 +652,17 @@ class TestClient
void mediaTerminate( uint32_t handle )
{
// Send request
Remote::ClientMessage request;
LmsAPI::ClientMessage request;
request.set_type( Remote::ClientMessage::MediaRequest);
request.set_type( LmsAPI::ClientMessage::MediaRequest);
request.mutable_media_request()->set_type( Remote::MediaRequest::TypeMediaTerminate);
request.mutable_media_request()->set_type( LmsAPI::MediaRequest::TypeMediaTerminate);
request.mutable_media_request()->mutable_terminate()->set_handle(handle);
sendMsg(request);
// Receive response
Remote::ServerMessage response;
LmsAPI::ServerMessage response;
recvMsg(response);
@@ -684,18 +684,18 @@ class TestClient
if (message.SerializeToOstream(&os))
{
if (_outputStreamBuf.size() > Remote::Header::max_data_size)
if (_outputStreamBuf.size() > LmsAPI::Header::max_data_size)
{
std::ostringstream oss; oss << "Message too big = " << _outputStreamBuf.size() << " bytes! (max is " << Remote::Header::max_data_size << ")" << std::endl;
std::ostringstream oss; oss << "Message too big = " << _outputStreamBuf.size() << " bytes! (max is " << LmsAPI::Header::max_data_size << ")" << std::endl;
throw std::runtime_error("Message to big!");
}
// Send message header
std::array<unsigned char, Remote::Header::size> headerBuffer;
std::array<unsigned char, LmsAPI::Header::size> headerBuffer;
// Generate header content
{
Remote::Header header;
LmsAPI::Header header;
header.setDataSize(_outputStreamBuf.size());
header.to_buffer(headerBuffer);
}
@@ -725,18 +725,18 @@ class TestClient
{
// reserve bytes in output sequence
boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(Remote::Header::size);
boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(LmsAPI::Header::size);
std::size_t n = boost::asio::read(_socket,
bufs,
boost::asio::transfer_exactly(Remote::Header::size));
boost::asio::transfer_exactly(LmsAPI::Header::size));
assert(n == Remote::Header::size);
assert(n == LmsAPI::Header::size);
_inputStreamBuf.commit(n);
}
Remote::Header header;
LmsAPI::Header header;
if (!header.from_istream(is))
throw std::runtime_error("Cannot read header from buffer!");