diff --git a/README b/README index dca3d7e7..dd778bc4 100644 --- a/README +++ b/README @@ -8,7 +8,7 @@ Features: - Winamp-like interface, suited for large databases - Audio/Video transcode for maximum interoperability and low bandwith requirements - User management -- API available in Protocol Buffers (see src/remote/proto/messages.proto) +- LMS API available in Protocol Buffers (see src/lms-api/proto/messages.proto) Please note LMS is still under development and will gain more features in the future. A mobile application based on Kivy is being developped as well. @@ -33,6 +33,9 @@ II) BUILD (configure will complain if a mandatory library is missing) +The configure script allows you to disable the LMS API support (and protobuf dependencies) +See 'configure --help' for more information. + # make III) Setting up SSL materials (required by the SSL server and the web server) diff --git a/TODO b/TODO index ea4d5187..343ebbcc 100644 --- a/TODO +++ b/TODO @@ -19,6 +19,7 @@ - Optim, use SQL query to get the "genre" orphans - Use Inotify like system to watch modified/added files? - count scan import errors +- handle access rights problems (instead of aborting) [Metadata] - OGG metadata -> properly handle metadata nested in the audio stream @@ -60,7 +61,7 @@ - Make a dedicated audio layout in mobile environment (keep the same layout for videos?) - Handle internal path -[Remote API] +[Lms API] - Implement partial text search options in the GetXXX messages [REST API] diff --git a/configure.ac b/configure.ac index 5ab8fa94..e7408b53 100644 --- a/configure.ac +++ b/configure.ac @@ -1,29 +1,43 @@ AC_PREREQ(2.59) AC_INIT(lms, 1.0.0, test@test) AM_INIT_AUTOMAKE -AC_CONFIG_HEADER(include/config.h) +AC_CONFIG_HEADER(src/config/config.h) AC_LANG_CPLUSPLUS # Checks for programs AC_PROG_CXX + +AC_ARG_ENABLE(lmsapi, AS_HELP_STRING([--enable-lmsapi], + [Build with the lmsapi protocol support. Defaults to yes.])], + [enable_lmsapi="$enableval"], + [enable_lmsapi="yes"]) + # ProtoBuf compiler. # First, specify with --with-protoc=/path/of/protoc. # Or, specify with env variable PROTOC. # If neither of the above, find it in the path. -#AC_MSG_CHECKING([for ProtoBuf compiler protoc]) -AC_ARG_WITH([protoc], +AS_IF([test "$enable_lmsapi" = "yes"], + [AC_ARG_WITH([protoc], [AS_HELP_STRING([--with-protoc=/path/of/protoc], [Location of the protocol buffers compiler protoc. Defaults to looking on path.])], [PROTOC="$withval"], [ AS_IF([test "x${PROTOC}" == "x"], [AC_PATH_PROG([PROTOC], [protoc], [no])])] ) + ] + [ AS_IF([test "${PROTOC}" == "no"], [AC_MSG_ERROR([ProtoBuf compiler "protoc" not found.])]) ] + [AC_CHECK_LIB( [protobuf], + [main], + , + [AC_MSG_ERROR([libprotobuf not found!])])] + [lmsapi=true] + , + [lmsapi=false]) -#AC_MSG_RESULT([${PROTOC}]) -AS_IF([test "${PROTOC}" == "no"], [AC_MSG_ERROR([ProtoBuf compiler "protoc" not found.])]) - +AM_CONDITIONAL([LMSAPI], [test x$lmsapi = xtrue]) +AS_IF([test x$lmsapi = xtrue], [AC_DEFINE(HAVE_LMSAPI, [1], [Enable LMS API support])]) AC_CHECK_HEADERS([Wt/WApplication jpeglib.h], [], @@ -114,11 +128,6 @@ AC_CHECK_LIB( [boost_log_setup], , [AC_MSG_ERROR([libboost_log_setup not found!])]) -AC_CHECK_LIB( [protobuf], - [main], - , - [AC_MSG_ERROR([libprotobuf not found!])]) - # TODO -> PARAMETRIZE THIS AC_CHECK_LIB([wthttp], [main], diff --git a/src/Makefile.am b/src/Makefile.am index 4fe9107b..e537e5c2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -11,9 +11,12 @@ lms_SOURCES = \ $(srcdir)/config/ConfigReader.cpp \ $(srcdir)/cover/CoverArt.cpp \ $(srcdir)/cover/CoverArtGrabber.cpp \ + $(srcdir)/database/Artist.cpp \ $(srcdir)/database/DatabaseHandler.cpp \ $(srcdir)/database/MediaDirectory.cpp \ $(srcdir)/database/Playlist.cpp \ + $(srcdir)/database/Release.cpp \ + $(srcdir)/database/SearchFilter.cpp \ $(srcdir)/database/SqlQuery.cpp \ $(srcdir)/database/Track.cpp \ $(srcdir)/database/User.cpp \ @@ -23,17 +26,9 @@ lms_SOURCES = \ $(srcdir)/logger/Logger.cpp \ $(srcdir)/metadata/AvFormat.cpp \ $(srcdir)/metadata/Utils.cpp \ - $(srcdir)/remote/server/Connection.cpp \ - $(srcdir)/remote/server/ConnectionManager.cpp \ - $(srcdir)/remote/server/AudioCollectionRequestHandler.cpp \ - $(srcdir)/remote/server/AuthRequestHandler.cpp \ - $(srcdir)/remote/server/MediaRequestHandler.cpp \ - $(srcdir)/remote/server/RequestHandler.cpp \ - $(srcdir)/remote/server/Server.cpp \ $(srcdir)/service/ServiceManager.cpp \ $(srcdir)/service/DatabaseUpdateService.cpp \ $(srcdir)/service/UserInterfaceService.cpp \ - $(srcdir)/service/RemoteServerService.cpp \ $(srcdir)/transcode/AvConvTranscoder.cpp \ $(srcdir)/transcode/Format.cpp \ $(srcdir)/transcode/Parameters.cpp \ @@ -70,6 +65,17 @@ lms_SOURCES = \ $(srcdir)/ui/settings/SettingsUserFormView.cpp \ $(srcdir)/ui/settings/SettingsUsers.cpp +if LMSAPI +lms_SOURCES += \ + $(srcdir)/lms-api/server/Connection.cpp \ + $(srcdir)/lms-api/server/ConnectionManager.cpp \ + $(srcdir)/lms-api/server/AudioCollectionRequestHandler.cpp \ + $(srcdir)/lms-api/server/AuthRequestHandler.cpp \ + $(srcdir)/lms-api/server/MediaRequestHandler.cpp \ + $(srcdir)/lms-api/server/RequestHandler.cpp \ + $(srcdir)/lms-api/server/Server.cpp \ + $(srcdir)/service/LmsAPIServerService.cpp + nodist_lms_SOURCES = \ $(builddir)/auth.pb.cc \ $(builddir)/auth.pb.h \ @@ -106,8 +112,10 @@ MOSTLYCLEANFILES = \ $(builddir)/messages.pb.cc \ $(builddir)/messages.pb.h -%.pb.cc %.pb.h: $(srcdir)/remote/proto/%.proto - $(PROTOC) --proto_path=$(srcdir)/remote/proto/ --cpp_out=$(builddir)/ $^ +%.pb.cc %.pb.h: $(srcdir)/lms-api/proto/%.proto + $(PROTOC) --proto_path=$(srcdir)/lms-api/proto/ --cpp_out=$(builddir)/ $^ -lms_CXXFLAGS=-DBOOST_LOG_DYN_LINK -std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui -I$(srcdir)/remote +endif + +lms_CXXFLAGS=-DBOOST_LOG_DYN_LINK -std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui -I$(srcdir)/lms-api diff --git a/src/config/ConfigReader.cpp b/src/config/ConfigReader.cpp index efe00fe2..5e351bc8 100644 --- a/src/config/ConfigReader.cpp +++ b/src/config/ConfigReader.cpp @@ -77,8 +77,9 @@ ConfigReader::getUserInterfaceConfig(Service::UserInterfaceService::Config& conf config.dbPath = _config.lookup("main.database.path"); } +#if defined HAVE_LMSAPI void -ConfigReader::getRemoteServerConfig(Service::RemoteServerService::Config& config) +ConfigReader::getLmsAPIConfig(Service::LmsAPIService::Config& config) { config.enable = _config.lookup("remote.enable"); if (!config.enable) @@ -92,6 +93,7 @@ ConfigReader::getRemoteServerConfig(Service::RemoteServerService::Config& config config.dbPath = _config.lookup("main.database.path"); } +#endif void ConfigReader::getDatabaseUpdateConfig(Service::DatabaseUpdateService::Config& config) diff --git a/src/config/ConfigReader.hpp b/src/config/ConfigReader.hpp index 5701a3b5..6abc5039 100644 --- a/src/config/ConfigReader.hpp +++ b/src/config/ConfigReader.hpp @@ -23,12 +23,16 @@ #include #include +#include "config/config.h" + #include "cover/CoverArtGrabber.hpp" #include "logger/Logger.hpp" #include "service/UserInterfaceService.hpp" -#include "service/RemoteServerService.hpp" #include "service/DatabaseUpdateService.hpp" +#if defined HAVE_LMSAPI +#include "service/LmsAPIServerService.hpp" +#endif class ConfigReader { @@ -44,9 +48,12 @@ class ConfigReader // Service configurations void getUserInterfaceConfig(Service::UserInterfaceService::Config& config); - void getRemoteServerConfig(Service::RemoteServerService::Config& config); void getDatabaseUpdateConfig(Service::DatabaseUpdateService::Config& config); +#if defined HAVE_LMSAPI + void getLmsAPIConfig(Service::LmsAPIService::Config& config); +#endif + private: libconfig::Config _config; diff --git a/src/cover/CoverArtGrabber.cpp b/src/cover/CoverArtGrabber.cpp index deab6d46..9194330a 100644 --- a/src/cover/CoverArtGrabber.cpp +++ b/src/cover/CoverArtGrabber.cpp @@ -196,7 +196,7 @@ Grabber::getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackI std::vector -Grabber::getFromRelease(Wt::Dbo::Session& session, std::string releaseName, std::size_t nbMaxCovers) const +Grabber::getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers) const { using namespace Database; @@ -205,13 +205,11 @@ Grabber::getFromRelease(Wt::Dbo::Session& session, std::string releaseName, std: // Get the first track of the release { - SearchFilter filter; - filter.exactMatch[SearchFilter::Field::Release].push_back(releaseName); - Wt::Dbo::Transaction transaction(session); - std::vector tracks - = Track::getAll(session, filter, -1, 1 /* limit result size */); + std::vector tracks = Track::getByFilter(session, + SearchFilter::IdMatch({{SearchFilter::Field::Release, {releaseId}}}), + -1, 1 /* limit result size */); if (tracks.empty()) return std::vector(); diff --git a/src/cover/CoverArtGrabber.hpp b/src/cover/CoverArtGrabber.hpp index a4768be5..b07bbb56 100644 --- a/src/cover/CoverArtGrabber.hpp +++ b/src/cover/CoverArtGrabber.hpp @@ -52,7 +52,7 @@ class Grabber std::vector getFromInputFormatContext(const Av::InputFormatContext& input, std::size_t nbMaxCovers = 1) const; std::vector getFromTrack(const boost::filesystem::path& path, std::size_t nbMaxCovers = 1) const; std::vector getFromTrack(Wt::Dbo::Session& session, Database::Track::id_type trackId, std::size_t nbMaxCovers = 1) const; - std::vector getFromRelease(Wt::Dbo::Session& session, std::string releaseName, std::size_t nbMaxCovers = 1) const; + std::vector getFromRelease(Wt::Dbo::Session& session, Database::Release::id_type releaseId, std::size_t nbMaxCovers = 1) const; private: Grabber(); diff --git a/src/database-updater/DatabaseUpdater.cpp b/src/database-updater/DatabaseUpdater.cpp index e59a0e03..dd4d0683 100644 --- a/src/database-updater/DatabaseUpdater.cpp +++ b/src/database-updater/DatabaseUpdater.cpp @@ -230,7 +230,7 @@ Updater::process(boost::system::error_code err) rootDirectories.push_back( std::make_pair( directory->getPath(), directory->getType() )); } - BOOST_FOREACH( RootDirectory rootDirectory, rootDirectories) + for (RootDirectory rootDirectory : rootDirectories) processDirectory(rootDirectory.first, rootDirectory.first, rootDirectory.second, stats); LMS_LOG(MOD_DBUPDATER, SEV_INFO) << "Changes = " << stats.nbChanges(); @@ -261,11 +261,104 @@ Updater::process(boost::system::error_code err) } } +Artist::pointer +Updater::getArtist( const boost::filesystem::path& file, const std::string& name, const std::string& mbid) +{ + Artist::pointer artist; + + // First try to get by MBID + if (!mbid.empty()) + { + artist = Artist::getByMBID( _db.getSession(), mbid ); + if (!artist) + artist = Artist::create( _db.getSession(), name, mbid); + + return artist; + } + + // Fall back on artist name (collisions may occur) + if (!name.empty()) + { + for (Artist::pointer sameNamedArtist : Artist::getByName( _db.getSession(), name )) + { + if (sameNamedArtist->getMBID().empty()) + { + artist = sameNamedArtist; + break; + } + } + + // No Artist found with the same name and without MBID -> creating + if (!artist) + artist = Artist::create( _db.getSession(), name); + + return artist; + } + + return Artist::getNone( _db.getSession() ); +} + +Release::pointer +Updater::getRelease( const boost::filesystem::path& file, const std::string& name, const std::string& mbid) +{ + Release::pointer release; + + // First try to get by MBID + if (!mbid.empty()) + { + release = Release::getByMBID( _db.getSession(), mbid ); + if (!release) + release = Release::create( _db.getSession(), name, mbid); + + return release; + } + + // Fall back on release name (collisions may occur) + if (!name.empty()) + { + for (Release::pointer sameNamedRelease : Release::getByName( _db.getSession(), name )) + { + if (sameNamedRelease->getMBID().empty()) + { + release = sameNamedRelease; + break; + } + } + + // No release found with the same name and without MBID -> creating + if (!release) + release = Release::create( _db.getSession(), name); + + return release; + } + + return Release::getNone( _db.getSession() ); +} + +std::vector +Updater::getGenres( const std::list& names) +{ + std::vector< Genre::pointer > genres; + + for (const std::string& name : names) + { + Genre::pointer genre ( Genre::getByName(_db.getSession(), name) ); + if (!genre) + genre = Genre::create(_db.getSession(), name); + + genres.push_back( genre ); + } + + if (genres.empty()) + genres.push_back( Genre::getNone( _db.getSession() )); + + return genres; +} + void Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) { try { - // Check last update time boost::posix_time::ptime lastWriteTime (boost::posix_time::from_time_t( boost::filesystem::last_write_time( file ) ) ); @@ -335,26 +428,49 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) // ***** Genres - typedef std::list GenreList; - GenreList genreList; std::vector< Genre::pointer > genres; - if (items.find(MetaData::Type::Genres) != items.end()) { - genreList = (boost::any_cast(items[MetaData::Type::Genres])); + std::list genreList; - BOOST_FOREACH(const std::string& genre, genreList) { - Genre::pointer dbGenre ( Genre::getByName(_db.getSession(), genre) ); - if (!dbGenre) - dbGenre = Genre::create(_db.getSession(), genre); + if (items.find(MetaData::Type::Genres) != items.end()) + genreList = boost::any_cast< std::list > (items[MetaData::Type::Genres]); - genres.push_back( dbGenre ); - } + genres = getGenres( genreList ); } - if (genres.empty()) - genres.push_back( Genre::getNone( _db.getSession() )); - assert( !genres.empty() ); + // ***** Artist + Artist::pointer artist; + { + std::string artistName; + std::string artistMusicBrainzID; + + if (items.find(MetaData::Type::MusicBrainzArtistID) != items.end()) + artistMusicBrainzID = boost::any_cast(items[MetaData::Type::MusicBrainzArtistID] ); + + if (items.find(MetaData::Type::Artist) != items.end()) + artistName = boost::any_cast(items[MetaData::Type::Artist]); + + artist = getArtist(file, artistName, artistMusicBrainzID); + } + assert(artist); + + // ***** Release + Release::pointer release; + { + std::string releaseName; + std::string releaseMusicBrainzID; + + if (items.find(MetaData::Type::MusicBrainzAlbumID) != items.end()) + releaseMusicBrainzID = boost::any_cast(items[MetaData::Type::MusicBrainzAlbumID] ); + + if (items.find(MetaData::Type::Album) != items.end()) + releaseName = boost::any_cast(items[MetaData::Type::Album]); + + release = getRelease(file, releaseName, releaseMusicBrainzID); + } + assert(release); + // If file already exist, update data // Otherwise, create it if (!track) @@ -372,6 +488,8 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) assert(track); + track.modify()->setArtist(artist); + track.modify()->setRelease(release); track.modify()->setLastWriteTime(lastWriteTime); track.modify()->setName(title); track.modify()->setDuration( boost::any_cast(items[MetaData::Type::Duration]) ); @@ -379,22 +497,17 @@ Updater::processAudioFile( const boost::filesystem::path& file, Stats& stats) { std::string trackGenreList; // Product genre list - BOOST_FOREACH(const std::string& genre, genreList) { + for (Genre::pointer genre : genres) + { if (!trackGenreList.empty()) trackGenreList += ", "; - trackGenreList += genre; + trackGenreList += genre->getName(); } track.modify()->setGenres( trackGenreList ); } track.modify()->setGenres( genres ); - if (items.find(MetaData::Type::Artist) != items.end()) - track.modify()->setArtistName( boost::any_cast(items[MetaData::Type::Artist]) ); - - if (items.find(MetaData::Type::Album) != items.end()) - track.modify()->setReleaseName( boost::any_cast(items[MetaData::Type::Album]) ); - if (items.find(MetaData::Type::TrackNumber) != items.end()) track.modify()->setTrackNumber( boost::any_cast(items[MetaData::Type::TrackNumber]) ); @@ -538,7 +651,7 @@ Updater::checkAudioFiles( Stats& stats ) } // Now process orphan Genre (no track) - LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Genres..."; +/* LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Checking Genres..."; typedef Wt::Dbo::collection< Wt::Dbo::ptr > Genres; Genres genres = Genre::getAll(_db.getSession()); @@ -549,7 +662,7 @@ Updater::checkAudioFiles( Stats& stats ) if (genre->getTracks().size() == 0) genre.remove(); } - +*/ LMS_LOG(MOD_DBUPDATER, SEV_DEBUG) << "Check audio files done!"; } diff --git a/src/database-updater/DatabaseUpdater.hpp b/src/database-updater/DatabaseUpdater.hpp index 34d58d04..22a9836a 100644 --- a/src/database-updater/DatabaseUpdater.hpp +++ b/src/database-updater/DatabaseUpdater.hpp @@ -72,6 +72,10 @@ class Updater Database::MediaDirectory::Type type, Stats& stats); + // Helpers + Database::Artist::pointer getArtist( const boost::filesystem::path& file, const std::string& name, const std::string& MBID); + Database::Release::pointer getRelease( const boost::filesystem::path& file, const std::string& name, const std::string& MBID); + std::vector getGenres( const std::list& names); // Audio void checkAudioFiles( Stats& stats ); diff --git a/src/database/Artist.cpp b/src/database/Artist.cpp new file mode 100644 index 00000000..ffff62b7 --- /dev/null +++ b/src/database/Artist.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "Types.hpp" +#include "SqlQuery.hpp" + +#include "logger/Logger.hpp" + +namespace Database +{ + +Artist::Artist(const std::string& name, const std::string& MBID) +: _name(std::string(name, 0 , _maxNameLength)), +_MBID(MBID) +{ + +} + +std::vector +Artist::getByName(Wt::Dbo::Session& session, const std::string& name) +{ + Wt::Dbo::collection res = session.find().where("name = ?").bind( std::string(name, 0, _maxNameLength) ); + return std::vector(res.begin(), res.end()); +} + +Artist::pointer +Artist::getByMBID(Wt::Dbo::Session& session, const std::string& mbid) +{ + return session.find().where("mbid = ?").bind(mbid); +} + +Artist::pointer +Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID) +{ + return session.add(new Artist(name, MBID)); +} + +Artist::pointer +Artist::getNone(Wt::Dbo::Session& session) +{ + std::vector res = getByName(session, ""); + if (res.empty()) + return create(session, ""); + + return res.front(); +} + +std::vector +Artist::getAll(Wt::Dbo::Session& session, int offset, int size) +{ + Wt::Dbo::collection res = session.find().offset(offset).limit(size); + return std::vector(res.begin(), res.end()); +} + +std::vector +Artist::getAllOrphans(Wt::Dbo::Session& session) +{ + Wt::Dbo::collection res = session.query< Wt::Dbo::ptr >("select a from artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL"); + + return std::vector(res.begin(), res.end()); +} + +std::vector +Artist::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) +{ + Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset); + + return std::vector(res.begin(), res.end()); +} + +Wt::Dbo::Query +Artist::getQuery(Wt::Dbo::Session& session, SearchFilter filter) +{ + SqlQuery sqlQuery = generatePartialQuery(filter); + + Wt::Dbo::Query query + = session.query( "SELECT a FROM artist a INNER JOIN track t ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_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 " + sqlQuery.where().get()).groupBy("a.id").orderBy("a.name"); + + for (const std::string& bindArg : sqlQuery.where().getBindArgs()) + query.bind(bindArg); + + return query; +} + +Wt::Dbo::Query +Artist::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) +{ + SqlQuery sqlQuery = generatePartialQuery(filter); + + Wt::Dbo::Query query + = session.query( "SELECT a.id, a.name, COUNT(DISTINCT r.id), COUNT(DISTINCT t.id) FROM artist a INNER JOIN track t ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_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 " + sqlQuery.where().get()).groupBy("a.id").orderBy("a.name"); + + for (const std::string& bindArg : sqlQuery.where().getBindArgs()) + query.bind(bindArg); + + return query; +} + +void +Artist::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) +{ + Wt::Dbo::Query query = getUIQuery(session, filter); + + model.setQuery(query, columnNames.empty() ? true : false); + + // TODO do something better + if (columnNames.size() == 3) + { + model.addColumn( "a.name", columnNames.at(0)); + model.addColumn( "COUNT(DISTINCT r.id)", columnNames.at(1) ); + model.addColumn( "COUNT(DISTINCT t.id)", columnNames.at(2) ); + } +} + + +} // namespace Database diff --git a/src/database/Artist.hpp b/src/database/Artist.hpp new file mode 100644 index 00000000..8c59ac07 --- /dev/null +++ b/src/database/Artist.hpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#ifndef _DB_ARTIST_HPP_ +#define _DB_ARTIST_HPP_ + +#include +#include + +#include +#include + +#include "SearchFilter.hpp" + +namespace Database +{ + +class Track; +class Genre; + +class Artist +{ + public: + + typedef Wt::Dbo::ptr pointer; + typedef Wt::Dbo::dbo_traits::IdType id_type; + + Artist() {} + Artist(const std::string& name, const std::string& MBID = ""); + + // Accessors + static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); + static pointer getById(Wt::Dbo::Session& session, id_type id); + static pointer getNone(Wt::Dbo::Session& session); // Special entry + static std::vector getByName(Wt::Dbo::Session& session, const std::string& name); + static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); + + static std::vector getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1); + static std::vector getAllOrphans(Wt::Dbo::Session& session); + + // Accessors + std::string getName(void) const { return _name; } + std::string getMBID(void) const { return _MBID; } + + void setMBID(std::string mbid) { _MBID = mbid; } + + // Create + static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); + + // MVC models for the user interface + // ID, Artist name, albums, tracks + typedef boost::tuple UIQueryResult; + static Wt::Dbo::Query getUIQuery(Wt::Dbo::Session& session, SearchFilter filter); + static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames = std::vector()); + + bool isNone(void) const; + + template + void persist(Action& a) + { + Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::field(a, _MBID, "mbid"); + + Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "artist"); + } + + private: + + static Wt::Dbo::Query getQuery(Wt::Dbo::Session& session, SearchFilter filter); + + static const std::size_t _maxNameLength = 128; + + std::string _name; + std::string _MBID; // Musicbrainz Identifier + + Wt::Dbo::collection< Wt::Dbo::ptr > _tracks; // Tracks of this artist +}; + +} // namespace Database + +#endif diff --git a/src/database/DatabaseHandler.cpp b/src/database/DatabaseHandler.cpp index c7b3eddf..81255287 100644 --- a/src/database/DatabaseHandler.cpp +++ b/src/database/DatabaseHandler.cpp @@ -77,10 +77,12 @@ Handler::Handler(boost::filesystem::path db) _dbBackend( db.string() ) { _session.setConnection(_dbBackend); + _session.mapClass("artist"); _session.mapClass("genre"); _session.mapClass("track"); _session.mapClass("playlist"); _session.mapClass("playlist_entry"); + _session.mapClass("release"); _session.mapClass("video"); _session.mapClass("media_directory"); _session.mapClass("media_directory_settings"); @@ -92,9 +94,10 @@ _dbBackend( db.string() ) try { _session.createTables(); - _dbBackend.executeSql("CREATE INDEX artist_name_idx ON track(artist_name)"); - _dbBackend.executeSql("CREATE INDEX release_name_idx ON track(release_name)"); + _dbBackend.executeSql("CREATE INDEX artist_name_idx ON artist(name)"); _dbBackend.executeSql("CREATE INDEX genre_name_idx ON genre(name)"); + _dbBackend.executeSql("CREATE INDEX release_name_idx ON release(name)"); + _dbBackend.executeSql("CREATE INDEX track_name_idx ON track(name)"); } catch(std::exception& e) { LMS_LOG(MOD_DB, SEV_ERROR) << "Cannot create tables: " << e.what(); diff --git a/src/database/Playlist.hpp b/src/database/Playlist.hpp index ba7e43e2..a8e0ddf0 100644 --- a/src/database/Playlist.hpp +++ b/src/database/Playlist.hpp @@ -22,6 +22,8 @@ #include +#include + namespace Database { class PlaylistEntry; diff --git a/src/database/Release.cpp b/src/database/Release.cpp new file mode 100644 index 00000000..d2bd4f72 --- /dev/null +++ b/src/database/Release.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "Types.hpp" +#include "SearchFilter.hpp" +#include "SqlQuery.hpp" + +namespace Database +{ + +Release::Release(const std::string& name, const std::string& MBID) +: _name(std::string(name, 0 , _maxNameLength)), +_MBID(MBID) +{ + +} + +std::vector +Release::getByName(Wt::Dbo::Session& session, const std::string& name) +{ + Wt::Dbo::collection res = session.find().where("name = ?").bind( std::string(name, 0, _maxNameLength) ); + return std::vector(res.begin(), res.end()); +} + +Release::pointer +Release::getByMBID(Wt::Dbo::Session& session, const std::string& mbid) +{ + return session.find().where("mbid = ?").bind(mbid); +} + + +Release::pointer +Release::create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID) +{ + return session.add(new Release(name, MBID)); +} + +Release::pointer +Release::getNone(Wt::Dbo::Session& session) +{ + std::vector res = getByName(session, ""); + if (res.empty()) + return create(session, ""); + + return res.front(); +} + +std::vector +Release::getAll(Wt::Dbo::Session& session, int offset, int size) +{ + Wt::Dbo::collection res = session.find().offset(offset).limit(size); + return std::vector(res.begin(), res.end()); +} + +std::vector +Release::getAllOrphans(Wt::Dbo::Session& session) +{ + Wt::Dbo::collection res = session.query< Wt::Dbo::ptr >("select a from artist a LEFT OUTER JOIN Track t ON a.id = t.artist_id WHERE t.id IS NULL"); + + return std::vector(res.begin(), res.end()); +} + +Wt::Dbo::Query +Release::getQuery(Wt::Dbo::Session& session, SearchFilter filter) +{ + SqlQuery sqlQuery = generatePartialQuery(filter); + + Wt::Dbo::Query query + = session.query("SELECT r FROM release r INNER JOIN artist a ON a.id = t.artist_id INNER JOIN track t ON t.release_id = r.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 " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name"); + + for (const std::string& bindArg : sqlQuery.where().getBindArgs()) + query.bind(bindArg); + + return query; +} + +Wt::Dbo::Query +Release::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) +{ + SqlQuery sqlQuery = generatePartialQuery(filter); + + // TODO DATE of RELEASE + Wt::Dbo::Query query + = session.query("SELECT r.id, r.name, t.date, COUNT(DISTINCT t.id) FROM release r INNER JOIN track t ON t.release_id = r.id INNER JOIN artist a ON a.id = t.artist_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 " + sqlQuery.where().get()).groupBy("r.id").orderBy("r.name"); + + for (const std::string& bindArg : sqlQuery.where().getBindArgs()) + query.bind(bindArg); + + return query; +} + +void +Release::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) +{ + Wt::Dbo::Query query = getUIQuery(session, filter); + + model.setQuery(query, columnNames.empty() ? true : false); + + // TODO do something better + if (columnNames.size() == 3) + { + model.addColumn( "r.name", columnNames[0]); + model.addColumn( "t.date", columnNames[1]); + model.addColumn( "COUNT(DISTINCT t.id)", columnNames[2]); + } +} + +std::vector +Release::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) +{ + Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset); + + return std::vector(res.begin(), res.end()); +} + +} // namespace Database diff --git a/src/database/Release.hpp b/src/database/Release.hpp new file mode 100644 index 00000000..479870a7 --- /dev/null +++ b/src/database/Release.hpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#ifndef _DB_RELEASE_HPP_ +#define _DB_RELEASE_HPP_ + +#include +#include + +#include "SearchFilter.hpp" + +namespace Database +{ + +class Track; + +class Release +{ + public: + + typedef Wt::Dbo::ptr pointer; + typedef Wt::Dbo::dbo_traits::IdType id_type; + + Release() {} + Release(const std::string& name, const std::string& MBID = ""); + + // Accessors + static pointer getByMBID(Wt::Dbo::Session& session, const std::string& MBID); + static std::vector getByName(Wt::Dbo::Session& session, const std::string& name); + static pointer getById(Wt::Dbo::Session& session, id_type id); + static pointer getNone(Wt::Dbo::Session& session); // Special entry + static std::vector getAllOrphans(Wt::Dbo::Session& session); + static std::vector getAll(Wt::Dbo::Session& session, int offset, int size); + static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); + + // Create + static pointer create(Wt::Dbo::Session& session, const std::string& name, const std::string& MBID = ""); + + // MVC models for the user interface + // ID, Release name, year, track counts + typedef boost::tuple UIQueryResult; + static Wt::Dbo::Query getUIQuery(Wt::Dbo::Session& session, SearchFilter filter); + static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector& columnNames = std::vector()); + + + // Accessosrs + std::string getName() const { return _name; } + std::string getMBID() const { return _MBID; } + bool isNone(void) const; + boost::posix_time::time_duration getDuration(void) const; + + void setMBID(std::string mbid) { _MBID = mbid; } + + template + void persist(Action& a) + { + Wt::Dbo::field(a, _name, "name"); + Wt::Dbo::field(a, _MBID, "mbid"); + + Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release"); + } + + private: + static Wt::Dbo::Query getQuery(Wt::Dbo::Session& session, SearchFilter filter); + + static const std::size_t _maxNameLength = 128; + + std::string _name; + std::string _MBID; + + Wt::Dbo::collection< Wt::Dbo::ptr > _tracks; // Tracks in the release +}; + +} // namespace Database + +#endif + diff --git a/src/database/SearchFilter.cpp b/src/database/SearchFilter.cpp new file mode 100644 index 00000000..e5bcca35 --- /dev/null +++ b/src/database/SearchFilter.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "SearchFilter.hpp" + +namespace Database +{ + +SqlQuery generatePartialQuery(SearchFilter& filter) +{ + SqlQuery sqlQuery; + + // Process name like parameters + for (auto nameLikeMatches : filter.nameLikeMatch) + { + WhereClause likeWhereClause; + + for (auto nameLikeMatch : nameLikeMatches) + { + + // Artist + switch (nameLikeMatch.first) + { + case SearchFilter::Field::Artist: + for (const std::string& name : nameLikeMatch.second) + likeWhereClause.Or( WhereClause("a.name LIKE ?") ).bind("%%" + name + "%%"); + break; + + case SearchFilter::Field::Release: + for (const std::string& name : nameLikeMatch.second) + likeWhereClause.Or( WhereClause("r.name LIKE ?") ).bind("%%" + name + "%%"); + break; + + case SearchFilter::Field::Genre: + for (const std::string& name : nameLikeMatch.second) + likeWhereClause.Or( WhereClause("g.name LIKE ?") ).bind("%%" + name + "%%"); + break; + + case SearchFilter::Field::Track: + for (const std::string& name : nameLikeMatch.second) + likeWhereClause.Or( WhereClause("t.name LIKE ?") ).bind("%%" + name + "%%"); + break; + } + } + sqlQuery.where().And( likeWhereClause ); + } + + // Process id exact match parameters + + for (auto idMatch : filter.idMatch) + { + WhereClause idWhereClause; + + switch (idMatch.first) + { + case SearchFilter::Field::Artist: + for (auto id : idMatch.second) + idWhereClause.Or( WhereClause("a.id = ?") ).bind( std::to_string(id)); + break; + case SearchFilter::Field::Release: + for (auto id : idMatch.second) + idWhereClause.Or( WhereClause("r.id = ?") ).bind( std::to_string(id)); + break; + case SearchFilter::Field::Genre: + for (auto id : idMatch.second) + idWhereClause.Or( WhereClause("g.id = ?") ).bind( std::to_string(id)); + break; + case SearchFilter::Field::Track: + for (auto id : idMatch.second) + idWhereClause.Or( WhereClause("t.id = ?") ).bind( std::to_string(id)); + break; + } + + sqlQuery.where().And( idWhereClause ); + } + + return sqlQuery; +} + +} // namespace Database + diff --git a/src/database/SearchFilter.hpp b/src/database/SearchFilter.hpp new file mode 100644 index 00000000..10c27d2d --- /dev/null +++ b/src/database/SearchFilter.hpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#ifndef _DB_SEARCH_FILTER_HPP_ +#define _DB_SEARCH_FILTER_HPP_ + +#include +#include +#include + +#include + +#include "SqlQuery.hpp" + +namespace Database +{ + +class SearchFilter +{ + public: + + enum class Field { + Artist, // artist + Release, // release + Genre, // genre + Track, // track + }; + + typedef std::vector > > NameLikeMatchType; + typedef std::map > IdMatchType; + + SearchFilter() {} + + static SearchFilter IdMatch( const IdMatchType& _idMatch ) + { + return SearchFilter(_idMatch); + } + + static SearchFilter NameLikeMatch( const NameLikeMatchType& _nameLikeMatch ) + { + return SearchFilter(_nameLikeMatch); + } + + // The filter is a AND of the following conditions: + + // ((Field1.name LIKE STR1-1 OR Field1.name LIKE STR1-2 ...) OR (Field2.name LIKE STR2-1 OR Field2.name LIKE STR2-2 ...) ... + NameLikeMatchType nameLikeMatch; + + // ((Field1.id = ID1-1 OR Field1.id = ID1-2 ... ) AND ((Field2.id = ID2-1 OR Field2.id = ID2-2 ... ) ... + IdMatchType idMatch; + + private: + + SearchFilter(const NameLikeMatchType& _nameLikeMatch) : nameLikeMatch(_nameLikeMatch) {} + SearchFilter(const IdMatchType& _idMatch) : idMatch(_idMatch) {} +}; + +SqlQuery generatePartialQuery(SearchFilter& filter); + +} // namespace Database + +#endif // _DB_SEARCH_FILTER_HPP_ diff --git a/src/database/Track.cpp b/src/database/Track.cpp index 930887ae..850e72c6 100644 --- a/src/database/Track.cpp +++ b/src/database/Track.cpp @@ -17,8 +17,6 @@ * along with LMS. If not, see . */ -#include - #include #include "logger/Logger.hpp" @@ -29,104 +27,6 @@ namespace Database { -static SqlQuery generatePartialQuery(SearchFilter& filter, bool forceGenreInnerJoin = false) -{ - bool genreJoin = forceGenreInnerJoin; - SqlQuery sqlQuery; - - - // Process like searches - BOOST_FOREACH(SearchFilter::FieldValues& likeMatch, filter.likeMatches) - { - WhereClause likeWhereClause; - - // Artist - { - WhereClause whereClause; - - BOOST_FOREACH(const std::string& name, likeMatch[SearchFilter::Field::Artist]) - whereClause.Or( WhereClause("t.artist_name LIKE ?") ).bind("%%" + name + "%%"); - - likeWhereClause.Or( whereClause ); - } - // Release - { - WhereClause whereClause; - - BOOST_FOREACH(const std::string& name, likeMatch[SearchFilter::Field::Release]) - whereClause.Or( WhereClause("t.release_name LIKE ?") ).bind("%%" + name + "%%"); - - likeWhereClause.Or( whereClause ); - } - // Genre - { - WhereClause whereClause; - - BOOST_FOREACH(const std::string& name, likeMatch[SearchFilter::Field::Genre]) - { - whereClause.Or( WhereClause("g.name LIKE ?") ).bind("%%" + name + "%%"); - genreJoin = true; - } - - likeWhereClause.Or( whereClause ); - } - // Track - { - WhereClause whereClause; - - BOOST_FOREACH(const std::string& name, likeMatch[SearchFilter::Field::Track]) - whereClause.Or( WhereClause("t.name LIKE ?") ).bind("%%" + name + "%%"); - - likeWhereClause.Or( whereClause ); - } - - sqlQuery.where().And( likeWhereClause ); - } - - // Add exact search constraints - // Artist - { - WhereClause whereClause; - - BOOST_FOREACH(const std::string& name, filter.exactMatch[SearchFilter::Field::Artist]) - whereClause.Or( WhereClause("t.artist_name = ?") ).bind(name); - - sqlQuery.where().And( whereClause ); - } - - // Release - { - WhereClause whereClause; - - BOOST_FOREACH(const std::string& name, filter.exactMatch[SearchFilter::Field::Release]) - whereClause.Or( WhereClause("t.release_name = ?") ).bind(name); - - sqlQuery.where().And( whereClause ); - } - - // Genre - { - WhereClause whereClause; - - BOOST_FOREACH(const std::string& name, filter.exactMatch[SearchFilter::Field::Genre]) - { - whereClause.Or( WhereClause("g.name = ?") ).bind(name); - genreJoin = true; - } - - sqlQuery.where().And( whereClause ); - } - - if (genreJoin) - { - sqlQuery.innerJoin().And( InnerJoinClause("genre g ON g.id = t_g.genre_id")); - sqlQuery.innerJoin().And( InnerJoinClause("track_genre t_g ON t_g.track_id = t.id")); - } - - return sqlQuery; -} - - Track::Track(const boost::filesystem::path& p) : _trackNumber(0), @@ -148,7 +48,7 @@ Track::setGenres(std::vector genres) if (_genres.size()) _genres.clear(); - BOOST_FOREACH(Genre::pointer genre, genres) { + for (Genre::pointer genre : genres) { _genres.insert( genre ); } } @@ -156,7 +56,7 @@ Track::setGenres(std::vector genres) Track::pointer Track::getByPath(Wt::Dbo::Session& session, const boost::filesystem::path& p) { - return session.find().where("path = ?").bind(p.string()); + return session.find().where("file_path = ?").bind(p.string()); } Track::pointer @@ -165,6 +65,11 @@ Track::getById(Wt::Dbo::Session& session, id_type id) return session.find().where("id = ?").bind(id); } +Track::pointer +Track::getByMBID(Wt::Dbo::Session& session, const std::string& mbid) +{ + return session.find().where("mbid = ?").bind(mbid); +} Track::pointer Track::create(Wt::Dbo::Session& session, const boost::filesystem::path& p) @@ -181,104 +86,52 @@ Track::getGenres(void) const } Wt::Dbo::Query< Track::pointer > -Track::getAllQuery(Wt::Dbo::Session& session, SearchFilter filter) +Track::getQuery(Wt::Dbo::Session& session, SearchFilter filter) { SqlQuery sqlQuery = generatePartialQuery(filter); Wt::Dbo::Query query - = session.query( "SELECT t FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.id").orderBy("t.artist_name,t.date,t.release_name,t.disc_number,t.track_number"); + = session.query( "SELECT t 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()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number"); - BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) + for (const std::string& bindArg : sqlQuery.where().getBindArgs()) + query.bind(bindArg); + + return query; +} + +Wt::Dbo::Query< Track::UIQueryResult > +Track::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) +{ + SqlQuery sqlQuery = generatePartialQuery(filter); + + Wt::Dbo::Query query + = session.query( "SELECT t.id, a.name, r.name, t.disc_number, t.track_number, t.name, t.duration, t.date, t.original_date, t.genre_list 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()).groupBy("t.id").orderBy("a.name,t.date,r.name,t.disc_number,t.track_number"); + + for (const std::string& bindArg : sqlQuery.where().getBindArgs()) query.bind(bindArg); return query; } std::vector -Track::getAll(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) +Track::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) { - Wt::Dbo::collection res = getAllQuery(session, filter).limit(size).offset(offset); - return std::vector(res.begin(), res.end()); -} + Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset); -std::vector -Track::getTracks(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) -{ - return getAll(session, filter, offset, size); -} - -Wt::Dbo::Query -Track::getReleasesQuery(Wt::Dbo::Session& session, SearchFilter filter) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); - - Wt::Dbo::Query query - = session.query("SELECT t.release_name, t.date, COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.release_name").orderBy("t.release_name"); - - BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) - query.bind(bindArg); - - return query; + return std::vector(res.begin(), res.end()); } void -Track::updateReleaseQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) +Track::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< UIQueryResult >& model, SearchFilter filter, const std::vector& columnNames) { - Wt::Dbo::Query query = getReleasesQuery(session, filter); - - model.setQuery(query, columnNames.empty() ? true : false); - - // TODO do something better - if (columnNames.size() == 3) - { - model.addColumn( "t.release_name", columnNames[0]); - model.addColumn( "t.date", columnNames[1]); - model.addColumn( "COUNT(DISTINCT t.id)", columnNames[2] ); - } -} - -Wt::Dbo::Query -Track::getArtistsQuery(Wt::Dbo::Session& session, SearchFilter filter) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); - - Wt::Dbo::Query query - = session.query( "SELECT t.artist_name, COUNT(DISTINCT t.release_name), COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("t.artist_name").orderBy("t.artist_name"); - - BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) - query.bind(bindArg); - - return query; -} - -void -Track::updateArtistQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) -{ - Wt::Dbo::Query query = getArtistsQuery(session, filter); - - model.setQuery(query, columnNames.empty() ? true : false); - - // TODO do something better - if (columnNames.size() == 3) - { - model.addColumn( "t.artist_name", columnNames.at(0)); - model.addColumn( "COUNT(DISTINCT t.release_name)", columnNames.at(1) ); - model.addColumn( "COUNT(DISTINCT t.id)", columnNames.at(2) ); - } -} - - -void -Track::updateTracksQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< pointer >& model, SearchFilter filter, const std::vector& columnNames) -{ - Wt::Dbo::Query< pointer > query = getAllQuery(session, filter); + Wt::Dbo::Query< UIQueryResult > query = getUIQuery(session, filter); model.setQuery(query, columnNames.empty() ? true : false); // TODO do something better if (columnNames.size() == 9) { - model.addColumn( "t.artist_name", columnNames[0] ); - model.addColumn( "t.release_name", columnNames[1] ); + model.addColumn( "a.name", columnNames[0] ); + model.addColumn( "r.name", columnNames[1] ); model.addColumn( "t.disc_number", columnNames[2] ); model.addColumn( "t.track_number", columnNames[3] ); model.addColumn( "t.name", columnNames[4] ); @@ -290,48 +143,6 @@ Track::updateTracksQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< po } -std::vector -Track::getArtists(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); - - Wt::Dbo::Query query - = session.query( "SELECT DISTINCT t.artist_name FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).offset(offset).limit(size).orderBy("t.artist_name"); - - BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) - query.bind(bindArg); - - typedef Wt::Dbo::collection< std::string > ArtistNames; - ArtistNames artistNames(query); - - std::vector res; - for (ArtistNames::const_iterator it = artistNames.begin(); it != artistNames.end(); ++it) - res.push_back((*it)); - - return res; -} - -std::vector -Track::getReleases(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) -{ - SqlQuery sqlQuery = generatePartialQuery(filter); - - Wt::Dbo::Query query - = session.query( "SELECT DISTINCT t.release_name FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).offset(offset).limit(size).orderBy("t.release_name"); - - BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) - query.bind(bindArg); - - typedef Wt::Dbo::collection< std::string > ReleaseNames; - ReleaseNames releaseNames(query); - - std::vector res; - for (ReleaseNames::const_iterator it = releaseNames.begin(); it != releaseNames.end(); ++it) - res.push_back((*it)); - - return res; -} - Genre::Genre() { } @@ -341,6 +152,12 @@ Genre::Genre(const std::string& name) { } +std::vector +Genre::getAll(Wt::Dbo::Session& session, int offset, int size) +{ + Wt::Dbo::collection res = session.find().offset(offset).limit(size); + return std::vector(res.begin(), res.end()); +} Genre::pointer Genre::getByName(Wt::Dbo::Session& session, const std::string& name) @@ -370,30 +187,38 @@ Genre::create(Wt::Dbo::Session& session, const std::string& name) return session.add(new Genre(name)); } -Wt::Dbo::collection -Genre::getAll(Wt::Dbo::Session& session, int offset, int size) +Wt::Dbo::Query +Genre::getQuery(Wt::Dbo::Session& session, SearchFilter filter) { - return session.find().offset(offset).limit(size).orderBy("name"); + SqlQuery sqlQuery = generatePartialQuery(filter); + + Wt::Dbo::Query query + = session.query( "SELECT g FROM genre g INNER JOIN track_genre t_g ON t_g.genre_id = g.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_g.track_id " + sqlQuery.where().get()).groupBy("g.name").orderBy("g.name"); + + for (const std::string& bindArg : sqlQuery.where().getBindArgs()) + query.bind(bindArg); + + return query; } -Wt::Dbo::Query -Genre::getAllQuery(Wt::Dbo::Session& session, SearchFilter& filter) +Wt::Dbo::Query +Genre::getUIQuery(Wt::Dbo::Session& session, SearchFilter filter) { - SqlQuery sqlQuery = generatePartialQuery(filter, true); + SqlQuery sqlQuery = generatePartialQuery(filter); - Wt::Dbo::Query query - = session.query( "SELECT g.name, COUNT(DISTINCT t.id) FROM track t " + sqlQuery.innerJoin().get() + " " + sqlQuery.where().get()).groupBy("g.name").orderBy("g.name"); + Wt::Dbo::Query query + = session.query( "SELECT g.id, g.name, COUNT(DISTINCT t.id) FROM genre g INNER JOIN track_genre t_g ON t_g.genre_id = g.id INNER JOIN artist a ON t.artist_id = a.id INNER JOIN release r ON r.id = t.release_id INNER JOIN track t ON t.id = t_g.track_id " + sqlQuery.where().get()).groupBy("g.name").orderBy("g.name"); - BOOST_FOREACH(const std::string& bindArg, sqlQuery.where().getBindArgs()) + for (const std::string& bindArg : sqlQuery.where().getBindArgs()) query.bind(bindArg); return query; } void -Genre::updateGenreQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) +Genre::updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames) { - Wt::Dbo::Query query = getAllQuery(session, filter); + Wt::Dbo::Query query = getUIQuery(session, filter); model.setQuery(query, columnNames.empty() ? true : false); // TODO do something better @@ -404,5 +229,13 @@ Genre::updateGenreQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel +Genre::getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset, int size) +{ + Wt::Dbo::collection res = getQuery(session, filter).limit(size).offset(offset); + + return std::vector(res.begin(), res.end()); +} + } // namespace Database diff --git a/src/database/Track.hpp b/src/database/Track.hpp index 9bd59df1..3d0e7fe5 100644 --- a/src/database/Track.hpp +++ b/src/database/Track.hpp @@ -32,25 +32,9 @@ namespace Database { -// Find utilities -struct SearchFilter -{ - enum class Field { - Artist, // artist name - Release, // release name - Genre, // genre name - Track, // track name - }; - - typedef std::map > FieldValues; - - // Formulas : - // ((like1-1 LIKE ? OR like1-2 LIKE = ? ...) AND (like2-1 LIKE ? OR like2-2 LIKE ? ...)) AND exact1 = ? AND exact2 = ? ... - - std::vector likeMatches; - FieldValues exactMatch; -}; +class Artist; +class Release; class Track; class PlaylistEntry; @@ -67,10 +51,14 @@ class Genre // Find utility static pointer getByName(Wt::Dbo::Session& session, const std::string& name); static pointer getNone(Wt::Dbo::Session& session); - static Wt::Dbo::collection getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1); - typedef boost::tuple GenreResult; - static Wt::Dbo::Query getAllQuery(Wt::Dbo::Session& session, SearchFilter& filter); - static void updateGenreQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames = std::vector()); + static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); + static std::vector getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1); + + // MVC models for the user interface + // Genre ID, name, track count + typedef boost::tuple UIQueryResult; + static Wt::Dbo::Query getUIQuery(Wt::Dbo::Session& session, SearchFilter filter); + static void updateUIQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames = std::vector()); // Create utility static pointer create(Wt::Dbo::Session& session, const std::string& name); @@ -88,6 +76,8 @@ class Genre } private: + static Wt::Dbo::Query getQuery(Wt::Dbo::Session& session, SearchFilter filter); + static const std::size_t _maxNameLength = 128; std::string _name; @@ -114,22 +104,28 @@ class Track // Find utility functions 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 getAll(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); - static std::vector getTracks(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); - static std::vector getReleases(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); - static std::vector getArtists(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); + static std::vector getByFilter(Wt::Dbo::Session& session, SearchFilter filter, int offset = -1, int size = -1); // Utility fonctions // MVC models for the user interface - static void updateTracksQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel< pointer >& model, SearchFilter filter, const std::vector& columnNames = std::vector()); - // Release name, year, track counts - typedef boost::tuple ReleaseResult; - static void updateReleaseQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames = std::vector()); - // Artist name, albums, tracks - typedef boost::tuple ArtistResult; - static void updateArtistQueryModel(Wt::Dbo::Session& session, Wt::Dbo::QueryModel& model, SearchFilter filter, const std::vector& columnNames = std::vector()); + // ID, Artist name, Release Name, DiscNumber, TrackNumber, Name, duration, date, original date, genre list + typedef boost::tuple // genre list + UIQueryResult; + 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& columnNames = std::vector()); // Create utility static pointer create(Wt::Dbo::Session& session, const boost::filesystem::path& p); @@ -138,32 +134,35 @@ class Track void setTrackNumber(int num) { _trackNumber = num; } void setDiscNumber(int num) { _discNumber = num; } void setName(const std::string& name) { _name = std::string(name, 0, _maxNameLength); } - void setArtistName(const std::string& name) { _artistName = std::string(name, 0, _maxNameLength); } - void setReleaseName(const std::string& name) { _releaseName = std::string(name, 0, _maxNameLength); } void setDuration(boost::posix_time::time_duration duration) { _duration = duration; } void setLastWriteTime(boost::posix_time::ptime time) { _fileLastWrite = time; } + void setAddedTime(boost::posix_time::ptime time) { _fileAdded = time; } void setChecksum(const std::vector& checksum) { _fileChecksum = checksum; } void setDate(const boost::posix_time::ptime& date) { _date = date; } void setOriginalDate(const boost::posix_time::ptime& date) { _originalDate = date; } void setGenres(const std::string& genreList) { _genreList = genreList; } - void setGenres(std::vector genres); void setCoverType(CoverType coverType) { _coverType = coverType; } + void setMBID(const std::string& MBID) { _MBID = MBID; } + void setArtist(Wt::Dbo::ptr artist) { _artist = artist; } + void setRelease(Wt::Dbo::ptr release) { _release = release; } + void setGenres(std::vector genres); int getTrackNumber(void) const { return _trackNumber; } int getDiscNumber(void) const { return _discNumber; } std::string getName(void) const { return _name; } - std::string getArtistName(void) const { return _artistName; } - std::string getReleaseName(void) const { return _releaseName; } boost::filesystem::path getPath(void) const { return _filePath; } boost::posix_time::time_duration getDuration(void) const { return _duration; } boost::posix_time::ptime getDate(void) const { return _date; } boost::posix_time::ptime getOriginalDate(void) const { return _originalDate; } - bool hasGenre(Genre::pointer genre) const { return _genres.count(genre); } - std::vector< Genre::pointer > getGenres(void) const; - boost::posix_time::ptime getLastWriteTime(void) const { return _fileLastWrite; } + boost::posix_time::ptime getAddedTime(void) const { return _fileAdded; } const std::vector& getChecksum(void) const { return _fileChecksum; } CoverType getCoverType(void) const { return _coverType; } + const std::string& getMBID(void) const { return _MBID; } + Wt::Dbo::ptr getArtist(void) const { return _artist; } + Wt::Dbo::ptr getRelease(void) const { return _release; } + std::vector< Genre::pointer > getGenres(void) const; + bool hasGenre(Genre::pointer genre) const { return _genres.count(genre); } template void persist(Action& a) @@ -171,25 +170,25 @@ class Track Wt::Dbo::field(a, _trackNumber, "track_number"); Wt::Dbo::field(a, _discNumber, "disc_number"); Wt::Dbo::field(a, _name, "name"); - Wt::Dbo::field(a, _artistName, "artist_name"); - Wt::Dbo::field(a, _releaseName, "release_name"); Wt::Dbo::field(a, _duration, "duration"); Wt::Dbo::field(a, _date, "date"); Wt::Dbo::field(a, _originalDate, "original_date"); Wt::Dbo::field(a, _genreList, "genre_list"); - Wt::Dbo::field(a, _filePath, "path"); - Wt::Dbo::field(a, _fileLastWrite, "last_write"); + Wt::Dbo::field(a, _filePath, "file_path"); + Wt::Dbo::field(a, _fileLastWrite, "file_last_write"); + Wt::Dbo::field(a, _fileAdded, "file_added"); Wt::Dbo::field(a, _fileChecksum, "checksum"); Wt::Dbo::field(a, _coverType, "cover_type"); + Wt::Dbo::field(a, _MBID, "mbid"); + Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade); Wt::Dbo::hasMany(a, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade); Wt::Dbo::hasMany(a, _playlistEntries, Wt::Dbo::ManyToOne, "track"); } private: - static Wt::Dbo::Query< pointer > getAllQuery(Wt::Dbo::Session& session, SearchFilter filter); - static Wt::Dbo::Query getReleasesQuery(Wt::Dbo::Session& session, SearchFilter filter); - static Wt::Dbo::Query getArtistsQuery(Wt::Dbo::Session& session, SearchFilter filter); + static Wt::Dbo::Query< pointer > getQuery(Wt::Dbo::Session& session, SearchFilter filter); static const std::size_t _maxNameLength = 128; @@ -200,14 +199,18 @@ class Track std::string _releaseName; boost::posix_time::time_duration _duration; boost::posix_time::ptime _date; - boost::posix_time::ptime _originalDate; + boost::posix_time::ptime _originalDate; // original date time std::string _genreList; std::string _filePath; std::vector _fileChecksum; boost::posix_time::ptime _fileLastWrite; + boost::posix_time::ptime _fileAdded; CoverType _coverType; + std::string _MBID; // Musicbrainz Identifier - Wt::Dbo::collection< Genre::pointer > _genres; // Genres that are related to this track + Wt::Dbo::ptr _artist; + Wt::Dbo::ptr _release; + Wt::Dbo::collection< Genre::pointer > _genres; // Genres that are related to this track Wt::Dbo::collection< Wt::Dbo::ptr > _playlistEntries; }; diff --git a/src/database/Types.hpp b/src/database/Types.hpp index 82385590..a04b978b 100644 --- a/src/database/Types.hpp +++ b/src/database/Types.hpp @@ -19,8 +19,10 @@ // header file aimed to ease database class declarations +#include "Artist.hpp" #include "Track.hpp" #include "Playlist.hpp" +#include "Release.hpp" #include "Video.hpp" #include "MediaDirectory.hpp" #include "User.hpp" diff --git a/src/remote/messages/Header.hpp b/src/lms-api/messages/Header.hpp similarity index 98% rename from src/remote/messages/Header.hpp rename to src/lms-api/messages/Header.hpp index e98ac5b2..6744b343 100644 --- a/src/remote/messages/Header.hpp +++ b/src/lms-api/messages/Header.hpp @@ -24,7 +24,7 @@ #include -namespace Remote +namespace LmsAPI { @@ -104,7 +104,7 @@ class Header -} // namespace Remote +} // namespace LmsAPI #endif diff --git a/src/lms-api/proto/auth.proto b/src/lms-api/proto/auth.proto new file mode 100644 index 00000000..b054ef06 --- /dev/null +++ b/src/lms-api/proto/auth.proto @@ -0,0 +1,67 @@ +/* + * 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 . + */ + +import "common.proto"; + +package LmsAPI; + +message AuthRequest +{ + message Password + { + required string user_login = 1; + required string user_password = 2; + } + + enum Type + { + TypePassword = 1; + } + + required Type type = 1; + + optional Password password = 2; +} + +message AuthResponse +{ + + message PasswordResult + { + enum Type + { + TypePasswordInvalid = 1; + TypeLoginThrottling = 2; // The attempt was not processed because of throttling + TypePasswordValid = 3; + } + required Type type = 1; + + optional uint32 delay = 2; // in seconds, in case of PasswordInvalid/LoginThrottling + } + + enum Type + { + TypePasswordResult = 1; + } + + required Type type = 1; + + optional PasswordResult password_result = 2; +} + diff --git a/src/remote/proto/collection.proto b/src/lms-api/proto/collection.proto similarity index 54% rename from src/remote/proto/collection.proto rename to src/lms-api/proto/collection.proto index 0af2ea6d..8037ab02 100644 --- a/src/remote/proto/collection.proto +++ b/src/lms-api/proto/collection.proto @@ -1,10 +1,37 @@ +/* + * 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 . + */ + import "common.proto"; -package Remote; +package LmsAPI; message AudioCollectionRequest { + message SearchFilter + { + repeated uint64 artist_id = 1; // has one of these artists + repeated uint64 genre_id = 2; // has one of these genres + repeated uint64 release_id = 3; // hase one of these releases + repeated uint64 track_id = 4; // has one of theses tracks + } + message BatchParameter { required uint32 offset = 1; // First element requested @@ -14,30 +41,25 @@ message AudioCollectionRequest message GetGenreList { required BatchParameter batch_parameter = 1; + optional SearchFilter search_filter = 2; } message GetArtistList { required BatchParameter batch_parameter = 1; - // Search filters - repeated string genre = 2; // Artist that has at least a track of the genre + optional SearchFilter search_filter = 2; } message GetReleaseList { required BatchParameter batch_parameter = 1; - // Search filters - repeated string artist = 2; // Release that contains at least a track of one of these artists - repeated string genre = 3; // Release that has at least a track of the genre + optional SearchFilter search_filter = 2; } message GetTrackList { required BatchParameter batch_parameter = 1; - // Search filters - repeated string artist = 2; // Track that belongs to these artists - repeated string release = 3; // Track that is part of these releases - repeated string genre = 4; // Track that has at least a track of the genre + optional SearchFilter search_filter = 2; } message GetCoverArt @@ -50,7 +72,7 @@ message AudioCollectionRequest required Type type = 1; - optional string release = 2; // Release that owns the cover art + optional uint64 release_id = 2; // Release that owns the cover art optional uint64 track_id = 3; // Track that owns the cover art required uint32 size = 4; // Scale image to size*size pixels. Set 0 to get the biggest image @@ -112,33 +134,42 @@ message AudioCollectionResponse message Genre { - required string name = 1; + required uint64 id = 1; + optional string mbid = 2; + required string name = 3; } message Artist { - required string name = 1; + required uint64 id = 1; + optional string mbid = 2; + required string name = 3; } message Release { - required string name = 1; + required uint64 id = 1; + optional string mbid = 2; + required string name = 3; } message Track { required uint64 id = 1; // Track id + optional string mbid = 2; - required string artist = 2; - required string release = 3; - repeated string genre = 4; + required uint64 artist_id = 3; + required uint64 release_id = 4; + repeated uint64 genre_id = 5; - optional uint32 disc_number = 5; - optional uint32 track_number = 6; - required string name = 7; - required uint32 duration_secs = 8; - optional string release_date = 9; - optional string original_release_date = 10; + optional string artist_mbid = 6; + optional string release_mbid = 7; + optional uint32 disc_number = 8; + optional uint32 track_number = 9; + required string name = 10; + required uint32 duration_secs = 11; + optional string release_date = 12; + optional string original_release_date = 13; } enum Type { diff --git a/src/lms-api/proto/common.proto b/src/lms-api/proto/common.proto new file mode 100644 index 00000000..3042d84d --- /dev/null +++ b/src/lms-api/proto/common.proto @@ -0,0 +1,27 @@ +/* + * 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 . + */ + +package LmsAPI; + +message Error +{ + required bool error = 1; + optional string message = 2; +} + diff --git a/src/remote/proto/media.proto b/src/lms-api/proto/media.proto similarity index 76% rename from src/remote/proto/media.proto rename to src/lms-api/proto/media.proto index acb5d787..5dae7257 100644 --- a/src/remote/proto/media.proto +++ b/src/lms-api/proto/media.proto @@ -1,8 +1,25 @@ +/* + * 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 . + */ + import "common.proto"; -package Remote; - - +package LmsAPI; message MediaRequest { diff --git a/src/remote/proto/messages.proto b/src/lms-api/proto/messages.proto similarity index 62% rename from src/remote/proto/messages.proto rename to src/lms-api/proto/messages.proto index b9b2f704..7ba930c3 100644 --- a/src/remote/proto/messages.proto +++ b/src/lms-api/proto/messages.proto @@ -1,9 +1,29 @@ +/* + * 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 . + */ + import "common.proto"; + import "auth.proto"; import "collection.proto"; import "media.proto"; -package Remote; +package LmsAPI; // Every request sent by the client must be a ClientMessage // The server will answer using a ServerMessage diff --git a/src/remote/server/AudioCollectionRequestHandler.cpp b/src/lms-api/server/AudioCollectionRequestHandler.cpp similarity index 72% rename from src/remote/server/AudioCollectionRequestHandler.cpp rename to src/lms-api/server/AudioCollectionRequestHandler.cpp index 009997ee..2f4a5427 100644 --- a/src/remote/server/AudioCollectionRequestHandler.cpp +++ b/src/lms-api/server/AudioCollectionRequestHandler.cpp @@ -21,7 +21,6 @@ #include #include -#include #include "logger/Logger.hpp" @@ -30,16 +29,34 @@ #include "database/Types.hpp" #include "cover/CoverArtGrabber.hpp" -namespace Remote { +namespace LmsAPI { namespace Server { using namespace Database; -AudioCollectionRequestHandler::AudioCollectionRequestHandler(Database::Handler& db) +static SearchFilter SearchFilterFromRequest(const AudioCollectionRequest_SearchFilter& request) +{ + SearchFilter filter; + + for (int id = 0; id < request.artist_id_size(); ++id) + filter.idMatch[SearchFilter::Field::Artist].push_back( request.artist_id(id) ); + + for (int id = 0; id < request.genre_id_size(); ++id) + filter.idMatch[SearchFilter::Field::Genre].push_back( request.genre_id(id) ); + + for (int id = 0; id < request.release_id_size(); ++id) + filter.idMatch[SearchFilter::Field::Release].push_back( request.release_id(id) ); + + for (int id = 0; id < request.track_id_size(); ++id) + filter.idMatch[SearchFilter::Field::Track].push_back( request.track_id(id) ); + + return filter; +} + +AudioCollectionRequestHandler::AudioCollectionRequestHandler(Handler& db) : _db(db) {} - bool AudioCollectionRequestHandler::process(const AudioCollectionRequest& request, AudioCollectionResponse& response) { @@ -133,17 +150,21 @@ AudioCollectionRequestHandler::processGetGenres(const AudioCollectionRequest::Ge size = _maxListArtists; size = std::min(size, _maxListArtists); + // Get filters + SearchFilter filter; + if (request.has_search_filter()) + filter = SearchFilterFromRequest(request.search_filter()); + Wt::Dbo::Transaction transaction( _db.getSession() ); - Wt::Dbo::collection genres = Database::Genre::getAll( _db.getSession(), request.batch_parameter().offset(), static_cast(size)); + std::vector genres = Genre::getByFilter( _db.getSession(), filter, request.batch_parameter().offset(), static_cast(size)); - typedef Wt::Dbo::collection< Database::Genre::pointer > Genres; - - for (Genres::const_iterator it = genres.begin(); it != genres.end(); ++it) + for (Genre::pointer genre : genres) { - AudioCollectionResponse_Genre* genre = response.add_genres(); + AudioCollectionResponse_Genre* addGenre = response.add_genres(); - genre->set_name( std::string( boost::locale::conv::to_utf((*it)->getName(), "UTF-8") ) ); + addGenre->set_id(genre.id()); + addGenre->set_name( std::string( boost::locale::conv::to_utf(genre->getName(), "UTF-8") ) ); } return true; @@ -164,23 +185,28 @@ AudioCollectionRequestHandler::processGetArtists(const AudioCollectionRequest::G size = _maxListArtists; size = std::min(size, _maxListArtists); - SearchFilter filter; - // Get filters - std::vector genres; - for (int id = 0; id < request.genre_size(); ++id) - filter.exactMatch[SearchFilter::Field::Genre].push_back( request.genre(id) ); + SearchFilter filter; + if (request.has_search_filter()) + filter = SearchFilterFromRequest(request.search_filter()); // Now fetch requested data... Wt::Dbo::Transaction transaction( _db.getSession() ); - std::vector artists - = Database::Track::getArtists(_db.getSession(), filter, + std::vector artists + = Artist::getByFilter(_db.getSession(), filter, request.batch_parameter().offset(), static_cast(size) ); - BOOST_FOREACH(const std::string& artist, artists) - response.add_artists()->set_name( std::string( boost::locale::conv::to_utf(artist, "UTF-8") ) ); + for (Artist::pointer artist : artists) + { + AudioCollectionResponse_Artist *addArtist = response.add_artists(); + + addArtist->set_id(artist.id()); + addArtist->set_name( std::string( boost::locale::conv::to_utf(artist->getName(), "UTF-8") ) ); + if (!artist->getMBID().empty()) + addArtist->set_mbid(artist->getMBID()); + } return true; } @@ -200,25 +226,25 @@ AudioCollectionRequestHandler::processGetReleases(const AudioCollectionRequest:: size = _maxListReleases; size = std::min(size, _maxListReleases); + // Get filters SearchFilter filter; - - std::vector artists; - for (int id = 0; id < request.artist_size(); ++id) - filter.exactMatch[SearchFilter::Field::Artist].push_back( request.artist(id) ); - - std::vector genres; - for (int id = 0; id < request.genre_size(); ++id) - filter.exactMatch[SearchFilter::Field::Genre].push_back( request.genre(id) ); + if (request.has_search_filter()) + filter = SearchFilterFromRequest(request.search_filter()); Wt::Dbo::Transaction transaction( _db.getSession() ); - std::vector releases - = Database::Track::getReleases( _db.getSession(), filter, + std::vector releases + = Release::getByFilter( _db.getSession(), filter, request.batch_parameter().offset(), static_cast(size)); - BOOST_FOREACH(const std::string& release, releases) + for (Release::pointer release : releases) { - response.add_releases()->set_name( std::string( boost::locale::conv::to_utf(release, "UTF-8"))); + AudioCollectionResponse_Release *addRelease = response.add_releases(); + + addRelease->set_id(release.id()); + addRelease->set_name( std::string( boost::locale::conv::to_utf(release->getName(), "UTF-8"))); + if (!release->getMBID().empty()) + addRelease->set_mbid(release->getMBID()); } return true; @@ -241,34 +267,24 @@ AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::Ge // Get filters SearchFilter filter; - - std::vector artists; - for (int id = 0; id < request.artist_size(); ++id) - filter.exactMatch[SearchFilter::Field::Artist].push_back( request.artist(id) ); - - std::vector releases; - for (int id = 0; id < request.release_size(); ++id) - filter.exactMatch[SearchFilter::Field::Release].push_back( request.release(id) ); - - std::vector genres; - for (int id = 0; id < request.genre_size(); ++id) - filter.exactMatch[SearchFilter::Field::Genre].push_back( request.genre(id) ); + if (request.has_search_filter()) + filter = SearchFilterFromRequest(request.search_filter()); Wt::Dbo::Transaction transaction( _db.getSession() ); - std::vector tracks - = Database::Track::getAll( _db.getSession(), filter, + std::vector tracks + = Track::getByFilter( _db.getSession(), filter, request.batch_parameter().offset(), static_cast(size)); - BOOST_FOREACH(Database::Track::pointer track, tracks) + for (Track::pointer track : tracks) { AudioCollectionResponse_Track* newTrack = response.add_tracks(); newTrack->set_id(track.id()); newTrack->set_disc_number( track->getDiscNumber() ); newTrack->set_track_number( track->getTrackNumber() ); - newTrack->set_artist( track->getArtistName() ); - newTrack->set_release( track->getReleaseName() ); + newTrack->set_artist_id( track->getArtist().id() ); + newTrack->set_release_id( track->getRelease().id() ); newTrack->set_name( std::string( boost::locale::conv::to_utf(track->getName(), "UTF-8") ) ); newTrack->set_duration_secs( track->getDuration().total_seconds() ); @@ -278,9 +294,11 @@ AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::Ge newTrack->set_release_date( std::to_string(track->getDate().date().year()) ); if (!track->getOriginalDate().is_special()) newTrack->set_original_release_date( std::to_string(track->getOriginalDate().date().year()) ); + if (!track->getMBID().empty()) + newTrack->set_mbid(track->getMBID()); - BOOST_FOREACH(Database::Genre::pointer genre, track->getGenres()) - newTrack->add_genre( genre->getName() ); + for (Genre::pointer genre : track->getGenres()) + newTrack->add_genre_id( genre.id() ); } @@ -299,9 +317,9 @@ AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest:: switch(request.type()) { case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease: - if (request.has_release()) + if (request.has_release_id()) { - coverArts = CoverArt::Grabber::instance().getFromRelease( _db.getSession(), request.release()); + coverArts = CoverArt::Grabber::instance().getFromRelease( _db.getSession(), request.release_id()); res = true; } break; @@ -315,7 +333,7 @@ AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest:: break; } - BOOST_FOREACH(CoverArt::CoverArt& coverArt, coverArts) + for (CoverArt::CoverArt& coverArt : coverArts) { AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art(); @@ -345,12 +363,12 @@ AudioCollectionRequestHandler::processGetRevision(AudioCollectionResponse::Revis Wt::Dbo::Transaction transaction( _db.getSession() ); - Database::MediaDirectorySettings::pointer settings = Database::MediaDirectorySettings::get( _db.getSession() ); + MediaDirectorySettings::pointer settings = MediaDirectorySettings::get( _db.getSession() ); std::string hashStr = boost::posix_time::to_iso_string(settings->getLastUpdated()); boost::uuids::detail::sha1 s; - BOOST_FOREACH(const char c, hashStr) + for (const char c : hashStr) s.process_byte(c); unsigned int digest[5]; @@ -369,7 +387,7 @@ AudioCollectionRequestHandler::processGetRevision(AudioCollectionResponse::Revis -} // namespace Remote +} // namespace LmsAPI } // namespace Server diff --git a/src/remote/server/AudioCollectionRequestHandler.hpp b/src/lms-api/server/AudioCollectionRequestHandler.hpp similarity index 98% rename from src/remote/server/AudioCollectionRequestHandler.hpp rename to src/lms-api/server/AudioCollectionRequestHandler.hpp index 66c38a1e..5d7522c4 100644 --- a/src/remote/server/AudioCollectionRequestHandler.hpp +++ b/src/lms-api/server/AudioCollectionRequestHandler.hpp @@ -24,7 +24,7 @@ #include "messages.pb.h" -namespace Remote { +namespace LmsAPI { namespace Server { class AudioCollectionRequestHandler @@ -54,7 +54,7 @@ class AudioCollectionRequestHandler static const std::size_t _maxCoverArtSize = 512; // in pixels, square }; -} // namespace Remote +} // namespace LmsAPI } // namespace Server #endif diff --git a/src/remote/server/AuthRequestHandler.cpp b/src/lms-api/server/AuthRequestHandler.cpp similarity index 98% rename from src/remote/server/AuthRequestHandler.cpp rename to src/lms-api/server/AuthRequestHandler.cpp index f8f225c6..b0f98a70 100644 --- a/src/remote/server/AuthRequestHandler.cpp +++ b/src/lms-api/server/AuthRequestHandler.cpp @@ -23,7 +23,7 @@ #include "AuthRequestHandler.hpp" -namespace Remote { +namespace LmsAPI { namespace Server { AuthRequestHandler::AuthRequestHandler(Database::Handler& db) @@ -100,6 +100,6 @@ AuthRequestHandler::processPassword(const AuthRequest::Password& request, AuthRe return res; } -} // namespace Remote } // namespace Server +} // namespace LmsAPI diff --git a/src/remote/server/AuthRequestHandler.hpp b/src/lms-api/server/AuthRequestHandler.hpp similarity index 90% rename from src/remote/server/AuthRequestHandler.hpp rename to src/lms-api/server/AuthRequestHandler.hpp index c7ec1f29..363dcab2 100644 --- a/src/remote/server/AuthRequestHandler.hpp +++ b/src/lms-api/server/AuthRequestHandler.hpp @@ -17,14 +17,14 @@ * along with LMS. If not, see . */ -#ifndef REMOTE_AUTH_REQUEST_HANDLER -#define REMOTE_AUTH_REQUEST_HANDLER +#ifndef LMSAPI_AUTH_REQUEST_HANDLER +#define LMSAPI_AUTH_REQUEST_HANDLER #include "database/DatabaseHandler.hpp" #include "messages.pb.h" -namespace Remote { +namespace LmsAPI { namespace Server { class AuthRequestHandler @@ -41,7 +41,7 @@ class AuthRequestHandler Database::Handler& _db; }; -} // namespace Remote } // namespace Server +} // namespace LmsAPI #endif diff --git a/src/remote/server/Connection.cpp b/src/lms-api/server/Connection.cpp similarity index 90% rename from src/remote/server/Connection.cpp rename to src/lms-api/server/Connection.cpp index 619551af..27203b62 100644 --- a/src/remote/server/Connection.cpp +++ b/src/lms-api/server/Connection.cpp @@ -33,7 +33,7 @@ #include "ConnectionManager.hpp" #include "Connection.hpp" -namespace Remote { +namespace LmsAPI { namespace Server { Connection::Connection(boost::asio::io_service& ioService, @@ -79,11 +79,11 @@ void Connection::readMsg() { // Read a header first - boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(Remote::Header::size); + boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(LmsAPI::Header::size); boost::asio::async_read(_socket, bufs, - boost::asio::transfer_exactly(Remote::Header::size), + boost::asio::transfer_exactly(LmsAPI::Header::size), boost::bind(&Connection::handleReadHeader, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)); @@ -114,9 +114,9 @@ Connection::handleReadHeader(const boost::system::error_code& error, std::size_t { if (!error) { - if (bytes_transferred != Remote::Header::size) + if (bytes_transferred != LmsAPI::Header::size) { - LMS_LOG(MOD_REMOTE, SEV_ERROR) << "bytes_transferred (" << bytes_transferred << ") != Remote::Header::size!"; + LMS_LOG(MOD_REMOTE, SEV_ERROR) << "bytes_transferred (" << bytes_transferred << ") != LmsAPI::Header::size!"; _connectionManager.stop(shared_from_this()); return; } @@ -125,7 +125,7 @@ Connection::handleReadHeader(const boost::system::error_code& error, std::size_t std::istream is(&_inputStreamBuf); - Remote::Header header; + LmsAPI::Header header; if (!header.from_istream(is)) { LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot read header from buffer!"; @@ -161,8 +161,8 @@ Connection::handleReadMsg(const boost::system::error_code& error, std::size_t by std::istream is(&_inputStreamBuf); std::ostream os(&_outputStreamBuf); - Remote::ServerMessage response; - Remote::ClientMessage request; + LmsAPI::ServerMessage response; + LmsAPI::ClientMessage request; if (!request.ParseFromIstream(&is)) { @@ -188,23 +188,23 @@ Connection::handleReadMsg(const boost::system::error_code& error, std::size_t by return; } - if (_outputStreamBuf.size() >= Remote::Header::max_data_size) + if (_outputStreamBuf.size() >= LmsAPI::Header::max_data_size) { - LMS_LOG(MOD_REMOTE, SEV_ERROR) << "output message is too big! " << _outputStreamBuf.size() << " > " << Remote::Header::max_data_size; + LMS_LOG(MOD_REMOTE, SEV_ERROR) << "output message is too big! " << _outputStreamBuf.size() << " > " << LmsAPI::Header::max_data_size; _connectionManager.stop(shared_from_this()); return; } - std::array headerBuffer; + std::array headerBuffer; { - Remote::Header header; + LmsAPI::Header header; header.setDataSize(_outputStreamBuf.size()); header.to_buffer(headerBuffer); } std::size_t n = boost::asio::write(_socket, boost::asio::buffer(headerBuffer), - boost::asio::transfer_exactly(Remote::Header::size), + boost::asio::transfer_exactly(LmsAPI::Header::size), ec); if (ec) { @@ -213,7 +213,7 @@ Connection::handleReadMsg(const boost::system::error_code& error, std::size_t by } else { - assert(n == Remote::Header::size); + assert(n == LmsAPI::Header::size); } // Now send serialized payload @@ -247,4 +247,4 @@ Connection::handleReadMsg(const boost::system::error_code& error, std::size_t by } // namespace Server -} // namespace Remote +} // namespace LmsAPI diff --git a/src/remote/server/Connection.hpp b/src/lms-api/server/Connection.hpp similarity index 96% rename from src/remote/server/Connection.hpp rename to src/lms-api/server/Connection.hpp index d61614e9..72fdca27 100644 --- a/src/remote/server/Connection.hpp +++ b/src/lms-api/server/Connection.hpp @@ -17,8 +17,8 @@ * along with LMS. If not, see . */ -#ifndef REMOTE_CONNECTION_HPP -#define REMOTE_CONNECTION_HPP +#ifndef LMSAPI_CONNECTION_HPP +#define LMSAPI_CONNECTION_HPP #include #include @@ -30,7 +30,7 @@ #include "messages/Header.hpp" -namespace Remote { +namespace LmsAPI { namespace Server { class ConnectionManager; @@ -91,7 +91,7 @@ class Connection : public std::enable_shared_from_this } // namespace Server -} // namespace Remote +} // namespace LmsAPI #endif diff --git a/src/remote/server/ConnectionManager.cpp b/src/lms-api/server/ConnectionManager.cpp similarity index 96% rename from src/remote/server/ConnectionManager.cpp rename to src/lms-api/server/ConnectionManager.cpp index 406d6092..c4fc6eb7 100644 --- a/src/remote/server/ConnectionManager.cpp +++ b/src/lms-api/server/ConnectionManager.cpp @@ -23,7 +23,7 @@ #include "ConnectionManager.hpp" -namespace Remote { +namespace LmsAPI { namespace Server { ConnectionManager::ConnectionManager() @@ -57,6 +57,6 @@ ConnectionManager::stopAll() } // namespace Server -} // namespace Remote +} // namespace LmsAPI diff --git a/src/remote/server/ConnectionManager.hpp b/src/lms-api/server/ConnectionManager.hpp similarity index 97% rename from src/remote/server/ConnectionManager.hpp rename to src/lms-api/server/ConnectionManager.hpp index b3ea48cf..ce2b194a 100644 --- a/src/remote/server/ConnectionManager.hpp +++ b/src/lms-api/server/ConnectionManager.hpp @@ -24,7 +24,7 @@ #include "Connection.hpp" -namespace Remote { +namespace LmsAPI { namespace Server { /// Manages open connections so that they may be cleanly stopped when the server @@ -53,6 +53,6 @@ class ConnectionManager }; } // namespace Server -} // namespace Remote +} // namespace LmsAPI #endif diff --git a/src/remote/server/MediaRequestHandler.cpp b/src/lms-api/server/MediaRequestHandler.cpp similarity index 99% rename from src/remote/server/MediaRequestHandler.cpp rename to src/lms-api/server/MediaRequestHandler.cpp index 7b9734be..be3e6b53 100644 --- a/src/remote/server/MediaRequestHandler.cpp +++ b/src/lms-api/server/MediaRequestHandler.cpp @@ -23,7 +23,7 @@ #include "database/Types.hpp" -namespace Remote { +namespace LmsAPI { namespace Server { MediaRequestHandler::MediaRequestHandler(Database::Handler& db) @@ -211,7 +211,7 @@ MediaRequestHandler::processTerminate(const MediaRequest::Terminate& request, Me -} // namespace Remote +} // namespace LmsAPI } // namespace Server diff --git a/src/remote/server/MediaRequestHandler.hpp b/src/lms-api/server/MediaRequestHandler.hpp similarity index 97% rename from src/remote/server/MediaRequestHandler.hpp rename to src/lms-api/server/MediaRequestHandler.hpp index 675ca59c..f3f30c20 100644 --- a/src/remote/server/MediaRequestHandler.hpp +++ b/src/lms-api/server/MediaRequestHandler.hpp @@ -28,7 +28,7 @@ #include "media.pb.h" -namespace Remote { +namespace LmsAPI { namespace Server { class MediaRequestHandler @@ -58,8 +58,8 @@ class MediaRequestHandler static const std::size_t _maxTranscoders = 1; }; -} // namespace Remote } // namespace Server +} // namespace LmsAPI #endif diff --git a/src/remote/server/RequestHandler.cpp b/src/lms-api/server/RequestHandler.cpp similarity index 98% rename from src/remote/server/RequestHandler.cpp rename to src/lms-api/server/RequestHandler.cpp index f8fd2bba..3f27c21b 100644 --- a/src/remote/server/RequestHandler.cpp +++ b/src/lms-api/server/RequestHandler.cpp @@ -21,7 +21,7 @@ #include "RequestHandler.hpp" -namespace Remote { +namespace LmsAPI { namespace Server { RequestHandler::RequestHandler(boost::filesystem::path dbPath) @@ -95,7 +95,7 @@ RequestHandler::process(const ClientMessage& request, ServerMessage& response) } // namespace Server -} // namespace Remote +} // namespace LmsAPI diff --git a/src/remote/server/RequestHandler.hpp b/src/lms-api/server/RequestHandler.hpp similarity index 97% rename from src/remote/server/RequestHandler.hpp rename to src/lms-api/server/RequestHandler.hpp index 15d1b510..84f184e3 100644 --- a/src/remote/server/RequestHandler.hpp +++ b/src/lms-api/server/RequestHandler.hpp @@ -30,7 +30,7 @@ #include "AudioCollectionRequestHandler.hpp" #include "MediaRequestHandler.hpp" -namespace Remote { +namespace LmsAPI { namespace Server { class RequestHandler @@ -52,7 +52,7 @@ class RequestHandler }; } // namespace Server -} // namespace Remote +} // namespace LmsAPI #endif diff --git a/src/remote/server/Server.cpp b/src/lms-api/server/Server.cpp similarity index 98% rename from src/remote/server/Server.cpp rename to src/lms-api/server/Server.cpp index 50c1063b..6d095521 100644 --- a/src/remote/server/Server.cpp +++ b/src/lms-api/server/Server.cpp @@ -27,7 +27,7 @@ #include "Server.hpp" -namespace Remote { +namespace LmsAPI { namespace Server { Server::Server(const endpoint_type& bindEndpoint, @@ -109,4 +109,4 @@ Server::stop() } } // namespace Server -} // namespace Remote +} // namespace LmsAPI diff --git a/src/remote/server/Server.hpp b/src/lms-api/server/Server.hpp similarity index 98% rename from src/remote/server/Server.hpp rename to src/lms-api/server/Server.hpp index 297a26ac..57b77a2b 100644 --- a/src/remote/server/Server.hpp +++ b/src/lms-api/server/Server.hpp @@ -32,7 +32,7 @@ #include "RequestHandler.hpp" -namespace Remote { +namespace LmsAPI { namespace Server { class Server @@ -76,6 +76,6 @@ class Server }; } // namespace Server -} // namespace Remote +} // namespace LmsAPI #endif diff --git a/src/main/main.cpp b/src/main/main.cpp index afd404cc..c63f7247 100644 --- a/src/main/main.cpp +++ b/src/main/main.cpp @@ -19,6 +19,7 @@ #include +#include "config/config.h" #include "logger/Logger.hpp" #include "config/ConfigReader.hpp" @@ -28,7 +29,9 @@ #include "service/ServiceManager.hpp" #include "service/DatabaseUpdateService.hpp" #include "service/UserInterfaceService.hpp" -#include "service/RemoteServerService.hpp" +#if defined HAVE_LMSAPI +#include "service/LmsAPIServerService.hpp" +#endif int main(int argc, char* argv[]) { @@ -81,9 +84,6 @@ int main(int argc, char* argv[]) Service::UserInterfaceService::Config uiConfig; configReader.getUserInterfaceConfig(uiConfig); - Service::RemoteServerService::Config remoteConfig; - configReader.getRemoteServerConfig(remoteConfig); - Service::ServiceManager& serviceManager = Service::ServiceManager::instance(); // lib init @@ -96,8 +96,13 @@ int main(int argc, char* argv[]) if (dbUpdateConfig.enable) serviceManager.startService( std::make_shared( dbUpdateConfig ) ); - if (remoteConfig.enable) - serviceManager.startService( std::make_shared( remoteConfig )); +#if defined HAVE_LMSAPI + Service::LmsAPIService::Config lmsAPIConfig; + configReader.getLmsAPIConfig(lmsAPIConfig); + + if (lmsAPIConfig.enable) + serviceManager.startService( std::make_shared( lmsAPIConfig )); +#endif if (uiConfig.enable) serviceManager.startService( std::make_shared(boost::filesystem::path(argv[0]), uiConfig)); diff --git a/src/metadata/AvFormat.cpp b/src/metadata/AvFormat.cpp index 821d6191..2ec53409 100644 --- a/src/metadata/AvFormat.cpp +++ b/src/metadata/AvFormat.cpp @@ -161,6 +161,14 @@ AvFormat::parse(const boost::filesystem::path& p, Items& items) items.insert( std::make_pair(MetaData::Type::Genres, genres)); } + else if (boost::iequals(it->first, "MusicBrainz Artist Id")) + { + items.insert( std::make_pair(MetaData::Type::MusicBrainzArtistID, string_trim( string_to_utf8(it->second)) )); + } + else if (boost::iequals(it->first, "MusicBrainz Album Id")) + { + items.insert( std::make_pair(MetaData::Type::MusicBrainzAlbumID, string_trim( string_to_utf8(it->second)) )); + } /* else LMS_LOG(MOD_METADATA, SEV_DEBUG) << "key = " << it->first << ", value = " << it->second; */ diff --git a/src/metadata/MetaData.hpp b/src/metadata/MetaData.hpp index 07dec04d..f872847b 100644 --- a/src/metadata/MetaData.hpp +++ b/src/metadata/MetaData.hpp @@ -43,6 +43,8 @@ namespace MetaData AudioStreams, // vector VideoStreams, // vector SubtitleStreams, // vector + MusicBrainzArtistID, // string + MusicBrainzAlbumID, // string }; // Used by Streams diff --git a/src/remote/proto/auth.proto b/src/remote/proto/auth.proto deleted file mode 100644 index de3bfadd..00000000 --- a/src/remote/proto/auth.proto +++ /dev/null @@ -1,48 +0,0 @@ -import "common.proto"; - -package Remote; - -message AuthRequest -{ - message Password - { - required string user_login = 1; - required string user_password = 2; - } - - enum Type - { - TypePassword = 1; - } - - required Type type = 1; - - optional Password password = 2; -} - -message AuthResponse -{ - - message PasswordResult - { - enum Type - { - TypePasswordInvalid = 1; - TypeLoginThrottling = 2; // The attempt was not processed because of throttling - TypePasswordValid = 3; - } - required Type type = 1; - - optional uint32 delay = 2; // in seconds, in case of PasswordInvalid/LoginThrottling - } - - enum Type - { - TypePasswordResult = 1; - } - - required Type type = 1; - - optional PasswordResult password_result = 2; -} - diff --git a/src/remote/proto/common.proto b/src/remote/proto/common.proto deleted file mode 100644 index a4f35ec4..00000000 --- a/src/remote/proto/common.proto +++ /dev/null @@ -1,9 +0,0 @@ - -package Remote; - -message Error -{ - required bool error = 1; - optional string message = 2; -} - diff --git a/src/service/RemoteServerService.cpp b/src/service/LmsAPIServerService.cpp similarity index 63% rename from src/service/RemoteServerService.cpp rename to src/service/LmsAPIServerService.cpp index 178a7447..c141c4e0 100644 --- a/src/service/RemoteServerService.cpp +++ b/src/service/LmsAPIServerService.cpp @@ -19,11 +19,11 @@ #include "logger/Logger.hpp" -#include "RemoteServerService.hpp" +#include "LmsAPIServerService.hpp" namespace Service { -RemoteServerService::RemoteServerService(const Config& config) +LmsAPIService::LmsAPIService(const Config& config) : _server(boost::asio::ip::tcp::endpoint(config.address, config.port), config.sslCertificatePath, config.sslPrivateKeyPath, @@ -33,26 +33,26 @@ RemoteServerService::RemoteServerService(const Config& config) } void -RemoteServerService::start(void) +LmsAPIService::start(void) { - LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "RemoteServerService::start, starting..."; + LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "LmsAPIService::start, starting..."; _server.start(); - LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "RemoteServerService::start, started!"; + LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "LmsAPIService::start, started!"; } void -RemoteServerService::stop(void) +LmsAPIService::stop(void) { - LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "RemoteServerService::stop, stopping..."; + LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "LmsAPIService::stop, stopping..."; _server.stop(); - LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "RemoteServerService::stop, stopped!"; + LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "LmsAPIService::stop, stopped!"; } void -RemoteServerService::restart(void) +LmsAPIService::restart(void) { - LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "RemoteServerService::restart, not implemented!"; + LMS_LOG(MOD_SERVICE, SEV_DEBUG) << "LmsAPIService::restart, not implemented!"; } } // namespace Service diff --git a/src/service/RemoteServerService.hpp b/src/service/LmsAPIServerService.hpp similarity index 88% rename from src/service/RemoteServerService.hpp rename to src/service/LmsAPIServerService.hpp index f5fd80cf..8fd17f1f 100644 --- a/src/service/RemoteServerService.hpp +++ b/src/service/LmsAPIServerService.hpp @@ -23,13 +23,15 @@ #include #include +#include "config/config.h" + #include "Service.hpp" -#include "remote/server/Server.hpp" +#include "lms-api/server/Server.hpp" namespace Service { -class RemoteServerService : public Service +class LmsAPIService : public Service { public: @@ -43,7 +45,7 @@ class RemoteServerService : public Service boost::filesystem::path dbPath; }; - RemoteServerService(const Config& config); + LmsAPIService(const Config& config); void start(void); void stop(void); @@ -51,7 +53,7 @@ class RemoteServerService : public Service private: - Remote::Server::Server _server; + LmsAPI::Server::Server _server; }; } // namespace Service diff --git a/src/ui/audio/desktop/AudioMediaPlayer.cpp b/src/ui/audio/desktop/AudioMediaPlayer.cpp index d5b939f5..3a3ef8ca 100644 --- a/src/ui/audio/desktop/AudioMediaPlayer.cpp +++ b/src/ui/audio/desktop/AudioMediaPlayer.cpp @@ -199,7 +199,7 @@ AudioMediaPlayer::load(Database::Track::id_type trackId) bitrate = CurrentUser()->getAudioBitrate(); trackPath = track->getPath(); _mediaTitle->setText ( Wt::WString::fromUTF8(track->getName()) ); - _mediaArtistRelease->setText ( Wt::WString::fromUTF8(track->getArtistName()) + " - " + Wt::WString::fromUTF8(track->getReleaseName()) ); + _mediaArtistRelease->setText ( Wt::WString::fromUTF8(track->getArtist()->getName()) + " - " + Wt::WString::fromUTF8(track->getRelease()->getName()) ); _mediaCover->setImageLink( Wt::WLink (LmsApplication::instance()->getCoverResource()->getTrackUrl(trackId, 72))); } diff --git a/src/ui/audio/desktop/DesktopAudio.cpp b/src/ui/audio/desktop/DesktopAudio.cpp index 297fd342..7f5bc9a9 100644 --- a/src/ui/audio/desktop/DesktopAudio.cpp +++ b/src/ui/audio/desktop/DesktopAudio.cpp @@ -52,6 +52,8 @@ void WPopupMenuClear(Wt::WPopupMenu* menu) namespace UserInterface { namespace Desktop { +using namespace Database; + // Special playlist generated each time the playque gets changed // Restored at the beginning of the session static const std::string CurrentQueuePlaylistName = "__current__"; @@ -269,7 +271,7 @@ Audio::playlistShowSaveDialog(std::string playlistName) Wt::Dbo::Transaction transaction(DboSession()); // Actually create the dialog only if the given list already exists - if (Database::Playlist::get(DboSession(), playlistName, CurrentUser())) + if (Playlist::get(DboSession(), playlistName, CurrentUser())) { Wt::WMessageBox *messageBox = new Wt::WMessageBox ("Overwrite playlist", @@ -301,25 +303,25 @@ Audio::playlistSaveFromPlayqueue(std::string playlistName) Wt::Dbo::Transaction transaction(DboSession()); - Database::Playlist::pointer playlist = Database::Playlist::get(DboSession(), playlistName, CurrentUser()); + Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser()); if (playlist) { LMS_LOG(MOD_UI, SEV_INFO) << "Erasing playlist '" << playlistName << "'"; playlist.remove(); } - playlist = Database::Playlist::create(DboSession(), playlistName, false, CurrentUser()); + playlist = Playlist::create(DboSession(), playlistName, false, CurrentUser()); - std::vector trackIds; + std::vector trackIds; _playQueue->getTracks(trackIds); int pos = 0; - BOOST_FOREACH(Database::Track::id_type trackId, trackIds) + for (Track::id_type trackId : trackIds) { - Database::Track::pointer track = Database::Track::getById(DboSession(), trackId); + Track::pointer track = Track::getById(DboSession(), trackId); if (track) - Database::PlaylistEntry::create(DboSession(), track, playlist, pos++); + PlaylistEntry::create(DboSession(), track, playlist, pos++); } LMS_LOG(MOD_UI, SEV_INFO) << "Saving playqueue to playlist '" << playlistName << "' done. Contains " << pos << " entries"; @@ -330,16 +332,16 @@ Audio::playlistLoadToPlayqueue(std::string playlistName) { LMS_LOG(MOD_UI, SEV_DEBUG) << "Loading playlist '" << playlistName << "' to playqueue"; - std::vector entries; + std::vector entries; { Wt::Dbo::Transaction transaction(DboSession()); - Database::Playlist::pointer playlist = Database::Playlist::get(DboSession(), playlistName, CurrentUser()); + Playlist::pointer playlist = Playlist::get(DboSession(), playlistName, CurrentUser()); if (!playlist) return; - entries = Database::PlaylistEntry::getEntries(DboSession(), playlist); + entries = PlaylistEntry::getEntries(DboSession(), playlist); } _playQueue->clear(); @@ -365,7 +367,7 @@ Audio::playlistShowDeleteDialog(std::string name) { Wt::Dbo::Transaction transaction(DboSession()); - Database::Playlist::pointer playlist = Database::Playlist::get(DboSession(), name, CurrentUser()); + Playlist::pointer playlist = Playlist::get(DboSession(), name, CurrentUser()); if (playlist) playlist.remove(); @@ -396,9 +398,9 @@ Audio::playlistRefreshMenus() })); _popupMenuSave->addSeparator(); - std::vector playlists = Database::Playlist::get(DboSession(), CurrentUser()); + std::vector playlists = Playlist::get(DboSession(), CurrentUser()); - BOOST_FOREACH(Database::Playlist::pointer playlist, playlists) + for (Playlist::pointer playlist : playlists) { if (playlist->getName() == CurrentQueuePlaylistName) continue; @@ -431,7 +433,7 @@ Audio::search(std::string searchText) void Audio::addSelectedTracks(void) { - std::vector trackIds; + std::vector trackIds; _trackView->getSelectedTracks(trackIds); // If nothing is selected, get the whole track list @@ -444,7 +446,9 @@ Audio::addSelectedTracks(void) void Audio::playSelectedTracks(PlayQueueAddType addType) { - std::vector trackIds; + std::vector trackIds; + + LMS_LOG(MOD_UI, SEV_DEBUG) << "Playing selected tracks... nb selected = " << _trackView->getNbSelectedTracks() << ", add type = " << (addType == PlayQueueAddAllTracks ? "AddAll" : "AddSelected"); _playQueue->clear(); @@ -458,6 +462,9 @@ Audio::playSelectedTracks(PlayQueueAddType addType) break; case PlayQueueAddSelectedTracks: + + LMS_LOG(MOD_UI, SEV_DEBUG) << "Adding selected tracks..."; + // If nothing selected, get all the track and play everything if (_trackView->getNbSelectedTracks() == 0) { @@ -485,7 +492,7 @@ Audio::playSelectedTracks(PlayQueueAddType addType) } void -Audio::playTrack(Database::Track::id_type trackId, int pos) +Audio::playTrack(Track::id_type trackId, int pos) { { Wt::Dbo::Transaction transaction(DboSession()); diff --git a/src/ui/audio/desktop/KeywordSearchFilter.cpp b/src/ui/audio/desktop/KeywordSearchFilter.cpp index ab39b9ed..6b4d4355 100644 --- a/src/ui/audio/desktop/KeywordSearchFilter.cpp +++ b/src/ui/audio/desktop/KeywordSearchFilter.cpp @@ -47,16 +47,14 @@ KeywordSearchFilter::getConstraint(Database::SearchFilter& filter) boost::algorithm::split(values, _lastEmittedText, boost::is_any_of(" "), boost::token_compress_on); // For each part, do a global search on all searchable fields - BOOST_FOREACH(std::string value, values) + for (const std::string& value : values) { - Database::SearchFilter::FieldValues likeMatch; - - likeMatch[Database::SearchFilter::Field::Artist].push_back(value); - likeMatch[Database::SearchFilter::Field::Release].push_back(value); - likeMatch[Database::SearchFilter::Field::Genre].push_back(value); - likeMatch[Database::SearchFilter::Field::Track].push_back(value); - - filter.likeMatches.push_back(likeMatch); + filter.nameLikeMatch.push_back({ + {Database::SearchFilter::Field::Artist, {value}}, + {Database::SearchFilter::Field::Release, {value}}, + {Database::SearchFilter::Field::Genre, {value}}, + {Database::SearchFilter::Field::Track, {value}} + }); } } } diff --git a/src/ui/audio/desktop/PlayQueue.cpp b/src/ui/audio/desktop/PlayQueue.cpp index f2c47373..3f1e6fc8 100644 --- a/src/ui/audio/desktop/PlayQueue.cpp +++ b/src/ui/audio/desktop/PlayQueue.cpp @@ -366,12 +366,16 @@ PlayQueue::select(int rowId) void PlayQueue::addTracks(const std::vector& trackIds) { + using namespace Database; + + LMS_LOG(MOD_UI, SEV_DEBUG) << "Adding " << trackIds.size() << " tracks to play queue"; + // Add tracks to model - // - BOOST_FOREACH(Database::Track::id_type trackId, trackIds) + for (Track::id_type trackId : trackIds) { Wt::Dbo::Transaction transaction(DboSession()); - Database::Track::pointer track (Database::Track::getById(DboSession(), trackId)); + + Track::pointer track (Track::getById(DboSession(), trackId)); if (track) { @@ -382,7 +386,7 @@ PlayQueue::addTracks(const std::vector& trackIds) _model->setData(dataRow, COLUMN_ID_TRACK_ID, track.id(), Wt::UserRole); std::string coverUrl; - if (track->getCoverType() != Database::Track::CoverType::None) + if (track->getCoverType() != Track::CoverType::None) coverUrl = LmsApplication::instance()->getCoverResource()->getTrackUrl(track.id(), 64); else coverUrl = LmsApplication::instance()->getCoverResource()->getUnknownTrackUrl(64); @@ -392,8 +396,8 @@ PlayQueue::addTracks(const std::vector& trackIds) TrackInfo trackInfo; trackInfo.track = Wt::WString::fromUTF8(track->getName()); - trackInfo.artist = Wt::WString::fromUTF8(track->getArtistName()); - trackInfo.release = Wt::WString::fromUTF8(track->getReleaseName()); + trackInfo.artist = Wt::WString::fromUTF8(track->getArtist()->getName()); + trackInfo.release = Wt::WString::fromUTF8(track->getRelease()->getName()); _model->setData(dataRow, COLUMN_ID_NAME, trackInfo, TrackInfoRole); } } @@ -455,19 +459,21 @@ PlayQueue::playPrevious(void) } } -bool +void PlayQueue::readTrack(int rowPos) { - LMS_LOG(MOD_UI, SEV_DEBUG) << "Reading track at pos " << rowPos; - Database::Track::id_type trackId = boost::any_cast(_model->data(rowPos, COLUMN_ID_TRACK_ID, Wt::UserRole)); + LMS_LOG(MOD_UI, SEV_DEBUG) << "Reading track at pos " << rowPos << ", row count = " << _model->rowCount(); - setPlayingTrackPos(rowPos); + if (rowPos < _model->rowCount()) + { + Database::Track::id_type trackId = boost::any_cast(_model->data(rowPos, COLUMN_ID_TRACK_ID, Wt::UserRole)); - _sigTrackPlay.emit(trackId, rowPos); + setPlayingTrackPos(rowPos); - this->scrollTo( _model->index(_trackSelector->getCurrent(), 0)); + _sigTrackPlay.emit(trackId, rowPos); - return true; + this->scrollTo( _model->index(_trackSelector->getCurrent(), 0)); + } } void @@ -485,7 +491,7 @@ PlayQueue::setPlayingTrackPos(int newRowPos) if (newRowPos >= 0) { - _model->setData(newRowPos, COLUMN_ID_NAME, "playqueue-playing", Wt::StyleClassRole); + _model->setData(newRowPos, COLUMN_ID_NAME, std::string("playqueue-playing"), Wt::StyleClassRole); } } @@ -527,7 +533,7 @@ PlayQueue::moveSelectedUp(void) Wt::WModelIndexSet newIndexSet; // ordered from up to down - BOOST_FOREACH(Wt::WModelIndex index, indexSet) + for (Wt::WModelIndex index : indexSet) { // Do nothing if the first selected index is on the top if (index.row() == 0) @@ -570,7 +576,7 @@ PlayQueue::moveSelectedDown(void) Wt::WModelIndexSet newIndexSet; // ordered from up to down - BOOST_REVERSE_FOREACH(Wt::WModelIndex index, indexSet) + for (Wt::WModelIndex index : indexSet) { // Do nothing if the last selected index is the last one if (index.row() == _model->rowCount() - 1) diff --git a/src/ui/audio/desktop/PlayQueue.hpp b/src/ui/audio/desktop/PlayQueue.hpp index 7b80aa8a..69eac6b7 100644 --- a/src/ui/audio/desktop/PlayQueue.hpp +++ b/src/ui/audio/desktop/PlayQueue.hpp @@ -72,7 +72,7 @@ class PlayQueue : public Wt::WTableView void layoutSizeChanged (int width, int height); - bool readTrack(int rowId); + void readTrack(int rowId); void setPlayingTrackPos(int newRowPos); void renumber(int firstId, int lastId); diff --git a/src/ui/audio/desktop/TableFilter.cpp b/src/ui/audio/desktop/TableFilter.cpp index 664960d7..2e410870 100644 --- a/src/ui/audio/desktop/TableFilter.cpp +++ b/src/ui/audio/desktop/TableFilter.cpp @@ -39,7 +39,7 @@ TableFilterGenre::TableFilterGenre(Wt::WContainerWidget* parent) SearchFilter filter; - Genre::updateGenreQueryModel(DboSession(), _queryModel, filter, columnNames); + Genre::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); this->setSelectionMode(Wt::ExtendedSelection); this->setSortingEnabled(true); @@ -82,7 +82,7 @@ TableFilterGenre::layoutSizeChanged (int width, int height) void TableFilterGenre::refresh(SearchFilter& filter) { - Genre::updateGenreQueryModel(DboSession(), _queryModel, filter); + Genre::updateUIQueryModel(DboSession(), _queryModel, filter); } // Get constraint created by this filter @@ -91,14 +91,15 @@ TableFilterGenre::getConstraint(SearchFilter& filter) { Wt::WModelIndexSet indexSet = this->selectedIndexes(); - BOOST_FOREACH(Wt::WModelIndex index, indexSet) { - + for (Wt::WModelIndex index : indexSet) + { if (!index.isValid()) continue; - std::string name = _queryModel.resultRow( index.row() ).get<0>(); + // TODO set invisible track id + Database::Genre::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - filter.exactMatch[Database::SearchFilter::Field::Genre].push_back(name); + filter.idMatch[Database::SearchFilter::Field::Genre].push_back(id); } } @@ -109,7 +110,7 @@ TableFilterArtist::TableFilterArtist(Wt::WContainerWidget* parent) SearchFilter filter; - Track::updateArtistQueryModel(DboSession(), _queryModel, filter, columnNames); + Artist::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); this->setSelectionMode(Wt::ExtendedSelection); this->setSortingEnabled(true); @@ -153,7 +154,7 @@ TableFilterArtist::layoutSizeChanged (int width, int height) void TableFilterArtist::refresh(SearchFilter& filter) { - Track::updateArtistQueryModel(DboSession(), _queryModel, filter); + Artist::updateUIQueryModel(DboSession(), _queryModel, filter); } // Get constraint created by this filter @@ -167,9 +168,9 @@ TableFilterArtist::getConstraint(SearchFilter& filter) if (!index.isValid()) continue; - std::string name = _queryModel.resultRow( index.row() ).get<0>(); + Artist::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - filter.exactMatch[Database::SearchFilter::Field::Artist].push_back(name); + filter.idMatch[Database::SearchFilter::Field::Artist].push_back(id); } } @@ -180,7 +181,7 @@ TableFilterRelease::TableFilterRelease(Wt::WContainerWidget* parent) SearchFilter filter; - Track::updateReleaseQueryModel(DboSession(), _queryModel, filter, columnNames); + Release::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); this->setSelectionMode(Wt::ExtendedSelection); this->setSortingEnabled(true); @@ -231,7 +232,7 @@ TableFilterRelease::layoutSizeChanged (int width, int height) void TableFilterRelease::refresh(SearchFilter& filter) { - Track::updateReleaseQueryModel(DboSession(), _queryModel, filter); + Release::updateUIQueryModel(DboSession(), _queryModel, filter); } // Get constraint created by this filter @@ -240,14 +241,14 @@ TableFilterRelease::getConstraint(SearchFilter& filter) { Wt::WModelIndexSet indexSet = this->selectedIndexes(); - BOOST_FOREACH(Wt::WModelIndex index, indexSet) { - + for (Wt::WModelIndex index : indexSet) + { if (!index.isValid()) continue; - std::string name = _queryModel.resultRow( index.row() ).get<0>(); + Release::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - filter.exactMatch[Database::SearchFilter::Field::Release].push_back(name); + filter.idMatch[Database::SearchFilter::Field::Release].push_back(id); } } diff --git a/src/ui/audio/desktop/TableFilter.hpp b/src/ui/audio/desktop/TableFilter.hpp index ca4b8d2d..991414a9 100644 --- a/src/ui/audio/desktop/TableFilter.hpp +++ b/src/ui/audio/desktop/TableFilter.hpp @@ -52,7 +52,7 @@ class TableFilterGenre : public Wt::WTableView, public Filter SigDoubleClicked _sigDoubleClicked; // Name, track count - Wt::Dbo::QueryModel _queryModel; + Wt::Dbo::QueryModel _queryModel; }; class TableFilterArtist : public Wt::WTableView, public Filter @@ -77,7 +77,7 @@ class TableFilterArtist : public Wt::WTableView, public Filter SigDoubleClicked _sigDoubleClicked; // Name, track count - Wt::Dbo::QueryModel _queryModel; + Wt::Dbo::QueryModel _queryModel; }; class TableFilterRelease : public Wt::WTableView, public Filter @@ -102,7 +102,7 @@ class TableFilterRelease : public Wt::WTableView, public Filter SigDoubleClicked _sigDoubleClicked; // Name, track count - Wt::Dbo::QueryModel _queryModel; + Wt::Dbo::QueryModel _queryModel; }; diff --git a/src/ui/audio/desktop/TrackView.cpp b/src/ui/audio/desktop/TrackView.cpp index 3ceaf7f9..b98a0198 100644 --- a/src/ui/audio/desktop/TrackView.cpp +++ b/src/ui/audio/desktop/TrackView.cpp @@ -50,7 +50,7 @@ TrackView::TrackView(Wt::WContainerWidget* parent) Database::SearchFilter filter; - Database::Track::updateTracksQueryModel(DboSession(), _queryModel, filter, columnNames); + Database::Track::updateUIQueryModel(DboSession(), _queryModel, filter, columnNames); _queryModel.setBatchSize(300); @@ -111,7 +111,7 @@ TrackView::TrackView(Wt::WContainerWidget* parent) void TrackView::refresh(Database::SearchFilter& filter) { - Database::Track::updateTracksQueryModel(DboSession(), _queryModel, filter); + Database::Track::updateUIQueryModel(DboSession(), _queryModel, filter); } void @@ -121,17 +121,17 @@ TrackView::getSelectedTracks(std::vector& track_ids) Wt::WModelIndexSet indexSet = this->selectedIndexes(); - BOOST_FOREACH(Wt::WModelIndex index, indexSet) + for (Wt::WModelIndex index : indexSet) { if (!index.isValid()) continue; - Database::Track::pointer track = _queryModel.resultRow( index.row() ); + Database::Track::id_type id = _queryModel.resultRow( index.row() ).get<0>(); - track_ids.push_back(track.id()); + track_ids.push_back(id); } - LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all selected tracks DONE..."; + LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all selected tracks: " << track_ids.size(); } std::size_t @@ -144,7 +144,8 @@ int TrackView::getFirstSelectedTrackPosition(void) { Wt::WModelIndexSet indexSet = this->selectedIndexes(); - BOOST_FOREACH(Wt::WModelIndex index, indexSet) + + for (Wt::WModelIndex index : indexSet) { if (!index.isValid()) continue; @@ -160,14 +161,16 @@ TrackView::getTracks(std::vector& trackIds) { LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks..."; - for (int i = 0; i < _queryModel.rowCount(); ++i) - { - Database::Track::pointer track = _queryModel.resultRow(i); + Wt::Dbo::Transaction transaction(DboSession()); + Wt::Dbo::collection results = _queryModel.query(); - trackIds.push_back(track.id()); + for (auto it = results.begin(); it != results.end(); ++it) + { + Database::Track::id_type id = it->get<0>(); + trackIds.push_back(id); } - LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks done!"; + LMS_LOG(MOD_UI, SEV_DEBUG) << "Getting all tracks done! " << trackIds.size() << " tracks!"; } } // namespace Desktop diff --git a/src/ui/audio/desktop/TrackView.hpp b/src/ui/audio/desktop/TrackView.hpp index 494cd965..f922589e 100644 --- a/src/ui/audio/desktop/TrackView.hpp +++ b/src/ui/audio/desktop/TrackView.hpp @@ -62,7 +62,7 @@ class TrackView : public Wt::WTableView, public Filter SigTrackDoubleClicked _sigTrackDoubleClicked; - typedef Database::Track::pointer ResultType; + typedef Database::Track::UIQueryResult ResultType; Wt::Dbo::QueryModel< ResultType > _queryModel; Wt::WTableView* _tableView; diff --git a/src/ui/audio/mobile/ArtistSearch.cpp b/src/ui/audio/mobile/ArtistSearch.cpp index 3428bb07..f2dc41fb 100644 --- a/src/ui/audio/mobile/ArtistSearch.cpp +++ b/src/ui/audio/mobile/ArtistSearch.cpp @@ -28,6 +28,8 @@ namespace UserInterface { namespace Mobile { +using namespace Database; + ArtistSearch::ArtistSearch(Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent), _resCount(0) @@ -57,15 +59,9 @@ ArtistSearch::search(Database::SearchFilter filter, size_t nb) void ArtistSearch::addResults(Database::SearchFilter filter, std::size_t nb) { + Wt::Dbo::Transaction transaction(DboSession()); - std::vector artists; - - { - Wt::Dbo::Transaction transaction(DboSession()); - - // Request one more to see if more results are to be expected - artists = Database::Track::getArtists(DboSession(), filter, _resCount, nb + 1); - } + std::vector artists = Artist::getByFilter(DboSession(), filter, _resCount, nb + 1); bool expectMoreResults; if (artists.size() == nb + 1) @@ -76,16 +72,16 @@ ArtistSearch::addResults(Database::SearchFilter filter, std::size_t nb) else expectMoreResults = false; - BOOST_FOREACH(std::string artist, artists) + for (Artist::pointer artist : artists) { Wt::WTemplate* res = new Wt::WTemplate(this); res->setTemplateText(Wt::WString::tr("mobile-artist-res")); - Wt::WText *text = new Wt::WText(Wt::WString::fromUTF8(artist), Wt::PlainText); + Wt::WText *text = new Wt::WText(Wt::WString::fromUTF8(artist->getName()), Wt::PlainText); res->bindWidget("name", text); res->clicked().connect(std::bind([=] { - _sigArtistSelected(artist); + _sigArtistSelected(artist.id()); })); } diff --git a/src/ui/audio/mobile/ArtistSearch.hpp b/src/ui/audio/mobile/ArtistSearch.hpp index f5e30f93..dbe361ca 100644 --- a/src/ui/audio/mobile/ArtistSearch.hpp +++ b/src/ui/audio/mobile/ArtistSearch.hpp @@ -37,13 +37,13 @@ class ArtistSearch : public Wt::WContainerWidget void search(Database::SearchFilter filter, std::size_t nb); // Slots - Wt::Signal& artistSelected() { return _sigArtistSelected;} - Wt::Signal& moreArtistsSelected() { return _sigMoreArtistsSelected;} + Wt::Signal& artistSelected() { return _sigArtistSelected;} + Wt::Signal& moreArtistsSelected() { return _sigMoreArtistsSelected;} private: - Wt::Signal _sigArtistSelected; - Wt::Signal _sigMoreArtistsSelected; + Wt::Signal _sigArtistSelected; + Wt::Signal _sigMoreArtistsSelected; void clear(void); void addResults(Database::SearchFilter filter, size_t nb); diff --git a/src/ui/audio/mobile/MobileAudio.cpp b/src/ui/audio/mobile/MobileAudio.cpp index 04bd79e7..ccc02fd1 100644 --- a/src/ui/audio/mobile/MobileAudio.cpp +++ b/src/ui/audio/mobile/MobileAudio.cpp @@ -41,6 +41,8 @@ namespace UserInterface { namespace Mobile { +using namespace Database; + Audio::Audio(Wt::WContainerWidget *parent) : UserInterface::Audio(parent) { @@ -72,13 +74,13 @@ Audio::Audio(Wt::WContainerWidget *parent) switch (CurrentUser()->getAudioEncoding()) { - case Database::AudioEncoding::MP3: encoding = Wt::WMediaPlayer::MP3; break; - case Database::AudioEncoding::WEBMA: encoding = Wt::WMediaPlayer::WEBMA; break; - case Database::AudioEncoding::OGA: encoding = Wt::WMediaPlayer::OGA; break; - case Database::AudioEncoding::FLA: encoding = Wt::WMediaPlayer::FLA; break; - case Database::AudioEncoding::AUTO: + case AudioEncoding::MP3: encoding = Wt::WMediaPlayer::MP3; break; + case AudioEncoding::WEBMA: encoding = Wt::WMediaPlayer::WEBMA; break; + case AudioEncoding::OGA: encoding = Wt::WMediaPlayer::OGA; break; + case AudioEncoding::FLA: encoding = Wt::WMediaPlayer::FLA; break; + case AudioEncoding::AUTO: default: - encoding = AudioMediaPlayer::getBestEncoding(); + encoding = AudioMediaPlayer::getBestEncoding(); } } @@ -95,47 +97,16 @@ Audio::Audio(Wt::WContainerWidget *parent) std::vector keywords; boost::algorithm::split(keywords, text, boost::is_any_of(" "), boost::token_compress_on); - { - Database::SearchFilter filter; - BOOST_FOREACH(std::string keyword, keywords) - { - Database::SearchFilter::FieldValues likeMatch; + releaseSearch->search(SearchFilter::NameLikeMatch( {{ + { SearchFilter::Field::Artist, keywords }, + { SearchFilter::Field::Release, keywords }}}), + 3); - likeMatch[Database::SearchFilter::Field::Artist].push_back(keyword); - likeMatch[Database::SearchFilter::Field::Release].push_back(keyword); - filter.likeMatches.push_back(likeMatch); - } + artistSearch->search(SearchFilter::NameLikeMatch({{{SearchFilter::Field::Artist, keywords}}}), + 3); - releaseSearch->search(filter, 3); - } - - { - Database::SearchFilter filter; - BOOST_FOREACH(std::string keyword, keywords) - { - Database::SearchFilter::FieldValues likeMatch; - - likeMatch[Database::SearchFilter::Field::Artist].push_back(keyword); - - filter.likeMatches.push_back(likeMatch); - } - - artistSearch->search(filter, 3); - } - - { - Database::SearchFilter filter; - BOOST_FOREACH(std::string keyword, keywords) - { - Database::SearchFilter::FieldValues likeMatch; - - likeMatch[Database::SearchFilter::Field::Track].push_back(keyword); - - filter.likeMatches.push_back(likeMatch); - } - - trackSearch->search(filter, 3); - } + trackSearch->search(SearchFilter::NameLikeMatch({{{SearchFilter::Field::Track, keywords}}}), + 3); artistSearch->show(); releaseSearch->show(); @@ -143,40 +114,38 @@ Audio::Audio(Wt::WContainerWidget *parent) })); - artistSearch->moreArtistsSelected().connect(std::bind([=] { + artistSearch->moreArtistsSelected().connect(std::bind([=] + { releaseSearch->hide(); trackSearch->hide(); artistSearch->show(); })); - artistSearch->artistSelected().connect(std::bind([=] (std::string artist) { + artistSearch->artistSelected().connect(std::bind([=] (Artist::id_type artistId) + { artistSearch->hide(); trackSearch->hide(); releaseSearch->show(); - Database::SearchFilter filter; - filter.exactMatch[Database::SearchFilter::Field::Artist].push_back(artist); - - releaseSearch->search(filter, 20); + releaseSearch->search(SearchFilter::IdMatch({{SearchFilter::Field::Artist, {artistId}}}), + 20); }, std::placeholders::_1)); - releaseSearch->moreReleasesSelected().connect(std::bind([=] { + releaseSearch->moreReleasesSelected().connect(std::bind([=] + { artistSearch->hide(); trackSearch->hide(); releaseSearch->show(); })); - releaseSearch->releaseSelected().connect(std::bind([=] (std::string release) + releaseSearch->releaseSelected().connect(std::bind([=] (Release::id_type releaseId) { artistSearch->hide(); releaseSearch->hide(); trackSearch->show(); - // TODO load track search with selected release - Database::SearchFilter filter; - filter.exactMatch[Database::SearchFilter::Field::Release].push_back(release); - - trackSearch->search(filter, 20); + trackSearch->search(SearchFilter::IdMatch({{SearchFilter::Field::Release, {releaseId}}}), + 20); }, std::placeholders::_1)); trackSearch->moreTracksSelected().connect(std::bind([=] @@ -185,13 +154,13 @@ Audio::Audio(Wt::WContainerWidget *parent) releaseSearch->hide(); })); - trackSearch->trackPlay().connect(std::bind([=] (Database::Track::id_type id) + trackSearch->trackPlay().connect(std::bind([=] (Track::id_type id) { LMS_LOG(MOD_UI, SEV_DEBUG) << "Playing track id " << id; // TODO reduce transaction scope here Wt::Dbo::Transaction transaction(DboSession()); - Database::Track::pointer track = Database::Track::getById(DboSession(), id); + Track::pointer track = Track::getById(DboSession(), id); if (track) { @@ -217,10 +186,9 @@ Audio::Audio(Wt::WContainerWidget *parent) // Initially, populate the widgets using an empty search { - Database::SearchFilter filter; // empty filter - artistSearch->search(filter, 3); - releaseSearch->search(filter, 3); - trackSearch->search(filter, 3); + artistSearch->search(SearchFilter(), 3); + releaseSearch->search(SearchFilter(), 3); + trackSearch->search(SearchFilter(), 3); } } diff --git a/src/ui/audio/mobile/MobileAudioMediaPlayer.cpp b/src/ui/audio/mobile/MobileAudioMediaPlayer.cpp index 47c2ff0a..fedf6184 100644 --- a/src/ui/audio/mobile/MobileAudioMediaPlayer.cpp +++ b/src/ui/audio/mobile/MobileAudioMediaPlayer.cpp @@ -98,7 +98,7 @@ AudioMediaPlayer::play(Database::Track::id_type trackId, const Transcode::Parame Database::Track::pointer track = Database::Track::getById(DboSession(), trackId); _track->setText( Wt::WString::fromUTF8(track->getName() )); - _artistRelease->setText( Wt::WString::fromUTF8(track->getArtistName()) ); + _artistRelease->setText( Wt::WString::fromUTF8(track->getArtist()->getName()) ); } } diff --git a/src/ui/audio/mobile/ReleaseSearch.cpp b/src/ui/audio/mobile/ReleaseSearch.cpp index 4f4375f9..d9af0cab 100644 --- a/src/ui/audio/mobile/ReleaseSearch.cpp +++ b/src/ui/audio/mobile/ReleaseSearch.cpp @@ -30,6 +30,8 @@ namespace UserInterface { namespace Mobile { +using namespace Database; + ReleaseSearch::ReleaseSearch(Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent), _resCount(0) @@ -59,15 +61,9 @@ ReleaseSearch::search(Database::SearchFilter filter, size_t max) void ReleaseSearch::addResults(Database::SearchFilter filter, size_t nb) { + Wt::Dbo::Transaction transaction(DboSession()); - std::vector releases; - - { - Wt::Dbo::Transaction transaction(DboSession()); - - // Request one more to see if more results are to be expected - releases = Database::Track::getReleases(DboSession(), filter, _resCount, nb + 1); - } + std::vector releases = Release::getByFilter(DboSession(), filter, _resCount, nb + 1); bool expectMoreResults; if (releases.size() == nb + 1) @@ -78,20 +74,20 @@ ReleaseSearch::addResults(Database::SearchFilter filter, size_t nb) else expectMoreResults = false; - BOOST_FOREACH(std::string release, releases) + for (Release::pointer release : releases) { Wt::WTemplate* releaseWidget = new Wt::WTemplate(this); releaseWidget->setTemplateText(Wt::WString::tr("mobile-release-res")); Wt::WImage *cover = new Wt::WImage(); cover->setStyleClass("center-block"); - cover->setImageLink( Wt::WLink( LmsApplication::instance()->getCoverResource()->getReleaseUrl(release, 56))); + cover->setImageLink( Wt::WLink( LmsApplication::instance()->getCoverResource()->getReleaseUrl(release.id(), 56))); releaseWidget->bindWidget("cover", cover); - releaseWidget->bindWidget("name", new Wt::WText(Wt::WString::fromUTF8(release), Wt::PlainText)); + releaseWidget->bindWidget("name", new Wt::WText(Wt::WString::fromUTF8(release->getName()), Wt::PlainText)); releaseWidget->clicked().connect(std::bind([=] { - _sigReleaseSelected(release); + _sigReleaseSelected(release.id()); })); } diff --git a/src/ui/audio/mobile/ReleaseSearch.hpp b/src/ui/audio/mobile/ReleaseSearch.hpp index 591ae161..876c1492 100644 --- a/src/ui/audio/mobile/ReleaseSearch.hpp +++ b/src/ui/audio/mobile/ReleaseSearch.hpp @@ -37,13 +37,13 @@ class ReleaseSearch : public Wt::WContainerWidget void search(Database::SearchFilter filter, size_t nb); // Slots - Wt::Signal& releaseSelected() { return _sigReleaseSelected;} - Wt::Signal& moreReleasesSelected() { return _sigMoreReleasesSelected;} + Wt::Signal& releaseSelected() { return _sigReleaseSelected;} + Wt::Signal& moreReleasesSelected() { return _sigMoreReleasesSelected;} private: - Wt::Signal _sigReleaseSelected; - Wt::Signal _sigMoreReleasesSelected; + Wt::Signal _sigReleaseSelected; + Wt::Signal _sigMoreReleasesSelected; void clear(void); void addResults(Database::SearchFilter filter, size_t nb); diff --git a/src/ui/audio/mobile/TrackSearch.cpp b/src/ui/audio/mobile/TrackSearch.cpp index 0a5e4f28..0c1b9b94 100644 --- a/src/ui/audio/mobile/TrackSearch.cpp +++ b/src/ui/audio/mobile/TrackSearch.cpp @@ -30,6 +30,8 @@ namespace UserInterface { namespace Mobile { +using namespace Database; + TrackSearch::TrackSearch(Wt::WContainerWidget *parent) : Wt::WContainerWidget(parent), _resCount(0) @@ -62,7 +64,7 @@ TrackSearch::addResults(Database::SearchFilter filter, size_t nb) { Wt::Dbo::Transaction transaction(DboSession()); - std::vector< Database::Track::pointer > tracks = Database::Track::getTracks(DboSession(), filter, _resCount, nb + 1); + std::vector tracks = Track::getByFilter(DboSession(), filter, _resCount, nb + 1); bool expectMoreResults; if (tracks.size() == nb + 1) @@ -73,7 +75,7 @@ TrackSearch::addResults(Database::SearchFilter filter, size_t nb) else expectMoreResults = false; - BOOST_FOREACH(Database::Track::pointer track, tracks) + for (Track::pointer track : tracks) { Wt::WTemplate* trackWidget = new Wt::WTemplate(this); trackWidget->setTemplateText(Wt::WString::tr("mobile-track-res")); @@ -90,11 +92,11 @@ TrackSearch::addResults(Database::SearchFilter filter, size_t nb) title->setStyleClass("mobile-track"); container->addWidget(title); - if (!track->getArtistName().empty() - || !track->getReleaseName().empty()) + if (!track->getArtist()->getName().empty() + || !track->getRelease()->getName().empty()) { title->setInline(false); - Wt::WText *artistRelease = new Wt::WText(Wt::WString::fromUTF8(track->getArtistName() + " - " + track->getReleaseName()), Wt::PlainText); + Wt::WText *artistRelease = new Wt::WText(Wt::WString::fromUTF8(track->getArtist()->getName() + " - " + track->getRelease()->getName()), Wt::PlainText); artistRelease->setStyleClass("mobile-artist"); container->addWidget(artistRelease); } diff --git a/src/ui/resource/CoverResource.cpp b/src/ui/resource/CoverResource.cpp index 0224ab83..df229fe4 100644 --- a/src/ui/resource/CoverResource.cpp +++ b/src/ui/resource/CoverResource.cpp @@ -17,8 +17,6 @@ * along with LMS. If not, see . */ -#include - #include #include @@ -73,9 +71,9 @@ CoverResource::getDefaultCover(std::size_t size) } std::string -CoverResource::getReleaseUrl(std::string releaseName, std::size_t size) const +CoverResource::getReleaseUrl(Database::Release::id_type releaseId, std::size_t size) const { - return url() + "&release=" + releaseName + "&size=" + std::to_string(size); + return url() + "&releaseid=" + std::to_string(releaseId) + "&size=" + std::to_string(size); } std::string @@ -101,76 +99,84 @@ CoverResource::putCover(Wt::Http::Response& response, const CoverArt::CoverArt& void CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) { - - // Get the id of the track + // Retrieve parameters const std::string *trackIdStr = request.getParameter("trackid"); + const std::string *releaseIdStr = request.getParameter("releaseid"); const std::string *sizeStr = request.getParameter("size"); - const std::string *releaseStr = request.getParameter("release"); - std::vector covers; - - // Mandatory parameter size - if (!sizeStr) - return; - - std::size_t size = std::stol(*sizeStr); - if (size > maxSize) - return; - - if (trackIdStr) + try { - Database::Track::id_type trackId = std::stol(*trackIdStr); - boost::filesystem::path path; - Database::Track::CoverType coverType = Database::Track::CoverType::None; + std::vector covers; + // Mandatory parameter size + if (!sizeStr) + return; + + std::size_t size = std::stol(*sizeStr); + if (size > maxSize) + return; + + if (trackIdStr) { + Database::Track::id_type trackId = std::stol(*trackIdStr); + boost::filesystem::path path; + Database::Track::CoverType coverType = Database::Track::CoverType::None; + + { + // transactions are not thread safe + std::unique_lock lock(_mutex); + + Wt::Dbo::Transaction transaction(_db.getSession()); + + Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); + if (track) + { + coverType = track->getCoverType(); + path = track->getPath(); + } + } + + switch (coverType) + { + case Database::Track::CoverType::Embedded: + covers = CoverArt::Grabber::instance().getFromTrack(path); + break; + + case Database::Track::CoverType::ExternalFile: + covers = CoverArt::Grabber::instance().getFromDirectory(path.parent_path()); + break; + + case Database::Track::CoverType::None: + break; + } + } + else if (releaseIdStr) + { + Database::Release::id_type releaseId = std::stol(*releaseIdStr); // TODO try catch // transactions are not thread safe std::unique_lock lock(_mutex); - Wt::Dbo::Transaction transaction(_db.getSession()); - Database::Track::pointer track = Database::Track::getById(_db.getSession(), trackId); - if (track) + covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), releaseId); + } + + for (CoverArt::CoverArt& cover : covers) + { + if (cover.scale(size)) { - coverType = track->getCoverType(); - path = track->getPath(); + putCover(response, cover); + return; } } - switch (coverType) - { - case Database::Track::CoverType::Embedded: - covers = CoverArt::Grabber::instance().getFromTrack(path); - break; + // If no cover found, just send default one + putCover(response, getDefaultCover(size)); - case Database::Track::CoverType::ExternalFile: - covers = CoverArt::Grabber::instance().getFromDirectory(path.parent_path()); - break; - - case Database::Track::CoverType::None: - break; - } } - else if (releaseStr) + catch (std::invalid_argument& e) { - // transactions are not thread safe - std::unique_lock lock(_mutex); - Wt::Dbo::Transaction transaction(_db.getSession()); - - covers = CoverArt::Grabber::instance().getFromRelease(_db.getSession(), *releaseStr); + LMS_LOG(MOD_UI, SEV_ERROR) << "Invalid argument: " << e.what(); } - - BOOST_FOREACH(CoverArt::CoverArt& cover, covers) - { - if (cover.scale(size)) - { - putCover(response, cover); - return; - } - } - - // If no cover found, just send default one - putCover(response, getDefaultCover(size)); } } // namespace UserInterface diff --git a/src/ui/resource/CoverResource.hpp b/src/ui/resource/CoverResource.hpp index 9b2610e6..60fe12a8 100644 --- a/src/ui/resource/CoverResource.hpp +++ b/src/ui/resource/CoverResource.hpp @@ -39,7 +39,7 @@ class CoverResource : public Wt::WResource CoverResource(Database::Handler& db, Wt::WObject *parent = 0); ~CoverResource(); - std::string getReleaseUrl(std::string releaseName, size_t size) const; + std::string getReleaseUrl(Database::Release::id_type releaseId, size_t size) const; std::string getTrackUrl(Database::Track::id_type trackId, size_t size) const; std::string getUnknownTrackUrl(size_t size) const; diff --git a/test/CheckDbBasics.cpp b/test/CheckDbBasics.cpp new file mode 100644 index 00000000..e1fbfaa9 --- /dev/null +++ b/test/CheckDbBasics.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "database/DatabaseHandler.hpp" + +static const std::string trackMBID = "123e4567-e89b-12d3-a456-426655440000"; +static const std::string artistMBID = "xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx"; +static const std::string releaseMBID = "xxxxxxxx-xxxx-9877-Nxxx-xxxxxxxxxxxx"; + +int main(void) +{ + try + { + using namespace Database; + + boost::filesystem::remove("test.db"); + + Handler db("test.db"); + + // Create + { + Wt::Dbo::Transaction transaction(db.getSession()); + + Track::pointer track = Track::create(db.getSession(), "test.mp2"); + + track.modify()->setName("track01"); + track.modify()->setMBID(trackMBID); + + Artist::pointer artist = Artist::create(db.getSession(), "artist01", artistMBID); + Release::pointer release = Release::create(db.getSession(), "release01", releaseMBID); + Genre::pointer genre = Genre::create(db.getSession(), "genre01"); + + track.modify()->setArtist(artist); + track.modify()->setRelease(release); + track.modify()->setGenres( { genre }); + } + + // Search + { + Wt::Dbo::Transaction transaction(db.getSession()); + + Track::pointer track = Track::getByMBID(db.getSession(), trackMBID); + assert(track); + assert(track->getArtist()->getMBID() == artistMBID); + + Track::pointer trackNotFound = Track::getByMBID(db.getSession(), "foobar"); + assert(!trackNotFound); + + Artist::pointer artist = Artist::getByMBID(db.getSession(), artistMBID); + assert(artist); + } + + // Search Filters + // Select track by track name + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Track, {"track"}}}}); + std::vector res = Track::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + + filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Track,{"not-found"}}}}); + res = Track::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 0 ); + } + + // Select track by artist name + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Artist, {"artist"}}}}); + std::vector res = Track::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + } + + // Select track by artist id + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::IdMatch({{SearchFilter::Field::Artist, {1}}}); + std::vector res = Track::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + } + + // Select track by track name + artist id + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter; + filter.idMatch[SearchFilter::Field::Artist] = { 1 }; + filter.nameLikeMatch = {{{ SearchFilter::Field::Track, {"track"} }}}; + std::vector res = Track::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + assert(res.front()->getName() == "track01" ); + } + + // Select track by genre name + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Genre, {"genre"}}}}); + std::vector res = Track::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + assert(res.front()->getName() == "track01" ); + } + + // Select artist by track name + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Track, { "track" } }}}); + std::vector res = Artist::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + + filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Track, {"badtrack"} }}}); + res = Artist::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 0); + } + + // Select artist by track id + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::IdMatch({{SearchFilter::Field::Track, {1} }}); + std::vector res = Artist::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + } + + // Select Artist by name + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Artist, {"artist"}}}}); + std::vector res = Artist::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + } + + // Select Release by name + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Release, {"release"} }}}); + + std::vector res = Release::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + } + + // Select Release by track name + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Track, {"track"}}}}); + std::vector res = Release::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + assert(res.front()->getName() == "release01"); + } + + // Select genre by name + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Genre, {"genre"}}}}); + std::vector res = Genre::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + assert(res.front()->getName() == "genre01"); + } + + // Select genre by track name + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Track, {"track"}}}}); + std::vector res = Genre::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + assert(res.front()->getName() == "genre01"); + } + + // Select genre by track name and artist name + { + Wt::Dbo::Transaction transaction(db.getSession()); + + SearchFilter filter = SearchFilter::NameLikeMatch({{{SearchFilter::Field::Track, {"track"}}, + {SearchFilter::Field::Artist, {"artist"}}}}); + std::vector res = Genre::getByFilter(db.getSession(), filter, -1, -1); + assert(res.size() == 1); + assert(res.front().id() == 1); + assert(res.front()->getName() == "genre01"); + } + + } + catch(std::exception& e) + { + std::cerr << "Caught exception " << e.what() << std::endl; + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + diff --git a/test/Makefile.am b/test/Makefile.am index 0ce574a1..d551d4f7 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -1,7 +1,11 @@ -TESTS = database-integrity sql-query database-user remote-client +TESTS = database-basics database-integrity sql-query database-user -check_PROGRAMS = database-integrity sql-query database-user remote-client +check_PROGRAMS = database-basics database-integrity sql-query database-user + +if LMSAPI +TESTS += remote-client +check_PROGRAMS += remote-client remote_client_SOURCES = \ $(srcdir)/RemoteClient.cpp \ @@ -20,20 +24,39 @@ BUILT_SOURCES = \ $(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/remote/proto/%.proto - $(PROTOC) --proto_path=$(top_srcdir)/src/remote/proto/ --cpp_out=$(builddir)/ $^ +%.pb.cc %.pb.h: $(top_srcdir)/src/lms-api/proto/%.proto + $(PROTOC) --proto_path=$(top_srcdir)/src/lms-api/proto/ --cpp_out=$(builddir)/ $^ +endif + +database_basics_SOURCES = \ + $(srcdir)/CheckDbBasics.cpp \ + $(top_srcdir)/src/logger/Logger.cpp \ + $(top_srcdir)/src/database/Artist.cpp \ + $(top_srcdir)/src/database/DatabaseHandler.cpp \ + $(top_srcdir)/src/database/MediaDirectory.cpp \ + $(top_srcdir)/src/database/Playlist.cpp \ + $(top_srcdir)/src/database/Release.cpp \ + $(top_srcdir)/src/database/SearchFilter.cpp \ + $(top_srcdir)/src/database/SqlQuery.cpp \ + $(top_srcdir)/src/database/Track.cpp \ + $(top_srcdir)/src/database/User.cpp \ + $(top_srcdir)/src/database/Video.cpp + +database_basics_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_srcdir)/src database_user_SOURCES = \ $(srcdir)/CheckDatabaseUser.cpp \ $(top_srcdir)/src/logger/Logger.cpp \ + $(top_srcdir)/src/database/Artist.cpp \ $(top_srcdir)/src/database/Playlist.cpp \ + $(top_srcdir)/src/database/Release.cpp \ $(top_srcdir)/src/database/Track.cpp \ $(top_srcdir)/src/database/DatabaseHandler.cpp \ $(top_srcdir)/src/database/MediaDirectory.cpp \ + $(top_srcdir)/src/database/SearchFilter.cpp \ $(top_srcdir)/src/database/SqlQuery.cpp \ $(top_srcdir)/src/database/User.cpp \ $(top_srcdir)/src/database/Video.cpp @@ -44,10 +67,13 @@ database_user_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_src database_integrity_SOURCES = \ $(srcdir)/DatabaseIntegrity.cpp \ $(top_srcdir)/src/logger/Logger.cpp \ + $(top_srcdir)/src/database/Artist.cpp \ $(top_srcdir)/src/database/Playlist.cpp \ $(top_srcdir)/src/database/Track.cpp \ $(top_srcdir)/src/database/DatabaseHandler.cpp \ $(top_srcdir)/src/database/MediaDirectory.cpp \ + $(top_srcdir)/src/database/Release.cpp \ + $(top_srcdir)/src/database/SearchFilter.cpp \ $(top_srcdir)/src/database/SqlQuery.cpp \ $(top_srcdir)/src/database/User.cpp \ $(top_srcdir)/src/database/Video.cpp diff --git a/test/RemoteClient.cpp b/test/RemoteClient.cpp index c37a9feb..dce3e641 100644 --- a/test/RemoteClient.cpp +++ b/test/RemoteClient.cpp @@ -34,40 +34,52 @@ struct GenreInfo { + uint64_t id; std::string name; + + GenreInfo() : id(0) {} }; std::ostream& operator<<(std::ostream& os, const GenreInfo& info) { - os << "name = '" << info.name << "'"; + os << "name = '" << info.name << "'(" << info.id << ")"; return os; } struct ArtistInfo { + uint64_t id; + std::string mbid; std::string name; + + ArtistInfo() :id(0) {} }; std::ostream& operator<<(std::ostream& os, const ArtistInfo& info) { - os << "name = '" << info.name << "'"; + os << "name = '" << info.name << "'(" << info.id << ")"; return os; } struct ReleaseInfo { + uint64_t id; + std::string mbid; std::string name; + + ReleaseInfo() : id(0) {} }; std::ostream& operator<<(std::ostream& os, const ReleaseInfo& info) { - os << "name = '" << info.name; + os << "name = '" << info.name << "'(" << info.id << ")"; return os; } struct TrackInfo { uint64_t id; + std::string mbid; uint64_t release_id; uint64_t artist_id; @@ -103,8 +115,30 @@ struct Cover std::vector data; }; -// Ugly class for testing purposes +struct SearchFilter +{ + std::vector artistIds; + std::vector genreIds; + std::vector releaseIds; + std::vector trackIds; +}; +void SearchFilterToRequest(const SearchFilter& filter, Remote::AudioCollectionRequest_SearchFilter& request) +{ + for (uint64_t id : filter.artistIds) + request.add_artist_id(id); + + for (uint64_t id : filter.genreIds) + request.add_genre_id(id); + + for (uint64_t id : filter.releaseIds) + request.add_release_id(id); + + for (uint64_t id : filter.trackIds) + request.add_track_id(id); +} + +// Ugly class for testing purposes class TestClient { public: @@ -122,8 +156,7 @@ class TestClient _socket.handshake(boost::asio::ssl::stream_base::client); } - void getArtists(std::vector& artists, - const std::vector& genres = std::vector()) + void getArtists(std::vector& artists, const SearchFilter& filter = SearchFilter()) { const std::size_t requestedBatchSize = 128; @@ -131,12 +164,12 @@ class TestClient std::size_t res = 0; - while ((res = getArtists(artists, genres, offset, requestedBatchSize) ) > 0) + while ((res = getArtists(artists, filter, offset, requestedBatchSize) ) > 0) offset += res; } - std::size_t getArtists(std::vector& artists, const std::vector& genres, std::size_t offset, std::size_t size) + std::size_t getArtists(std::vector& artists, const SearchFilter& filter, std::size_t offset, std::size_t size) { std::size_t nbArtists = 0; @@ -148,8 +181,7 @@ class TestClient request.mutable_audio_collection_request()->set_type( Remote::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); - BOOST_FOREACH(const std::string& genre, genres) - request.mutable_audio_collection_request()->mutable_get_artists()->add_genre(genre); + SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_artists()->mutable_search_filter()); sendMsg(request); @@ -166,11 +198,18 @@ class TestClient for (int i = 0; i < response.audio_collection_response().artist_list().artists_size(); ++i) { - if (!response.audio_collection_response().artist_list().artists(i).has_name()) + const Remote::AudioCollectionResponse_Artist& respArtist = response.audio_collection_response().artist_list().artists(i); + + if (!respArtist.has_id()) + throw std::runtime_error("no id!"); + if (!respArtist.has_name()) throw std::runtime_error("no artist name!"); ArtistInfo artist; - artist.name = response.audio_collection_response().artist_list().artists(i).name(); + artist.name = respArtist.name(); + artist.id = respArtist.id(); + if (respArtist.has_mbid()) + artist.mbid = respArtist.mbid(); artists.push_back( artist ); nbArtists++; @@ -179,19 +218,19 @@ class TestClient return nbArtists; } - void getGenres(std::vector& genres) + void getGenres(std::vector& genres, const SearchFilter& filter = SearchFilter()) { const std::size_t requestedBatchSize = 8; std::size_t offset = 0; std::size_t res = 0; - while ((res = getGenres(genres, offset, requestedBatchSize) ) > 0) + while ((res = getGenres(genres, filter, offset, requestedBatchSize) ) > 0) offset += res; } - std::size_t getGenres(std::vector& genres, std::size_t offset, std::size_t size) + std::size_t getGenres(std::vector& genres, const SearchFilter& filter, std::size_t offset, std::size_t size) { std::size_t nbAdded = 0; @@ -203,6 +242,7 @@ class TestClient request.mutable_audio_collection_request()->set_type( Remote::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()); sendMsg(request); @@ -219,11 +259,16 @@ class TestClient for (int i = 0; i < response.audio_collection_response().genre_list().genres_size(); ++i) { - if (!response.audio_collection_response().genre_list().genres(i).has_name()) + const Remote::AudioCollectionResponse_Genre& respGenre = response.audio_collection_response().genre_list().genres(i); + + if (!respGenre.has_id()) + throw std::runtime_error("no genre id!"); + if (!respGenre.has_name()) throw std::runtime_error("no genre name!"); GenreInfo genre; - genre.name = response.audio_collection_response().genre_list().genres(i).name(); + genre.id = respGenre.id(); + genre.name = respGenre.name(); genres.push_back( genre ); nbAdded++; @@ -231,21 +276,19 @@ class TestClient return nbAdded; } - void getReleases(std::vector& releases, - const std::vector& artists = std::vector(), - const std::vector& genres = std::vector()) + void getReleases(std::vector& releases, const SearchFilter& filter = SearchFilter()) { const std::size_t requestedBatchSize = 256; std::size_t offset = 0; std::size_t res = 0; - while ((res = getReleases(releases, artists, genres, offset, requestedBatchSize) ) > 0) + while ((res = getReleases(releases, filter, offset, requestedBatchSize) ) > 0) offset += res; } - std::size_t getReleases(std::vector& releases, const std::vector& artists, const std::vector& genres, std::size_t offset, std::size_t size) + std::size_t getReleases(std::vector& releases, const SearchFilter& filter, std::size_t offset, std::size_t size) { std::size_t nbAdded = 0; @@ -257,11 +300,7 @@ class TestClient request.mutable_audio_collection_request()->set_type( Remote::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); - BOOST_FOREACH(const std::string& artist, artists) - request.mutable_audio_collection_request()->mutable_get_releases()->add_artist(artist); - - BOOST_FOREACH(const std::string& genre, genres) - request.mutable_audio_collection_request()->mutable_get_releases()->add_genre(genre); + SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_releases()->mutable_search_filter()); sendMsg(request); @@ -284,11 +323,16 @@ class TestClient for (int i = 0; i < response.audio_collection_response().release_list().releases_size(); ++i) { - if (!response.audio_collection_response().release_list().releases(i).has_name()) + const Remote::AudioCollectionResponse_Release& respRelease = response.audio_collection_response().release_list().releases(i); + + if (!respRelease.has_id()) + throw std::runtime_error("no id!"); + if (!respRelease.has_name()) throw std::runtime_error("no release name!"); ReleaseInfo release; - release.name = response.audio_collection_response().release_list().releases(i).name(); + release.id = respRelease.id(); + release.name = respRelease.name(); releases.push_back( release ); nbAdded++; @@ -296,25 +340,17 @@ class TestClient return nbAdded; } - void getTracks(std::vector& tracks, - const std::vector& artists = std::vector(), - const std::vector& releases = std::vector(), - const std::vector& genres = std::vector()) + void getTracks(std::vector& tracks, const SearchFilter& filter = SearchFilter()) { const std::size_t requestedBatchSize = 0; std::size_t offset = 0; std::size_t res = 0; - while ((res = getTracks(tracks, artists, releases, genres, offset, requestedBatchSize) ) > 0) + while ((res = getTracks(tracks, filter, offset, requestedBatchSize) ) > 0) offset += res; } - std::size_t getTracks(std::vector& tracks, - const std::vector& artists, - const std::vector& releases, - const std::vector& genres, - std::size_t offset, - std::size_t size) + std::size_t getTracks(std::vector& tracks, const SearchFilter& filter, std::size_t offset, std::size_t size) { std::size_t nbAdded = 0; @@ -326,13 +362,7 @@ class TestClient request.mutable_audio_collection_request()->set_type( Remote::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); - - BOOST_FOREACH(const std::string& artist, artists) - request.mutable_audio_collection_request()->mutable_get_tracks()->add_artist(artist); - BOOST_FOREACH(const std::string& release, releases) - request.mutable_audio_collection_request()->mutable_get_tracks()->add_release(release); - BOOST_FOREACH(const std::string& genre, genres) - request.mutable_audio_collection_request()->mutable_get_tracks()->add_genre(genre); + SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_search_filter()); sendMsg(request); @@ -349,22 +379,27 @@ 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);; TrackInfo track; - track.id = response.audio_collection_response().track_list().tracks(i).id(); - track.name = response.audio_collection_response().track_list().tracks(i).name(); - track.duration = boost::posix_time::seconds(response.audio_collection_response().track_list().tracks(i).duration_secs()); - if (response.audio_collection_response().track_list().tracks(i).has_track_number()) - track.track_number = response.audio_collection_response().track_list().tracks(i).track_number(); + track.id = respTrack.id(); + if (respTrack.has_mbid()) + track.mbid = respTrack.mbid(); - if (response.audio_collection_response().track_list().tracks(i).has_disc_number()) - track.disc_number = response.audio_collection_response().track_list().tracks(i).disc_number(); + track.name = respTrack.name(); + track.duration = boost::posix_time::seconds(respTrack.duration_secs()); - if (response.audio_collection_response().track_list().tracks(i).has_release_date()) - track.date = response.audio_collection_response().track_list().tracks(i).release_date(); + if (respTrack.has_track_number()) + track.track_number = respTrack.track_number(); - if (response.audio_collection_response().track_list().tracks(i).has_original_release_date()) - track.original_date = response.audio_collection_response().track_list().tracks(i).original_release_date(); + if (respTrack.has_disc_number()) + track.disc_number = respTrack.disc_number(); + + if (respTrack.has_release_date()) + track.date = respTrack.release_date(); + + if (respTrack.has_original_release_date()) + track.original_date = respTrack.original_release_date(); tracks.push_back( track ); @@ -419,7 +454,7 @@ class TestClient } } - void getCoverRelease(std::vector& coverArt, const std::string& release) + void getCoverRelease(std::vector& coverArt, uint64_t releaseId) { // Send request Remote::ClientMessage request; @@ -428,7 +463,7 @@ class TestClient 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()->mutable_get_cover_art()->set_release( release ); + 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); @@ -772,7 +807,7 @@ int main() TestClient client( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4::loopback(), 5080)); // Use a dumb account in order to login TODO parametrize - if (!client.login("admin", "toto")) + if (!client.login("admin", "totoadmin")) throw std::runtime_error("login failed!"); // **** REVISION *** @@ -786,7 +821,7 @@ int main() client.getArtists(artists); std::cout << "Got " << artists.size() << " artists!" << std::endl; - BOOST_FOREACH(const ArtistInfo& artist, artists) + for (const ArtistInfo& artist : artists) std::cout << "Artist: '" << artist << "'" << std::endl; // ***** Genres ********* @@ -795,14 +830,14 @@ int main() client.getGenres(genres); std::cout << "Got " << genres.size() << " genres!" << std::endl; - BOOST_FOREACH(const GenreInfo& genre, genres) + for (const GenreInfo& genre : genres) std::cout << "Genre: '" << genre << "'" << std::endl; // **** Releases ****** std::cout << "Getting releases..." << std::endl; std::vector releases; client.getReleases(releases); - BOOST_FOREACH(const ReleaseInfo& release, releases) + for (const ReleaseInfo& release : releases) std::cout << "Release: '" << release << "'" << std::endl; // **** Tracks ****** @@ -811,7 +846,7 @@ int main() client.getTracks(tracks); std::cout << "Got " << tracks.size() << " tracks!" << std::endl; - BOOST_FOREACH(const TrackInfo& track, tracks) + for (const TrackInfo& track : tracks) std::cout << "Track: '" << track << "'" << std::endl; // Caution: long test! @@ -819,14 +854,17 @@ int main() { std::cout << "Getting artist for each genre..." << std::endl; // Get the artists for each genre - BOOST_FOREACH(const GenreInfo& genre, genres) + for (const GenreInfo& genre : genres) { std::cout << "Getting artists from genre '" << genre.name << "'... "; + SearchFilter filter; + filter.genreIds.push_back(genre.id); + std::vector artists; - client.getArtists(artists, std::vector(1, genre.name)); + client.getArtists(artists, filter); std::cout << "Found " << artists.size() << " artists!" << std::endl; - BOOST_FOREACH(const ArtistInfo& artist, artists) + for (const ArtistInfo& artist : artists) std::cout << "Genre '" << genre.name << "' -> Artist: " << artist << std::endl; } } @@ -834,14 +872,17 @@ int main() if (test(Test::ReleaseFilterArtist)) { std::cout << "Getting release for each artist..." << std::endl; - BOOST_FOREACH(const ArtistInfo& artist, artists) + for (const ArtistInfo& artist : artists) { std::cout << "Getting release from artist '" << artist.name << "'... "; + SearchFilter filter; + filter.artistIds.push_back(artist.id); + std::vector releases; - client.getReleases(releases, std::vector(1, artist.name) ); + client.getReleases(releases, filter); std::cout << "Found " << releases.size() << " releases!" << std::endl; - BOOST_FOREACH(const ReleaseInfo& release, releases) + for (const ReleaseInfo& release : releases) std::cout << "Artist '" << artist.name << "' -> Release: '" << release << "'" << std::endl; } } @@ -849,11 +890,14 @@ int main() if (test(Test::ReleaseFilterGenre)) { std::cout << "Getting release for each genre..." << std::endl; - BOOST_FOREACH(const GenreInfo& genre, genres) + for (const GenreInfo& genre : genres) { std::cout << "Getting release from genre '" << genre.name << "'... "; + SearchFilter filter; + filter.genreIds.push_back(genre.id); + std::vector releases; - client.getReleases(releases, std::vector(), std::vector(1, genre.name)); + client.getReleases(releases, filter); std::cout << "Found " << releases.size() << " releases!" << std::endl; BOOST_FOREACH(const ReleaseInfo& release, releases) @@ -865,13 +909,16 @@ int main() { std::cout << "Getting tracks for each artist..." << std::endl; // Get the tracks for each artist - BOOST_FOREACH(const ArtistInfo& artist, artists) + for (const ArtistInfo& artist : artists) { + SearchFilter filter; + filter.artistIds.push_back(artist.id); + std::vector tracks; - client.getTracks(tracks, std::vector(1, artist.name)); + client.getTracks(tracks, filter); std::cout << "Artist '" << artist.name << "', nb tracks = " << tracks.size() << std::endl; - BOOST_FOREACH(const TrackInfo& track, tracks) + for (const TrackInfo& track : tracks) std::cout << "Artist '" << artist.name << "', track: '" << track << "'" << std::endl; } } @@ -880,19 +927,19 @@ int main() if (test(Test::CoverByRelease)) { std::cout << "Getting cover for each release..." << std::endl; - BOOST_FOREACH(const ReleaseInfo& release, releases) + for (const ReleaseInfo& release : releases) { std::vector coverArts; - client.getCoverRelease(coverArts, release.name); + client.getCoverRelease(coverArts, release.id); if (writeCovers) { boost::filesystem::create_directory("cover"); - BOOST_FOREACH(const Cover coverArt, coverArts) + for (const Cover coverArt : coverArts) { std::ostringstream oss; oss << "cover/" << release.name << ".jpeg"; std::ofstream out(oss.str().c_str()); - BOOST_FOREACH(unsigned char c, coverArt.data) + for (unsigned char c : coverArt.data) out.put(c); } } @@ -904,7 +951,7 @@ int main() if (test(Test::CoverByTrack)) { std::cout << "Getting cover for each track..." << std::endl; - BOOST_FOREACH(const TrackInfo& track, tracks) + for (const TrackInfo& track : tracks) { std::vector coverArt; client.getCoverTrack(coverArt, track.id); @@ -916,7 +963,7 @@ int main() if (test(Test::Transcode)) { - BOOST_FOREACH(const TrackInfo& track, tracks) + for (const TrackInfo& track : tracks) { std::vector data; client.getMediaAudio(track.id, data); diff --git a/test/database b/test/database deleted file mode 100644 index 16da896e..00000000 Binary files a/test/database and /dev/null differ diff --git a/test/test.db b/test/test.db deleted file mode 100644 index da855d1d..00000000 Binary files a/test/test.db and /dev/null differ diff --git a/test/test2.db b/test/test2.db deleted file mode 100644 index 26d27b29..00000000 Binary files a/test/test2.db and /dev/null differ