Merge branch 'develop'
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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,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) );
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"))
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")));
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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!");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user