Remote. Reworked the 'media' messages. Updated test. Tested OK
This commit is contained in:
@@ -26,6 +26,7 @@
|
|||||||
|
|
||||||
[Transcode]
|
[Transcode]
|
||||||
- some early playback end spotted on flac files or huge audio files (> several hours)
|
- some early playback end spotted on flac files or huge audio files (> several hours)
|
||||||
|
- do not use a queue to output data, but just fill produced bytes into a buffer and let the transcode user decide what to do with it
|
||||||
|
|
||||||
[UI]
|
[UI]
|
||||||
- handle internationalization
|
- handle internationalization
|
||||||
|
|||||||
+947
-257
File diff suppressed because it is too large
Load Diff
+712
-227
File diff suppressed because it is too large
Load Diff
+51
-25
@@ -2,15 +2,6 @@ import "common.proto";
|
|||||||
|
|
||||||
package Remote;
|
package Remote;
|
||||||
|
|
||||||
enum AudioCodecType
|
|
||||||
{
|
|
||||||
CodecTypeOGA = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum VideoCodecType
|
|
||||||
{
|
|
||||||
CodecTypeOGV = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
message MediaRequest
|
message MediaRequest
|
||||||
@@ -19,11 +10,32 @@ message MediaRequest
|
|||||||
message Prepare
|
message Prepare
|
||||||
{
|
{
|
||||||
|
|
||||||
|
enum AudioCodecType
|
||||||
|
{
|
||||||
|
AudioCodecTypeOGA = 1;
|
||||||
|
}
|
||||||
|
enum AudioBitrate {
|
||||||
|
AudioBitrate_32_kbps = 1;
|
||||||
|
AudioBitrate_64_kbps = 2;
|
||||||
|
AudioBitrate_96_kbps = 3;
|
||||||
|
AudioBitrate_128_kbps = 4;
|
||||||
|
AudioBitrate_192_kbps = 5;
|
||||||
|
AudioBitrate_256_kbps = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VideoCodecType
|
||||||
|
{
|
||||||
|
VideoCodecTypeOGV = 1;
|
||||||
|
}
|
||||||
|
enum VideoBitrate {
|
||||||
|
VideoBitrate_512_kbps = 1;
|
||||||
|
}
|
||||||
|
|
||||||
message Audio
|
message Audio
|
||||||
{
|
{
|
||||||
required int64 track_id = 1; // Id if the media
|
required int64 track_id = 1; // Id if the media
|
||||||
optional AudioCodecType codec_type = 2;
|
required AudioCodecType codec_type = 2;
|
||||||
optional uint32 bitrate = 3;
|
required AudioBitrate bitrate = 3;
|
||||||
optional uint32 stream_idx = 4;
|
optional uint32 stream_idx = 4;
|
||||||
optional uint32 offset_secs = 5;
|
optional uint32 offset_secs = 5;
|
||||||
}
|
}
|
||||||
@@ -31,12 +43,13 @@ message MediaRequest
|
|||||||
message Video
|
message Video
|
||||||
{
|
{
|
||||||
required int64 video_id = 1; // Id if the media
|
required int64 video_id = 1; // Id if the media
|
||||||
optional VideoCodecType codec_type = 2;
|
required VideoCodecType codec_type = 2;
|
||||||
optional uint32 bitrate = 3;
|
required AudioBitrate audio_bitrate = 3;
|
||||||
optional uint32 offset_secs = 4;
|
required VideoBitrate video_bitrate = 4;
|
||||||
optional uint32 audio_stream_idx = 5;
|
optional uint32 offset_secs = 5;
|
||||||
optional uint32 video_stream_idx = 6;
|
optional uint32 audio_stream_idx = 6;
|
||||||
optional uint32 subtitle_stream_idx = 7;
|
optional uint32 video_stream_idx = 7;
|
||||||
|
optional uint32 subtitle_stream_idx = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Type {
|
enum Type {
|
||||||
@@ -52,12 +65,13 @@ message MediaRequest
|
|||||||
|
|
||||||
message GetPart
|
message GetPart
|
||||||
{
|
{
|
||||||
required uint32 requested_data_size = 1; // Amount of data requested. May receive more or less. May receive 0 byte if complete
|
required uint32 handle = 1; // handle that uniquely identify the media. Get using the Prepare request
|
||||||
|
required uint32 requested_data_size = 2; // Amount of data requested. May receive more or less. May receive 0 byte if complete
|
||||||
}
|
}
|
||||||
|
|
||||||
message Terminate
|
message Terminate
|
||||||
{
|
{
|
||||||
|
required uint32 handle = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Type
|
enum Type
|
||||||
@@ -77,20 +91,32 @@ message MediaRequest
|
|||||||
|
|
||||||
message MediaResponse
|
message MediaResponse
|
||||||
{
|
{
|
||||||
message Part
|
|
||||||
|
message PrepareResult
|
||||||
|
{
|
||||||
|
optional uint32 handle = 1; // handle iif prepare request was ok
|
||||||
|
}
|
||||||
|
|
||||||
|
message PartResult
|
||||||
|
{
|
||||||
|
optional bytes data = 2; // 0 bytes when the media is over, nothing when the request is ill formed
|
||||||
|
}
|
||||||
|
|
||||||
|
message TerminateResult
|
||||||
{
|
{
|
||||||
required bytes data = 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Type
|
enum Type
|
||||||
{
|
{
|
||||||
TypeError = 1;
|
TypePrepareResult = 1;
|
||||||
TypePart = 2;
|
TypePartResult = 2;
|
||||||
|
TypeTerminateResult = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
required Type type = 1;
|
required Type type = 1;
|
||||||
|
|
||||||
optional Error error = 2;
|
optional PrepareResult prepare_result = 2;
|
||||||
optional Part part = 3;
|
optional PartResult part_result = 3;
|
||||||
|
optional TerminateResult terminate_result = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,14 @@ MediaRequestHandler::process(const MediaRequest& request, MediaResponse& respons
|
|||||||
if (request.has_prepare())
|
if (request.has_prepare())
|
||||||
{
|
{
|
||||||
if (request.prepare().has_audio())
|
if (request.prepare().has_audio())
|
||||||
res = processAudioPrepare(request.prepare().audio(), response);
|
res = processAudioPrepare(request.prepare().audio(), *response.mutable_prepare_result());
|
||||||
else if (request.prepare().has_video())
|
else if (request.prepare().has_video())
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Video prepare not supported!";
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Video prepare not supported!";
|
||||||
else
|
else
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
|
||||||
|
|
||||||
|
if (res)
|
||||||
|
response.set_type(MediaResponse::TypePrepareResult);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
|
||||||
@@ -36,16 +38,26 @@ MediaRequestHandler::process(const MediaRequest& request, MediaResponse& respons
|
|||||||
|
|
||||||
case MediaRequest::TypeMediaGetPart:
|
case MediaRequest::TypeMediaGetPart:
|
||||||
if (request.has_get_part())
|
if (request.has_get_part())
|
||||||
res = processGetPart(request.get_part(), response);
|
{
|
||||||
|
res = processGetPart(request.get_part(), *response.mutable_part_result());
|
||||||
|
if (res)
|
||||||
|
response.set_type(MediaResponse::TypePartResult);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaGet!";
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaGet!";
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case MediaRequest::TypeMediaTerminate:
|
case MediaRequest::TypeMediaTerminate:
|
||||||
if (request.has_terminate())
|
if (request.has_terminate())
|
||||||
res = processTerminate(request.terminate(), response);
|
{
|
||||||
|
res = processTerminate(request.terminate(), *response.mutable_terminate_result());
|
||||||
|
if (res)
|
||||||
|
response.set_type(MediaResponse::TypePartResult);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaTerminate!";
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaTerminate!";
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@@ -56,117 +68,128 @@ MediaRequestHandler::process(const MediaRequest& request, MediaResponse& respons
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MediaRequestHandler::processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse& response)
|
MediaRequestHandler::processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse::PrepareResult& response)
|
||||||
{
|
{
|
||||||
// TODO, get user default values
|
std::size_t bitrate;
|
||||||
Transcode::Format::Encoding format = Transcode::Format::OGA;
|
Transcode::Format::Encoding format;
|
||||||
std::size_t bitrate = 128000;
|
|
||||||
|
|
||||||
if (request.has_codec_type())
|
switch( request.codec_type())
|
||||||
{
|
{
|
||||||
switch( request.codec_type())
|
case MediaRequest::Prepare::AudioCodecTypeOGA: format = Transcode::Format::OGA; break;
|
||||||
{
|
default:
|
||||||
case AudioCodecType::CodecTypeOGA:
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled codec type = " << request.codec_type();
|
||||||
format = Transcode::Format::OGA;
|
return false;
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled codec type = " << request.codec_type();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (request.has_bitrate())
|
|
||||||
bitrate = request.bitrate();
|
|
||||||
|
|
||||||
if (_transcoder)
|
|
||||||
{
|
|
||||||
response.mutable_error()->set_error(true);
|
|
||||||
response.mutable_error()->set_message("Transcode already in progress");
|
|
||||||
response.set_type(MediaResponse::TypeError);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction( _db.getSession());
|
switch( request.bitrate() )
|
||||||
|
|
||||||
Database::Track::pointer track = Database::Track::getById( _db.getSession(), request.track_id() );
|
|
||||||
|
|
||||||
if (!track)
|
|
||||||
{
|
{
|
||||||
response.mutable_error()->set_error(true);
|
case MediaRequest::Prepare::AudioBitrate_32_kbps: bitrate = 32000; break;
|
||||||
response.mutable_error()->set_message("Cannot find requested track!");
|
case MediaRequest::Prepare::AudioBitrate_64_kbps: bitrate = 64000; break;
|
||||||
response.set_type(MediaResponse::TypeError);
|
case MediaRequest::Prepare::AudioBitrate_96_kbps: bitrate = 96000; break;
|
||||||
|
case MediaRequest::Prepare::AudioBitrate_128_kbps: bitrate = 128000; break;
|
||||||
|
case MediaRequest::Prepare::AudioBitrate_192_kbps: bitrate = 192000; break;
|
||||||
|
case MediaRequest::Prepare::AudioBitrate_256_kbps: bitrate = 256000; break;
|
||||||
|
default:
|
||||||
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled bitrate type = " << request.bitrate();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO use user's bitrate limits!
|
||||||
|
// TODO limit transcoder number by user?
|
||||||
|
|
||||||
|
if (_transcoders.size() + 1 > _maxTranscoders)
|
||||||
|
{
|
||||||
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Transcoder limit reached!" << std::endl;
|
||||||
|
// Just answer an empty response, dont delete existing trasncode jobs
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
Wt::Dbo::Transaction transaction( _db.getSession());
|
||||||
|
|
||||||
|
Database::Track::pointer track = Database::Track::getById( _db.getSession(), request.track_id() );
|
||||||
|
if (!track)
|
||||||
|
{
|
||||||
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Requested track does not exist" << std::endl;
|
||||||
|
// Track does no longer exist, just answer an empty response
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
Transcode::InputMediaFile inputFile(track->getPath());
|
Transcode::InputMediaFile inputFile(track->getPath());
|
||||||
Transcode::Parameters parameters(inputFile, Transcode::Format::get( format ));
|
Transcode::Parameters parameters(inputFile, Transcode::Format::get( format ));
|
||||||
parameters.setBitrate( Transcode::Stream::Audio, bitrate);
|
parameters.setBitrate( Transcode::Stream::Audio, bitrate);
|
||||||
|
|
||||||
_transcoder = std::make_shared<Transcode::AvConvTranscoder>( parameters );
|
std::shared_ptr<Transcode::AvConvTranscoder> transcoder = std::make_shared<Transcode::AvConvTranscoder>( parameters );
|
||||||
|
|
||||||
response.mutable_error()->set_error(false);
|
// now get a unique id (relative to this connection!)
|
||||||
response.mutable_error()->set_message("");
|
uint32_t handle = _curHandle++;
|
||||||
response.set_type(MediaResponse::TypeError);
|
assert(_transcoders.find(handle) == _transcoders.end());
|
||||||
|
|
||||||
|
_transcoders[handle] = transcoder;
|
||||||
|
|
||||||
|
response.set_handle(handle);
|
||||||
|
|
||||||
|
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Set up new transcode, handle = " << handle;
|
||||||
}
|
}
|
||||||
catch(std::exception& e)
|
catch(std::exception& e)
|
||||||
{
|
{
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Caught exception: " << e.what();
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Caught exception: " << e.what();
|
||||||
response.mutable_error()->set_error(true);
|
return false;
|
||||||
response.mutable_error()->set_message("exception: " + std::string(e.what()));
|
|
||||||
response.set_type(MediaResponse::TypeError);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MediaRequestHandler::processGetPart(const MediaRequest::GetPart& request, MediaResponse& response)
|
MediaRequestHandler::processGetPart(const MediaRequest::GetPart& request, MediaResponse::PartResult& response)
|
||||||
{
|
{
|
||||||
std::size_t dataSize = request.requested_data_size();
|
std::size_t dataSize = request.requested_data_size();
|
||||||
if (dataSize > _maxPartSize)
|
if (dataSize > _maxPartSize)
|
||||||
dataSize = _maxPartSize;
|
dataSize = _maxPartSize;
|
||||||
|
|
||||||
if (!_transcoder)
|
if (_transcoders.find(request.handle()) == _transcoders.end())
|
||||||
{
|
{
|
||||||
response.mutable_error()->set_error(true);
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle();
|
||||||
response.mutable_error()->set_message("No transcoder set!");
|
|
||||||
response.set_type(MediaResponse::TypeError);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (!_transcoder->isComplete() && _transcoder->getOutputData().size() < dataSize)
|
std::shared_ptr<Transcode::AvConvTranscoder> transcoder = _transcoders[request.handle()];
|
||||||
_transcoder->process();
|
|
||||||
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler::processGetPart, isComplete = " << std::boolalpha << _transcoder->isComplete() << ", size = " << _transcoder->getOutputData().size();
|
while (!transcoder->isComplete() && transcoder->getOutputData().size() < dataSize)
|
||||||
|
transcoder->process();
|
||||||
|
|
||||||
|
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler::processGetPart, handle = " << request.handle() << ", isComplete = " << std::boolalpha << transcoder->isComplete() << ", size = " << transcoder->getOutputData().size();
|
||||||
|
|
||||||
Transcode::AvConvTranscoder::data_type::iterator itEnd;
|
Transcode::AvConvTranscoder::data_type::iterator itEnd;
|
||||||
if (_transcoder->getOutputData().size() > dataSize)
|
if (transcoder->getOutputData().size() > dataSize)
|
||||||
itEnd = _transcoder->getOutputData().begin() + dataSize;
|
itEnd = transcoder->getOutputData().begin() + dataSize;
|
||||||
else
|
else
|
||||||
itEnd = _transcoder->getOutputData().end();
|
itEnd = transcoder->getOutputData().end();
|
||||||
|
|
||||||
response.set_type(MediaResponse::TypePart);
|
std::copy(transcoder->getOutputData().begin(), itEnd, std::back_inserter(*response.mutable_data()));
|
||||||
std::copy(_transcoder->getOutputData().begin(), itEnd, std::back_inserter(*response.mutable_part()->mutable_data()));
|
|
||||||
|
|
||||||
// Consume sent bytes
|
// Consume sent bytes
|
||||||
_transcoder->getOutputData().erase(_transcoder->getOutputData().begin(), itEnd);
|
transcoder->getOutputData().erase(transcoder->getOutputData().begin(), itEnd);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool
|
bool
|
||||||
MediaRequestHandler::processTerminate(const MediaRequest::Terminate& /*request*/, MediaResponse& response)
|
MediaRequestHandler::processTerminate(const MediaRequest::Terminate& request, MediaResponse::TerminateResult& response)
|
||||||
{
|
{
|
||||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler: resetting transcoder";
|
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler: resetting transcoder for handle " << request.handle();
|
||||||
_transcoder.reset();
|
|
||||||
|
|
||||||
assert(!_transcoder);
|
if (_transcoders.find(request.handle()) == _transcoders.end())
|
||||||
|
{
|
||||||
response.mutable_error()->set_error(false);
|
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle();
|
||||||
response.mutable_error()->set_message("");
|
return true;
|
||||||
response.set_type(MediaResponse::TypeError);
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_transcoders.erase(request.handle());
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#ifndef REMOTE_MEDIA_REQUEST_HANDLER
|
#ifndef REMOTE_MEDIA_REQUEST_HANDLER
|
||||||
#define REMOTE_MEDIA_REQUEST_HANDLER
|
#define REMOTE_MEDIA_REQUEST_HANDLER
|
||||||
|
|
||||||
|
#include <map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "messages/media.pb.h"
|
#include "messages/media.pb.h"
|
||||||
@@ -20,17 +21,20 @@ class MediaRequestHandler
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
bool processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse& response);
|
bool processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse::PrepareResult& response);
|
||||||
bool processGetPart(const MediaRequest::GetPart& request, MediaResponse& response);
|
bool processGetPart(const MediaRequest::GetPart& request, MediaResponse::PartResult& response);
|
||||||
bool processTerminate(const MediaRequest::Terminate& request, MediaResponse& response);
|
bool processTerminate(const MediaRequest::Terminate& request, MediaResponse::TerminateResult& response);
|
||||||
|
|
||||||
// bool processVideoPrepare(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response);
|
// bool processVideoPrepare(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response);
|
||||||
|
|
||||||
std::shared_ptr<Transcode::AvConvTranscoder> _transcoder;
|
std::map<uint32_t, std::shared_ptr<Transcode::AvConvTranscoder> > _transcoders;
|
||||||
|
|
||||||
Database::Handler& _db;
|
Database::Handler& _db;
|
||||||
|
|
||||||
|
uint32_t _curHandle = 0;
|
||||||
|
|
||||||
static const std::size_t _maxPartSize = 65536 - 128;
|
static const std::size_t _maxPartSize = 65536 - 128;
|
||||||
|
static const std::size_t _maxTranscoders = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Remote
|
} // namespace Remote
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ ServiceManager::ServiceManager()
|
|||||||
|
|
||||||
ServiceManager::~ServiceManager()
|
ServiceManager::~ServiceManager()
|
||||||
{
|
{
|
||||||
|
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Stopping services...";
|
||||||
stopServices();
|
stopServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -92,6 +93,7 @@ ServiceManager::stopServices(void)
|
|||||||
void
|
void
|
||||||
ServiceManager::restartServices(void)
|
ServiceManager::restartServices(void)
|
||||||
{
|
{
|
||||||
|
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Restarting services...";
|
||||||
BOOST_FOREACH(Service::pointer service, _services)
|
BOOST_FOREACH(Service::pointer service, _services)
|
||||||
service->restart();
|
service->restart();
|
||||||
}
|
}
|
||||||
@@ -106,19 +108,17 @@ ServiceManager::handleSignal(boost::system::error_code /*ec*/, int signo)
|
|||||||
case SIGINT:
|
case SIGINT:
|
||||||
case SIGTERM:
|
case SIGTERM:
|
||||||
case SIGQUIT:
|
case SIGQUIT:
|
||||||
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Stopping services...";
|
|
||||||
stopServices();
|
stopServices();
|
||||||
|
|
||||||
// Do not listen for signals, this will make the ioservice.run return
|
// Do not listen for signals, this will make the ioservice.run return
|
||||||
break;
|
break;
|
||||||
case SIGHUP:
|
case SIGHUP:
|
||||||
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Restarting services...";
|
|
||||||
|
|
||||||
restartServices();
|
restartServices();
|
||||||
|
|
||||||
asyncWaitSignals();
|
asyncWaitSignals();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(0);
|
LMS_LOG(MOD_SERVICE, SEV_NOTICE) << "Unhandled signal " << signo;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
#include <boost/foreach.hpp>
|
|
||||||
#include <boost/filesystem.hpp>
|
|
||||||
|
|
||||||
#include "database/DatabaseHandler.hpp"
|
|
||||||
|
|
||||||
#include "database/FileTypes.hpp"
|
|
||||||
#include "database/MediaDirectory.hpp"
|
|
||||||
|
|
||||||
int main(void)
|
|
||||||
{
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
boost::filesystem::remove("test2.db");
|
|
||||||
|
|
||||||
// Set up the long living database session
|
|
||||||
Database::Handler database("test2.db");
|
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction(database.getSession());
|
|
||||||
|
|
||||||
std::cout << "Creating objects..." << std::endl;
|
|
||||||
|
|
||||||
assert( Database::Path::getRoots(database.getSession()).empty() );
|
|
||||||
|
|
||||||
Database::Path::pointer parentPath = database.getSession().add(new Database::Path("/PARENT") );
|
|
||||||
|
|
||||||
assert( Database::Path::getRoots(database.getSession()).size() == 1);
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < 5; ++i)
|
|
||||||
{
|
|
||||||
std::ostringstream oss; oss << "/PARENT/toto" << i << ".mp4";
|
|
||||||
Database::Path::pointer childPath = database.getSession().add(new Database::Path( oss.str()) );
|
|
||||||
|
|
||||||
{
|
|
||||||
std::ostringstream oss; oss << "/PARENT/toto" << i << "_dir";
|
|
||||||
Database::Path::pointer childDirPath = database.getSession().add(new Database::Path( oss.str()) );
|
|
||||||
for (std::size_t j = 0; j < 6; ++j) {
|
|
||||||
|
|
||||||
std::ostringstream oss2; oss2 << "/PARENT/toto" << i << "_dir/" << j << ".mp4";
|
|
||||||
Database::Path::pointer childPath2 = database.getSession().add(new Database::Path( oss2.str()) );
|
|
||||||
|
|
||||||
childDirPath.modify()->addChild( childPath2 );
|
|
||||||
}
|
|
||||||
|
|
||||||
childDirPath.modify()->addChild( childDirPath );
|
|
||||||
}
|
|
||||||
|
|
||||||
parentPath.modify()->addChild( childPath );
|
|
||||||
|
|
||||||
assert( childPath->getParent());
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
std::vector<Database::Path::pointer> roots = Database::Path::getRoots(database.getSession());
|
|
||||||
|
|
||||||
std::cout << "There are now " << roots.size() << " roots!" << std::endl;
|
|
||||||
BOOST_FOREACH(Database::Path::pointer root, roots)
|
|
||||||
{
|
|
||||||
std::cout << "ROOT Path = " << root->getPath() << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert( !parentPath->getParent() );
|
|
||||||
|
|
||||||
std::cout << "PRINT objects..." << std::endl;
|
|
||||||
|
|
||||||
std::vector< Database::Path::pointer > pathes = parentPath->getChilds();
|
|
||||||
BOOST_FOREACH(Database::Path::pointer path, pathes)
|
|
||||||
{
|
|
||||||
std::cout << "Found a child: " << path->getPath() << std::endl;
|
|
||||||
std::cout << "Parent's = " << path->getParent()->getPath() << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update last update
|
|
||||||
Database::MediaDirectorySettings::pointer settings = Database::MediaDirectorySettings::get(database.getSession());
|
|
||||||
settings.modify()->setLastUpdate(boost::posix_time::second_clock::local_time());
|
|
||||||
|
|
||||||
std::cout << "last update = " << settings->getLastUpdated() << std::endl;
|
|
||||||
|
|
||||||
// make some empty root dirs
|
|
||||||
|
|
||||||
transaction.commit();
|
|
||||||
|
|
||||||
}
|
|
||||||
catch(std::exception& e)
|
|
||||||
{
|
|
||||||
std::cerr << "Caught exception " << e.what() << std::endl;
|
|
||||||
return EXIT_FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
+5
-22
@@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
TESTS = database-integrity sql-query database-basics database-user remote
|
TESTS = database-integrity sql-query database-user remote
|
||||||
|
|
||||||
check_PROGRAMS = database-integrity sql-query database-basics database-user remote
|
check_PROGRAMS = database-integrity sql-query database-user remote
|
||||||
|
|
||||||
remote_SOURCES = \
|
remote_SOURCES = \
|
||||||
$(srcdir)/RemoteClientServer.cpp \
|
$(srcdir)/RemoteClientServer.cpp \
|
||||||
@@ -17,9 +17,9 @@ remote_SOURCES = \
|
|||||||
$(top_srcdir)/database/Release.cpp \
|
$(top_srcdir)/database/Release.cpp \
|
||||||
$(top_srcdir)/database/Track.cpp \
|
$(top_srcdir)/database/Track.cpp \
|
||||||
$(top_srcdir)/database/DatabaseHandler.cpp \
|
$(top_srcdir)/database/DatabaseHandler.cpp \
|
||||||
|
$(top_srcdir)/database/MediaDirectory.cpp \
|
||||||
$(top_srcdir)/database/SqlQuery.cpp \
|
$(top_srcdir)/database/SqlQuery.cpp \
|
||||||
$(top_srcdir)/database/User.cpp \
|
$(top_srcdir)/database/User.cpp \
|
||||||
$(top_srcdir)/database/Path.cpp \
|
|
||||||
$(top_srcdir)/database/Video.cpp
|
$(top_srcdir)/database/Video.cpp
|
||||||
|
|
||||||
remote_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_srcdir) -I$(top_srcdir)/remote -I$(top_srcdir)/boost
|
remote_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_srcdir) -I$(top_srcdir)/remote -I$(top_srcdir)/boost
|
||||||
@@ -32,8 +32,8 @@ database_user_SOURCES = \
|
|||||||
$(top_srcdir)/database/Release.cpp \
|
$(top_srcdir)/database/Release.cpp \
|
||||||
$(top_srcdir)/database/Track.cpp \
|
$(top_srcdir)/database/Track.cpp \
|
||||||
$(top_srcdir)/database/DatabaseHandler.cpp \
|
$(top_srcdir)/database/DatabaseHandler.cpp \
|
||||||
|
$(top_srcdir)/database/MediaDirectory.cpp \
|
||||||
$(top_srcdir)/database/SqlQuery.cpp \
|
$(top_srcdir)/database/SqlQuery.cpp \
|
||||||
$(top_srcdir)/database/Path.cpp \
|
|
||||||
$(top_srcdir)/database/User.cpp \
|
$(top_srcdir)/database/User.cpp \
|
||||||
$(top_srcdir)/database/Video.cpp
|
$(top_srcdir)/database/Video.cpp
|
||||||
|
|
||||||
@@ -48,30 +48,13 @@ database_integrity_SOURCES = \
|
|||||||
$(top_srcdir)/database/Release.cpp \
|
$(top_srcdir)/database/Release.cpp \
|
||||||
$(top_srcdir)/database/Track.cpp \
|
$(top_srcdir)/database/Track.cpp \
|
||||||
$(top_srcdir)/database/DatabaseHandler.cpp \
|
$(top_srcdir)/database/DatabaseHandler.cpp \
|
||||||
|
$(top_srcdir)/database/MediaDirectory.cpp \
|
||||||
$(top_srcdir)/database/SqlQuery.cpp \
|
$(top_srcdir)/database/SqlQuery.cpp \
|
||||||
$(top_srcdir)/database/Path.cpp \
|
|
||||||
$(top_srcdir)/database/User.cpp \
|
$(top_srcdir)/database/User.cpp \
|
||||||
$(top_srcdir)/database/Video.cpp
|
$(top_srcdir)/database/Video.cpp
|
||||||
|
|
||||||
database_integrity_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_srcdir)
|
database_integrity_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_srcdir)
|
||||||
|
|
||||||
database_basics_SOURCES = \
|
|
||||||
$(srcdir)/CheckDatabaseBasics.cpp \
|
|
||||||
$(top_srcdir)/logger/Logger.cpp \
|
|
||||||
$(top_srcdir)/database/Artist.cpp \
|
|
||||||
$(top_srcdir)/database/Genre.cpp \
|
|
||||||
$(top_srcdir)/database/Release.cpp \
|
|
||||||
$(top_srcdir)/database/Track.cpp \
|
|
||||||
$(top_srcdir)/database/DatabaseHandler.cpp \
|
|
||||||
$(top_srcdir)/database/MediaDirectory.cpp \
|
|
||||||
$(top_srcdir)/database/SqlQuery.cpp \
|
|
||||||
$(top_srcdir)/database/Path.cpp \
|
|
||||||
$(top_srcdir)/database/User.cpp \
|
|
||||||
$(top_srcdir)/database/Video.cpp
|
|
||||||
|
|
||||||
database_basics_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_srcdir)
|
|
||||||
|
|
||||||
|
|
||||||
sql_query_SOURCES = \
|
sql_query_SOURCES = \
|
||||||
$(srcdir)/CheckSqlQuery.cpp \
|
$(srcdir)/CheckSqlQuery.cpp \
|
||||||
$(top_srcdir)/database/SqlQuery.cpp
|
$(top_srcdir)/database/SqlQuery.cpp
|
||||||
|
|||||||
+37
-57
@@ -63,6 +63,8 @@ struct TrackInfo
|
|||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
boost::posix_time::time_duration duration;
|
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)
|
std::ostream& operator<<(std::ostream& os, const TrackInfo& info)
|
||||||
@@ -320,6 +322,12 @@ class TestClient
|
|||||||
track.id = response.audio_collection_response().track_list().tracks(i).id();
|
track.id = response.audio_collection_response().track_list().tracks(i).id();
|
||||||
track.name = response.audio_collection_response().track_list().tracks(i).name();
|
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());
|
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 );
|
tracks.push_back( track );
|
||||||
nbAdded++;
|
nbAdded++;
|
||||||
@@ -333,13 +341,13 @@ class TestClient
|
|||||||
{
|
{
|
||||||
|
|
||||||
// Prepare
|
// Prepare
|
||||||
mediaAudioPrepare( audioId );
|
uint32_t handle = mediaAudioPrepare( audioId );
|
||||||
|
|
||||||
// Get while available
|
// Get while available
|
||||||
mediaGet( data );
|
mediaGet( handle, data );
|
||||||
|
|
||||||
// Terminate
|
// Terminate
|
||||||
mediaTerminate();
|
mediaTerminate( handle );
|
||||||
}
|
}
|
||||||
|
|
||||||
void getCoverTrack(std::vector<Cover>& coverArt, uint64_t trackId)
|
void getCoverTrack(std::vector<Cover>& coverArt, uint64_t trackId)
|
||||||
@@ -487,7 +495,7 @@ class TestClient
|
|||||||
return true; // preverified;
|
return true; // preverified;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mediaAudioPrepare(uint64_t audioId)
|
uint32_t mediaAudioPrepare(uint64_t audioId)
|
||||||
{
|
{
|
||||||
|
|
||||||
// Send request
|
// Send request
|
||||||
@@ -500,6 +508,8 @@ class TestClient
|
|||||||
|
|
||||||
// Set fields
|
// Set fields
|
||||||
request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_track_id( audioId );
|
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;
|
std::cout << "Sending prepare request" << std::endl;
|
||||||
sendMsg(request);
|
sendMsg(request);
|
||||||
@@ -515,22 +525,23 @@ class TestClient
|
|||||||
if (!response.has_media_response())
|
if (!response.has_media_response())
|
||||||
throw std::runtime_error("Preapre: not an media reponse!");
|
throw std::runtime_error("Preapre: not an media reponse!");
|
||||||
|
|
||||||
if (!response.media_response().has_error())
|
if (!response.media_response().has_prepare_result())
|
||||||
throw std::runtime_error("Prepare: not an error msg!");
|
throw std::runtime_error("Prepare: not an prepare result msg!");
|
||||||
|
|
||||||
if (response.media_response().error().error())
|
if (!response.media_response().prepare_result().has_handle())
|
||||||
throw std::runtime_error("Prepare failed: '" + response.media_response().error().message() + "'");
|
throw std::runtime_error("Prepare: cannot get handle");
|
||||||
|
|
||||||
|
return response.media_response().prepare_result().handle();
|
||||||
}
|
}
|
||||||
|
|
||||||
void mediaGet(std::vector<unsigned char>& data)
|
void mediaGet(uint32_t handle, std::vector<unsigned char>& data)
|
||||||
{
|
{
|
||||||
while (mediaGetPart(data) > 0)
|
while (mediaGetPart(handle, data) > 0)
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::size_t mediaGetPart(std::vector<unsigned char>& data)
|
std::size_t mediaGetPart(uint32_t handle, std::vector<unsigned char>& data)
|
||||||
{
|
{
|
||||||
// Send request
|
// Send request
|
||||||
Remote::ClientMessage request;
|
Remote::ClientMessage request;
|
||||||
@@ -538,6 +549,7 @@ class TestClient
|
|||||||
request.set_type( Remote::ClientMessage::MediaRequest);
|
request.set_type( Remote::ClientMessage::MediaRequest);
|
||||||
|
|
||||||
request.mutable_media_request()->set_type( Remote::MediaRequest::TypeMediaGetPart);
|
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);
|
request.mutable_media_request()->mutable_get_part()->set_requested_data_size(65536);
|
||||||
|
|
||||||
std::cout << "Sending GetPart request" << std::endl;
|
std::cout << "Sending GetPart request" << std::endl;
|
||||||
@@ -554,40 +566,18 @@ class TestClient
|
|||||||
throw std::runtime_error("not an media response");
|
throw std::runtime_error("not an media response");
|
||||||
|
|
||||||
// Process message
|
// Process message
|
||||||
switch( response.media_response().type())
|
if (response.media_response().type() != Remote::MediaResponse::TypePartResult)
|
||||||
{
|
throw std::runtime_error("GetPart: not a Part response!");
|
||||||
case Remote::MediaResponse::TypeError:
|
|
||||||
{
|
|
||||||
std::string msg;
|
|
||||||
if (response.media_response().has_error())
|
|
||||||
{
|
|
||||||
if (response.media_response().error().has_message())
|
|
||||||
throw std::runtime_error("Error spotted: '" + response.media_response().error().message()+ "'");
|
|
||||||
else
|
|
||||||
throw std::runtime_error("Error spotted, no msg");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw std::runtime_error("Error spotted but no error message");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case Remote::MediaResponse::TypePart:
|
if (!response.media_response().has_part_result())
|
||||||
if (!response.media_response().has_part())
|
throw std::runtime_error("GetPart: does not have a Part result!");
|
||||||
throw std::runtime_error("not an media part");
|
|
||||||
|
|
||||||
std::copy(response.media_response().part().data().begin(), response.media_response().part().data().end(), std::back_inserter(data));
|
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().data().size();
|
return response.media_response().part_result().data().size();
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw std::runtime_error("Unhandled response.media_response().type()!");
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void mediaTerminate(void)
|
void mediaTerminate( uint32_t handle )
|
||||||
{
|
{
|
||||||
// Send request
|
// Send request
|
||||||
Remote::ClientMessage request;
|
Remote::ClientMessage request;
|
||||||
@@ -595,8 +585,7 @@ class TestClient
|
|||||||
request.set_type( Remote::ClientMessage::MediaRequest);
|
request.set_type( Remote::ClientMessage::MediaRequest);
|
||||||
|
|
||||||
request.mutable_media_request()->set_type( Remote::MediaRequest::TypeMediaTerminate);
|
request.mutable_media_request()->set_type( Remote::MediaRequest::TypeMediaTerminate);
|
||||||
request.mutable_media_request()->mutable_terminate();
|
request.mutable_media_request()->mutable_terminate()->set_handle(handle);
|
||||||
|
|
||||||
|
|
||||||
sendMsg(request);
|
sendMsg(request);
|
||||||
|
|
||||||
@@ -609,11 +598,8 @@ class TestClient
|
|||||||
if (!response.has_media_response())
|
if (!response.has_media_response())
|
||||||
throw std::runtime_error("Terminate: not an media response");
|
throw std::runtime_error("Terminate: not an media response");
|
||||||
|
|
||||||
if (!response.media_response().has_error())
|
if (!response.media_response().has_terminate_result())
|
||||||
throw std::runtime_error("Terminate: not an error msg!");
|
throw std::runtime_error("Terminate: not an terminate msg!");
|
||||||
|
|
||||||
if (response.media_response().error().error())
|
|
||||||
throw std::runtime_error("Terminate failed: '" + response.media_response().error().message() + "'");
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -725,7 +711,7 @@ int main()
|
|||||||
TestClient client( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4::loopback(), 5080));
|
TestClient client( boost::asio::ip::tcp::endpoint( boost::asio::ip::address_v4::loopback(), 5080));
|
||||||
|
|
||||||
// Use a dumb account in order to test the login
|
// Use a dumb account in order to test the login
|
||||||
if (!client.login("toto", "Tot&3131"))
|
if (!client.login("admin", "admin"))
|
||||||
throw std::runtime_error("login failed!");
|
throw std::runtime_error("login failed!");
|
||||||
|
|
||||||
// **** REVISION ***
|
// **** REVISION ***
|
||||||
@@ -809,19 +795,13 @@ int main()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ****** Transcode test ********
|
// ****** Transcode test ********
|
||||||
|
const std::vector<uint64_t> mediaIds = {14000, 14001};
|
||||||
|
BOOST_FOREACH(uint64_t mediaId, mediaIds)
|
||||||
{
|
{
|
||||||
std::vector<unsigned char> data;
|
std::vector<unsigned char> data;
|
||||||
client.getMediaAudio(30000, data);
|
client.getMediaAudio(mediaId, data);
|
||||||
|
|
||||||
std::cout << "Media size = " << data.size() << std::endl;
|
std::cout << "Media size = " << data.size() << std::endl;
|
||||||
}
|
}
|
||||||
/* {
|
|
||||||
std::vector<unsigned char> data;
|
|
||||||
client.getMediaAudio(100, data);
|
|
||||||
|
|
||||||
std::cout << "Media size = " << data.size() << std::endl;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
|
|
||||||
std::cout << "End of tests!" << std::endl;
|
std::cout << "End of tests!" << std::endl;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user