From 74511ac03c45cdc44f3eb49b61dd001da6ee91fe Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 11 Oct 2014 13:15:01 +0200 Subject: [PATCH] [Remote] Adding filter genre to search for releases --- src/database/AudioTypes.hpp | 5 +- src/database/MediaDirectory.hpp | 2 +- src/database/Release.cpp | 41 +- .../server/AudioCollectionRequestHandler.cpp | 10 +- test/RemoteClient.cpp | 915 ++++++++++++++++++ 5 files changed, 966 insertions(+), 7 deletions(-) create mode 100644 test/RemoteClient.cpp diff --git a/src/database/AudioTypes.hpp b/src/database/AudioTypes.hpp index 89b01fbb..cb18542b 100644 --- a/src/database/AudioTypes.hpp +++ b/src/database/AudioTypes.hpp @@ -136,7 +136,10 @@ class Release static pointer getById(Wt::Dbo::Session& session, id_type id); static pointer getNone(Wt::Dbo::Session& session); static Wt::Dbo::collection getAllOrphans(Wt::Dbo::Session& session); - static Wt::Dbo::collection getAll(Wt::Dbo::Session& session, std::vector artistIds, int offset = -1, int size = -1); + static Wt::Dbo::collection getAll(Wt::Dbo::Session& session, + const std::vector& artistIds, + const std::vector& genreIds, + int offset = -1, int size = -1); // Create static pointer create(Wt::Dbo::Session& session, const std::string& name); diff --git a/src/database/MediaDirectory.hpp b/src/database/MediaDirectory.hpp index 2c829617..6eae74bb 100644 --- a/src/database/MediaDirectory.hpp +++ b/src/database/MediaDirectory.hpp @@ -44,7 +44,7 @@ class MediaDirectorySettings typedef Wt::Dbo::ptr pointer; - MediaDirectorySettings(); + MediaDirectorySettings(); // accessors static pointer get(Wt::Dbo::Session& session); diff --git a/src/database/Release.cpp b/src/database/Release.cpp index 8420f6e9..138c37b4 100644 --- a/src/database/Release.cpp +++ b/src/database/Release.cpp @@ -59,20 +59,53 @@ Release::create(Wt::Dbo::Session& session, const std::string& name) } Wt::Dbo::collection -Release::getAll(Wt::Dbo::Session& session, std::vector artistIds, int offset, int size) +Release::getAll(Wt::Dbo::Session& session, + const std::vector& artistIds, + const std::vector& genreIds, + int offset, int size) { std::string sqlQuery = "SELECT r FROM release r"; + if (!artistIds.empty() || !genreIds.empty()) + sqlQuery += " INNER JOIN track t ON t.release_id = r.id"; + if (!artistIds.empty()) { sqlQuery += " INNER JOIN artist a ON a.id = t.artist_id"; - sqlQuery += " INNER JOIN track t ON t.release_id = r.id"; } - Wt::Dbo::Query query = session.query( sqlQuery ).offset(offset).limit(size); + if (!genreIds.empty()) + { + sqlQuery += " INNER JOIN genre g ON g.id = t_g.genre_id"; + sqlQuery += " INNER JOIN track_genre t_g ON t_g.track_id = t.id AND t_g.genre_id = g.id"; + } + + WhereClause where; + { + WhereClause artistWhere; + + for (std::size_t i = 0; i < artistIds.size(); ++i) + artistWhere.Or( WhereClause("a.id = ?") ); + + where.And(artistWhere); + } + + { + WhereClause genreWhere; + + for (std::size_t i = 0; i < genreIds.size(); ++i) + genreWhere.Or( WhereClause("g.id = ?") ); + + where.And(genreWhere); + } + + Wt::Dbo::Query query = session.query( sqlQuery + " " + where.get() ).offset(offset).limit(size); BOOST_FOREACH(const Artist::id_type artistId, artistIds) - query.where("a.id = ?").bind(artistId); + query.bind(artistId); + + BOOST_FOREACH(const Genre::id_type genreId, genreIds) + query.bind(genreId); query.groupBy("r"); diff --git a/src/remote/server/AudioCollectionRequestHandler.cpp b/src/remote/server/AudioCollectionRequestHandler.cpp index f986e6c0..5eb8e004 100644 --- a/src/remote/server/AudioCollectionRequestHandler.cpp +++ b/src/remote/server/AudioCollectionRequestHandler.cpp @@ -207,9 +207,17 @@ AudioCollectionRequestHandler::processGetReleases(const AudioCollectionRequest:: for (int id = 0; id < request.artist_id_size(); ++id) artistIds.push_back( request.artist_id(id) ); + std::vector genreIds; + for (int id = 0; id < request.genre_id_size(); ++id) + genreIds.push_back( request.genre_id(id) ); + Wt::Dbo::Transaction transaction( _db.getSession() ); - Wt::Dbo::collection releases = Database::Release::getAll( _db.getSession(), artistIds, request.batch_parameter().offset(), static_cast(size)); + Wt::Dbo::collection releases + = Database::Release::getAll( _db.getSession(), + artistIds, + genreIds, + request.batch_parameter().offset(), static_cast(size)); typedef Wt::Dbo::collection< Database::Release::pointer > Releases; diff --git a/test/RemoteClient.cpp b/test/RemoteClient.cpp new file mode 100644 index 00000000..69903354 --- /dev/null +++ b/test/RemoteClient.cpp @@ -0,0 +1,915 @@ +/* + * 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 . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "remote/messages/Header.hpp" +#include "messages.pb.h" + +#include "TestDatabase.hpp" + +struct GenreInfo +{ + uint64_t id; + std::string name; +}; + +std::ostream& operator<<(std::ostream& os, const GenreInfo& info) +{ + os << "id = " << info.id << ", name = '" << info.name << "'"; + return os; +} + +struct ArtistInfo +{ + uint64_t id; + std::string name; +}; + +std::ostream& operator<<(std::ostream& os, const ArtistInfo& info) +{ + os << "id = " << info.id << ", name = '" << info.name << "'"; + return os; +} + +struct ReleaseInfo +{ + uint64_t id; + std::string name; +}; + +std::ostream& operator<<(std::ostream& os, const ReleaseInfo& info) +{ + os << "id = " << info.id << ", name = '" << info.name; + return os; +} + +struct TrackInfo +{ + uint64_t id; + + uint64_t release_id; + uint64_t artist_id; + std::vector genre_id; + + uint32_t disc_number; + uint32_t track_number; + + std::string name; + + boost::posix_time::time_duration duration; + + TrackInfo(): id(0), release_id(0), artist_id(0), disc_number(0), track_number(0) {} +}; + +std::ostream& operator<<(std::ostream& os, const TrackInfo& info) +{ + os << "id = " << info.id << ", name = '" << info.name << "', track_number = " << info.track_number << ", duration = " << info.duration; + return os; +} + +struct Cover +{ + std::string mimeType; + std::vector data; +}; + +// Ugly class for testing purposes + +class TestClient +{ + public: + TestClient(boost::asio::ip::tcp::endpoint endpoint) + : _context(boost::asio::ssl::context::sslv23), // be large on this + _socket(_ioService, _context) + { + boost::asio::ip::tcp::resolver resolver(_ioService); + + _socket.set_verify_mode(boost::asio::ssl::verify_peer); + _socket.set_verify_callback(boost::bind(&TestClient::verifyCertificate, this, _1, _2)); + + boost::asio::connect(_socket.lowest_layer(), resolver.resolve(endpoint)); + + _socket.handshake(boost::asio::ssl::stream_base::client); + } + + void getArtists(std::vector& artists, const std::vector& genreIds) + { + + const std::size_t requestedBatchSize = 128; + std::size_t offset = 0; + std::size_t res = 0; + + + while ((res = getArtists(artists, genreIds, offset, requestedBatchSize) ) > 0) + offset += res; + + } + + std::size_t getArtists(std::vector& artists, const std::vector& genreIds, std::size_t offset, std::size_t size) + { + std::size_t nbArtists = 0; + + // Send request + Remote::ClientMessage request; + + request.set_type( Remote::ClientMessage_Type_AudioCollectionRequest ); + + 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(uint64_t genreId, genreIds) + request.mutable_audio_collection_request()->mutable_get_artists()->add_genre_id(genreId); + + sendMsg(request); + + // Receive responses + Remote::ServerMessage response; + recvMsg(response); + + // Process message + if (!response.has_audio_collection_response()) + throw std::runtime_error("not an audio_collection_response!"); + + if (!response.audio_collection_response().has_artist_list()) + throw std::runtime_error("not an artist_list!"); + + 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()) + throw std::runtime_error("no artist name!"); + + ArtistInfo artist; + artist.id = response.audio_collection_response().artist_list().artists(i).id(); + artist.name = response.audio_collection_response().artist_list().artists(i).name(); + + artists.push_back( artist ); + nbArtists++; + } + + return nbArtists; + } + + void getGenres(std::vector& genres) + { + + const std::size_t requestedBatchSize = 8; + std::size_t offset = 0; + std::size_t res = 0; + + while ((res = getGenres(genres, offset, requestedBatchSize) ) > 0) + offset += res; + + } + + std::size_t getGenres(std::vector& genres, std::size_t offset, std::size_t size) + { + std::size_t nbAdded = 0; + + // Send request + Remote::ClientMessage request; + + request.set_type( Remote::ClientMessage_Type_AudioCollectionRequest ); + + 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); + + sendMsg(request); + + // Receive responses + Remote::ServerMessage response; + recvMsg(response); + + // Process message + if (!response.has_audio_collection_response()) + throw std::runtime_error("not an audio_collection_response!"); + + if (!response.audio_collection_response().has_genre_list()) + throw std::runtime_error("not an genre_list!"); + + 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()) + throw std::runtime_error("no genre name!"); + + GenreInfo genre; + genre.id = response.audio_collection_response().genre_list().genres(i).id(); + genre.name = response.audio_collection_response().genre_list().genres(i).name(); + + genres.push_back( genre ); + nbAdded++; + } + return nbAdded; + } + + void getReleases(std::vector& releases, const std::vector artistIds, const std::vector genreIds) + { + + const std::size_t requestedBatchSize = 256; + std::size_t offset = 0; + std::size_t res = 0; + + while ((res = getReleases(releases, artistIds, genreIds, offset, requestedBatchSize) ) > 0) + offset += res; + + } + + std::size_t getReleases(std::vector& releases, const std::vector artistIds, const std::vector genreIds, std::size_t offset, std::size_t size) + { + std::size_t nbAdded = 0; + + // Send request + Remote::ClientMessage request; + + request.set_type( Remote::ClientMessage_Type_AudioCollectionRequest ); + + 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(uint64_t artistId, artistIds) + request.mutable_audio_collection_request()->mutable_get_releases()->add_artist_id(artistId); + + BOOST_FOREACH(uint64_t genreId, genreIds) + request.mutable_audio_collection_request()->mutable_get_releases()->add_genre_id(genreId); + + sendMsg(request); + + // Receive responses + Remote::ServerMessage response; + recvMsg(response); + + // Process message + if (!response.audio_collection_response().has_type()) + throw std::runtime_error("Missing type!"); + +// if (!response.audio_collection_response().type() != Remote::ServerMessage::AudioCollectionResponse::TypeReleaseList) +// throw std::runtime_error("Bad type!"); + + if (!response.has_audio_collection_response()) + throw std::runtime_error("not an audio_collection_response!"); + + if (!response.audio_collection_response().has_release_list()) + throw std::runtime_error("not an release list!"); + + 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()) + throw std::runtime_error("no release name!"); + + ReleaseInfo release; + release.id = response.audio_collection_response().release_list().releases(i).id(); + release.name = response.audio_collection_response().release_list().releases(i).name(); + + releases.push_back( release ); + nbAdded++; + } + return nbAdded; + } + + void getTracks(std::vector& tracks, const std::vector artistIds, const std::vector releaseIds, const std::vector genreIds) + { + const std::size_t requestedBatchSize = 0; + std::size_t offset = 0; + std::size_t res = 0; + + while ((res = getTracks(tracks, artistIds, releaseIds, genreIds, offset, requestedBatchSize) ) > 0) + offset += res; + } + + std::size_t getTracks(std::vector& tracks, + const std::vector artistIds, + const std::vector releaseIds, + const std::vector genreIds, + std::size_t offset, + std::size_t size) + { + std::size_t nbAdded = 0; + + // Send request + Remote::ClientMessage request; + + request.set_type( Remote::ClientMessage_Type_AudioCollectionRequest ); + + 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(uint64_t artistId, artistIds) + request.mutable_audio_collection_request()->mutable_get_tracks()->add_artist_id(artistId); + BOOST_FOREACH(uint64_t releaseId, releaseIds) + request.mutable_audio_collection_request()->mutable_get_tracks()->add_release_id(releaseId); + BOOST_FOREACH(uint64_t genreId, genreIds) + request.mutable_audio_collection_request()->mutable_get_tracks()->add_genre_id(genreId); + + sendMsg(request); + + // Receive responses + Remote::ServerMessage response; + recvMsg(response); + + // Process message + if (!response.has_audio_collection_response()) + throw std::runtime_error("not an audio_collection_response!"); + + if (!response.audio_collection_response().has_track_list()) + throw std::runtime_error("not an track list!"); + + for (int i = 0; i < response.audio_collection_response().track_list().tracks_size(); ++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(); + + 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(); + + + tracks.push_back( track ); + nbAdded++; + } + + return nbAdded; + } + + + void getMediaAudio(uint64_t audioId, std::vector& data) + { + + // Prepare + uint32_t handle = mediaAudioPrepare( audioId ); + + // Get while available + mediaGet( handle, data ); + + // Terminate + mediaTerminate( handle ); + } + + void getCoverTrack(std::vector& coverArt, uint64_t trackId) + { + // Send request + Remote::ClientMessage request; + + request.set_type( Remote::ClientMessage::AudioCollectionRequest ); + + request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest::TypeGetCoverArt); + request.mutable_audio_collection_request()->mutable_get_cover_art()->set_type( Remote::AudioCollectionRequest::GetCoverArt::TypeGetCoverArtTrack); + request.mutable_audio_collection_request()->mutable_get_cover_art()->set_track_id( trackId ); + request.mutable_audio_collection_request()->mutable_get_cover_art()->set_size( 256 ); + sendMsg(request); + + // Receive responses + Remote::ServerMessage response; + recvMsg(response); + + // Process message + if (!response.has_audio_collection_response()) + throw std::runtime_error("not an audio_collection_response!"); + + for (int i = 0; i < response.audio_collection_response().cover_art_size(); ++i) + { + Cover cover; + cover.mimeType = response.audio_collection_response().cover_art(i).mime_type(); + cover.data.assign(response.audio_collection_response().cover_art(i).data().begin(), response.audio_collection_response().cover_art(i).data().end());; + + coverArt.push_back(cover); + } + } + + void getCoverRelease(std::vector& coverArt, uint64_t releaseId) + { + // Send request + Remote::ClientMessage request; + + request.set_type( Remote::ClientMessage::AudioCollectionRequest ); + + request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest::TypeGetCoverArt); + request.mutable_audio_collection_request()->mutable_get_cover_art()->set_type( Remote::AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease); + request.mutable_audio_collection_request()->mutable_get_cover_art()->set_release_id( releaseId ); + request.mutable_audio_collection_request()->mutable_get_cover_art()->set_size( 256 ); + + sendMsg(request); + + // Receive responses + Remote::ServerMessage response; + recvMsg(response); + + // Process message + if (!response.has_audio_collection_response()) + throw std::runtime_error("not an audio_collection_response!"); + + for (int i = 0; i < response.audio_collection_response().cover_art_size(); ++i) + { + Cover cover; + cover.mimeType = response.audio_collection_response().cover_art(i).mime_type(); + cover.data.assign(response.audio_collection_response().cover_art(i).data().begin(), response.audio_collection_response().cover_art(i).data().end());; + + coverArt.push_back(cover); + } + + } + + std::string getRevision(void) + { + // Send request + Remote::ClientMessage request; + + request.set_type( Remote::ClientMessage::AudioCollectionRequest ); + + request.mutable_audio_collection_request()->set_type( Remote::AudioCollectionRequest::TypeGetRevision); + + sendMsg(request); + + // Receive responses + Remote::ServerMessage response; + recvMsg(response); + + // Process message + if (!response.has_audio_collection_response()) + throw std::runtime_error("not an audio_collection_response!"); + + if (!response.audio_collection_response().has_revision()) + throw std::runtime_error("not a revision!"); + + return response.audio_collection_response().revision().rev(); + } + + bool login(const std::string& username, const std::string& password) + { + // Send request + Remote::ClientMessage request; + + request.set_type( Remote::ClientMessage::AuthRequest ); + + request.mutable_auth_request()->set_type( Remote::AuthRequest::TypePassword); + request.mutable_auth_request()->mutable_password()->set_user_login( username ); + request.mutable_auth_request()->mutable_password()->set_user_password( password ); + + sendMsg(request); + + // Receive responses + Remote::ServerMessage response; + recvMsg(response); + + // Process message + if (!response.has_auth_response()) + throw std::runtime_error("not an auth response!"); + + if (!response.auth_response().has_password_result()) + throw std::runtime_error("not a password result!"); + + switch( response.auth_response().password_result().type()) + { + case Remote::AuthResponse::PasswordResult::TypePasswordValid: + return true; + case Remote::AuthResponse::PasswordResult::TypePasswordInvalid: + return false; + case Remote::AuthResponse::PasswordResult::TypeLoginThrottling: + if (response.auth_response().password_result().has_delay()) + std::cerr << "Has to wait for " << response.auth_response().password_result().delay() << " seconds" << std::endl; + + return false; + default: + throw std::runtime_error("bad password result type"); + } + } + + private: + + bool verifyCertificate(bool preverified, boost::asio::ssl::verify_context& ctx) + { + // In this example we will simply print the certificate's subject name. + std::array subject_name; + + X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle()); + X509_NAME_oneline(X509_get_subject_name(cert), subject_name.data(), subject_name.size()); + + std::cout << "Verifying '" << std::string(subject_name.data()) << "', preverified = " << std::boolalpha << preverified << std::endl; + + return true; // preverified; + } + + uint32_t mediaAudioPrepare(uint64_t audioId) + { + + // Send request + Remote::ClientMessage request; + + request.set_type( Remote::ClientMessage_Type_MediaRequest); + + request.mutable_media_request()->set_type( Remote::MediaRequest_Type_TypeMediaPrepare); + request.mutable_media_request()->mutable_prepare()->set_type(Remote::MediaRequest_Prepare_Type_AudioRequest); + + // Set fields + request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_track_id( audioId ); + request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_codec_type( Remote::MediaRequest::Prepare::AudioCodecTypeOGA ); + request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_bitrate( Remote::MediaRequest::Prepare::AudioBitrate_64_kbps ); + + std::cout << "Sending prepare request" << std::endl; + sendMsg(request); + + std::cout << "Waiting for response" << std::endl; + // Receive response + Remote::ServerMessage response; + recvMsg(response); + + std::cout << "Got a response" << std::endl; + + // Process message + if (!response.has_media_response()) + throw std::runtime_error("Preapre: not an media reponse!"); + + if (!response.media_response().has_prepare_result()) + throw std::runtime_error("Prepare: not an prepare result msg!"); + + if (!response.media_response().prepare_result().has_handle()) + throw std::runtime_error("Prepare: cannot get handle"); + + return response.media_response().prepare_result().handle(); + } + + void mediaGet(uint32_t handle, std::vector& data) + { + while (mediaGetPart(handle, data) > 0) + ; + } + + + std::size_t mediaGetPart(uint32_t handle, std::vector& data) + { + // Send request + Remote::ClientMessage request; + + request.set_type( Remote::ClientMessage::MediaRequest); + + request.mutable_media_request()->set_type( Remote::MediaRequest::TypeMediaGetPart); + request.mutable_media_request()->mutable_get_part()->set_handle(handle); + request.mutable_media_request()->mutable_get_part()->set_requested_data_size(65536); + + std::cout << "Sending GetPart request" << std::endl; + sendMsg(request); + + std::cout << "Waiting for response" << std::endl; + // Receive response + Remote::ServerMessage response; + recvMsg(response); + + std::cout << "Got a response" << std::endl; + + if (!response.has_media_response()) + throw std::runtime_error("not an media response"); + + // Process message + if (response.media_response().type() != Remote::MediaResponse::TypePartResult) + throw std::runtime_error("GetPart: not a Part response!"); + + if (!response.media_response().has_part_result()) + throw std::runtime_error("GetPart: does not have a Part result!"); + + std::copy(response.media_response().part_result().data().begin(), response.media_response().part_result().data().end(), std::back_inserter(data)); + + return response.media_response().part_result().data().size(); + } + + void mediaTerminate( uint32_t handle ) + { + // Send request + Remote::ClientMessage request; + + request.set_type( Remote::ClientMessage::MediaRequest); + + request.mutable_media_request()->set_type( Remote::MediaRequest::TypeMediaTerminate); + request.mutable_media_request()->mutable_terminate()->set_handle(handle); + + sendMsg(request); + + // Receive response + Remote::ServerMessage response; + recvMsg(response); + + + // Process message + if (!response.has_media_response()) + throw std::runtime_error("Terminate: not an media response"); + + if (!response.media_response().has_terminate_result()) + throw std::runtime_error("Terminate: not an terminate msg!"); + + } + + + void sendMsg(const ::google::protobuf::Message& message) + { + std::ostream os(&_outputStreamBuf); + + // Serialize message + if (message.SerializeToOstream(&os)) + { + + if (_outputStreamBuf.size() > Remote::Header::max_data_size) + { + std::ostringstream oss; oss << "Message too big = " << _outputStreamBuf.size() << " bytes! (max is " << Remote::Header::max_data_size << ")" << std::endl; + throw std::runtime_error("Message to big!"); + } + + // Send message header + std::array headerBuffer; + + // Generate header content + { + Remote::Header header; + header.setDataSize(_outputStreamBuf.size()); + header.to_buffer(headerBuffer); + } + + boost::asio::write(_socket, boost::asio::buffer(headerBuffer)); + + // Send serialized message + std::size_t n = boost::asio::write(_socket, + _outputStreamBuf.data(), + boost::asio::transfer_exactly(_outputStreamBuf.size())); + assert(n == _outputStreamBuf.size()); + + _outputStreamBuf.consume(n); + + } + else + { + std::cerr << "Cannot serialize message!" << std::endl; + throw std::runtime_error("message.SerializeToString failed!"); + } + + } + + void recvMsg(::google::protobuf::Message& message) + { + std::istream is(&_inputStreamBuf); + + { + // reserve bytes in output sequence + boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(Remote::Header::size); + + std::size_t n = boost::asio::read(_socket, + bufs, + boost::asio::transfer_exactly(Remote::Header::size)); + + assert(n == Remote::Header::size); + _inputStreamBuf.commit(n); + + } + + Remote::Header header; + if (!header.from_istream(is)) + throw std::runtime_error("Cannot read header from buffer!"); + + // Read in a stream buffer + { + // reserve bytes in output sequence + boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(header.getDataSize()); + + std::size_t n = boost::asio::read(_socket, + bufs, + boost::asio::transfer_exactly(header.getDataSize())); + + assert(n == header.getDataSize()); + _inputStreamBuf.commit(n); + + if (!message.ParseFromIstream(&is)) + throw std::runtime_error("message.ParseFromIstream failed!"); + + } + } + + typedef boost::asio::ssl::stream ssl_socket; + + boost::asio::io_service _ioService; + boost::asio::ssl::context _context; + ssl_socket _socket; + + boost::asio::streambuf _inputStreamBuf; + boost::asio::streambuf _outputStreamBuf; + +}; + +enum class Test { + ArtistFilters, + ReleaseFilterArtist, + ReleaseFilterGenre, + TrackFilters, + Cover, + Transcode, +}; + +/* Uncomment test to be disabled */ +std::set tests = { +// Test::ArtistFilters, +// Test::ReleaseFilterArtist, + Test::ReleaseFilterGenre, +// Test::TrackFilters, +// Test::Cover, +// Test::Transcode, +}; + +bool test(Test t) +{ + return (tests.find(t) != tests.end()); +} + +int main() +{ + try { + bool writeCovers = false; + + std::cout << "Running test..." << std::endl; + + // Client + // connect to loopback TODO parametrize + 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", "admin")) + throw std::runtime_error("login failed!"); + + // **** REVISION *** + std::cout << "Getting revision..." << std::endl; + std::string rev = client.getRevision(); + std::cout << "Revision '" << rev << "'" << std::endl; + + // ****** Artists ********* + std::cout << "Getting artists..." << std::endl; + std::vector artists; + client.getArtists(artists, std::vector()); + + std::cout << "Got " << artists.size() << " artists!" << std::endl; + BOOST_FOREACH(const ArtistInfo& artist, artists) + std::cout << "Artist: '" << artist << "'" << std::endl; + + // ***** Genres ********* + std::cout << "Getting genres..." << std::endl; + std::vector genres; + client.getGenres(genres); + + std::cout << "Got " << genres.size() << " genres!" << std::endl; + BOOST_FOREACH(const GenreInfo& genre, genres) + std::cout << "Genre: '" << genre << "'" << std::endl; + + // **** Releases ****** + std::cout << "Getting releases..." << std::endl; + std::vector releases; + client.getReleases(releases, std::vector(), std::vector()); + BOOST_FOREACH(const ReleaseInfo& release, releases) + std::cout << "Release: '" << release << "'" << std::endl; + + // **** Tracks ****** + std::cout << "Getting tracks..." << std::endl; + std::vector tracks; + client.getTracks(tracks, std::vector(), std::vector(), std::vector()); + BOOST_FOREACH(const TrackInfo& track, tracks) + std::cout << "Track: '" << track << "'" << std::endl; + + // Caution: long test! + if (test(Test::ArtistFilters)) + { + std::cout << "Getting artist for each genre..." << std::endl; + // Get the artists for each genre + BOOST_FOREACH(const GenreInfo& genre, genres) + { + std::cout << "Getting artists from genre '" << genre.name << "'" << std::endl; + std::vector artists; + client.getArtists(artists, std::vector(1, genre.id)); + + BOOST_FOREACH(const ArtistInfo& artist, artists) + std::cout << "Genre '" << genre.name << "' -> Artist: " << artist << std::endl; + } + } + + if (test(Test::ReleaseFilterArtist)) + { + std::cout << "Getting release for each artist..." << std::endl; + BOOST_FOREACH(const ArtistInfo& artist, artists) + { + std::cout << "Getting release from artist '" << artist.name << "'" << std::endl; + std::vector releases; + client.getReleases(releases, std::vector(1, artist.id), std::vector()); + + BOOST_FOREACH(const ReleaseInfo& release, releases) + std::cout << "Artist '" << artist.name << "' -> Release: '" << release << "'" << std::endl; + } + } + + if (test(Test::ReleaseFilterGenre)) + { + std::cout << "Getting release for each genre..." << std::endl; + BOOST_FOREACH(const GenreInfo& genre, genres) + { + std::cout << "Getting release from genre '" << genre.name << "'" << std::endl; + std::vector releases; + client.getReleases(releases, std::vector(), std::vector(1, genre.id)); + + BOOST_FOREACH(const ReleaseInfo& release, releases) + std::cout << "Genre '" << genre.name << "' -> Release: '" << release << "'" << std::endl; + } + } + + if (test(Test::TrackFilters)) + { + std::cout << "Getting tracks for each artist..." << std::endl; + // Get the tracks for each artist + BOOST_FOREACH(const ArtistInfo& artist, artists) + { + std::vector tracks; + client.getTracks(tracks, std::vector(1, artist.id), std::vector(), std::vector()); + + std::cout << "Artist '" << artist.name << "', nb tracks = " << tracks.size() << std::endl; + BOOST_FOREACH(const TrackInfo& track, tracks) + std::cout << "Artist '" << artist.name << "', track: '" << track << "'" << std::endl; + } + } + + // ***** Covers ******* + if (test(Test::Cover)) + { + std::cout << "Getting cover for each release..." << std::endl; + BOOST_FOREACH(const ReleaseInfo& release, releases) + { + std::vector coverArts; + client.getCoverRelease(coverArts, release.id); + + if (writeCovers) + { + boost::filesystem::create_directory("cover"); + BOOST_FOREACH(const Cover coverArt, coverArts) + { + std::ostringstream oss; oss << "cover/" << release.id << "." << release.name << ".jpeg"; + std::ofstream out(oss.str().c_str()); + BOOST_FOREACH(unsigned char c, coverArt.data) + out.put(c); + } + } + + std::cout << "Release '" << release << "', spotted " << coverArts.size() << " covers!" << std::endl; + } + + std::cout << "Getting cover for each track..." << std::endl; + BOOST_FOREACH(const TrackInfo& track, tracks) + { + std::vector coverArt; + client.getCoverTrack(coverArt, track.id); + + std::cout << "Track '" << track << "', spotted " << coverArt.size() << " covers!" << std::endl; + } + + } + + if (test(Test::Transcode)) + { + BOOST_FOREACH(const TrackInfo& track, tracks) + { + std::vector data; + client.getMediaAudio(track.id, data); + std::cout << "Media size = " << data.size() << std::endl; + } + } + + std::cout << "End of tests!" << std::endl; + } + catch(std::exception& e) + { + std::cerr << "Caught exception " << e.what() << std::endl; + return EXIT_FAILURE; + } + + std::cout << "Normal quit..." << std::endl; + return EXIT_SUCCESS; +} + +