[LMSAPI] Removed unused LMS API
This commit is contained in:
@@ -43,7 +43,9 @@ This command requires root privileges
|
|||||||
```sh
|
```sh
|
||||||
$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images' --approot=/usr/share/lms/approot --http-address 0.0.0.0 --http-port 5081
|
$ /usr/bin/lms --docroot='/usr/share/lms/docroot/;/resources,/css,/images' --approot=/usr/share/lms/approot --http-address 0.0.0.0 --http-port 5081
|
||||||
```
|
```
|
||||||
The exectuable needs write accesses to the /var/lms/ directory
|
It is highly recommended to run LMS as a non root user.
|
||||||
|
The exectuable needs write accesses to the /var/lms/ directory.
|
||||||
|
|
||||||
## Setting up SSL materials (optional)
|
## Setting up SSL materials (optional)
|
||||||
Here is just a self signed certificate example, you could do use a CA if you want.
|
Here is just a self signed certificate example, you could do use a CA if you want.
|
||||||
|
|
||||||
|
|||||||
+2
-33
@@ -8,41 +8,10 @@ AC_LANG_CPLUSPLUS
|
|||||||
# Checks for programs
|
# Checks for programs
|
||||||
AC_PROG_CXX
|
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.
|
|
||||||
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])
|
|
||||||
|
|
||||||
AM_CONDITIONAL([LMSAPI], [test x$lmsapi = xtrue])
|
|
||||||
AS_IF([test x$lmsapi = xtrue], [AC_DEFINE(HAVE_LMSAPI, [1], [Enable LMS API support])])
|
|
||||||
|
|
||||||
AC_ARG_ENABLE([video], AS_HELP_STRING([--enable-video],
|
AC_ARG_ENABLE([video], AS_HELP_STRING([--enable-video],
|
||||||
[Enable Video support. Default to yes.])],
|
[Enable Video support. Default to no.])],
|
||||||
[enable_video="$enableval"],
|
[enable_video="$enableval"],
|
||||||
[enable_video="yes"])
|
[enable_video="no"])
|
||||||
|
|
||||||
AM_CONDITIONAL([VIDEO], [test x$enable_video = xyes])
|
AM_CONDITIONAL([VIDEO], [test x$enable_video = xyes])
|
||||||
AS_IF([test x$enable_video = xyes], [AC_DEFINE(HAVE_VIDEO, [1], [Enable Video support])])
|
AS_IF([test x$enable_video = xyes], [AC_DEFINE(HAVE_VIDEO, [1], [Enable Video support])])
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package LmsAPI;
|
|
||||||
|
|
||||||
message 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package LmsAPI;
|
|
||||||
|
|
||||||
message 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
|
|
||||||
required uint32 size = 2; // Number of elements requested. Server may not honor this size if too big
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetGenreList
|
|
||||||
{
|
|
||||||
required BatchParameter batch_parameter = 1;
|
|
||||||
optional SearchFilter search_filter = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetArtistList
|
|
||||||
{
|
|
||||||
required BatchParameter batch_parameter = 1;
|
|
||||||
optional SearchFilter search_filter = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetReleaseList
|
|
||||||
{
|
|
||||||
required BatchParameter batch_parameter = 1;
|
|
||||||
optional SearchFilter search_filter = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetTrackList
|
|
||||||
{
|
|
||||||
required BatchParameter batch_parameter = 1;
|
|
||||||
optional SearchFilter search_filter = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetCoverArt
|
|
||||||
{
|
|
||||||
enum Type
|
|
||||||
{
|
|
||||||
TypeGetCoverArtRelease = 1;
|
|
||||||
TypeGetCoverArtTrack = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
required Type type = 1;
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Type
|
|
||||||
{
|
|
||||||
TypeGetRevision = 1;
|
|
||||||
TypeGetGenreList = 2;
|
|
||||||
TypeGetArtistList = 3;
|
|
||||||
TypeGetReleaseList = 4;
|
|
||||||
TypeGetTrackList = 5;
|
|
||||||
TypeGetCoverArt = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
required Type type = 1;
|
|
||||||
|
|
||||||
optional GetGenreList get_genres = 3;
|
|
||||||
optional GetArtistList get_artists = 4;
|
|
||||||
optional GetReleaseList get_releases = 5;
|
|
||||||
optional GetTrackList get_tracks = 6;
|
|
||||||
optional GetCoverArt get_cover_art = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
message AudioCollectionResponse
|
|
||||||
{
|
|
||||||
|
|
||||||
message Revision
|
|
||||||
{
|
|
||||||
required string rev = 1; // Unique identifier of the database revision
|
|
||||||
}
|
|
||||||
|
|
||||||
message GenreList
|
|
||||||
{
|
|
||||||
repeated Genre genres = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ArtistList
|
|
||||||
{
|
|
||||||
repeated Artist artists = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ReleaseList
|
|
||||||
{
|
|
||||||
repeated Release releases = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TrackList
|
|
||||||
{
|
|
||||||
repeated Track tracks = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message CoverArt
|
|
||||||
{
|
|
||||||
optional string mime_type = 1;
|
|
||||||
required bytes data = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Genre
|
|
||||||
{
|
|
||||||
required uint64 id = 1;
|
|
||||||
optional string mbid = 2;
|
|
||||||
required string name = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Artist
|
|
||||||
{
|
|
||||||
required uint64 id = 1;
|
|
||||||
optional string mbid = 2;
|
|
||||||
required string name = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Release
|
|
||||||
{
|
|
||||||
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 uint64 artist_id = 3;
|
|
||||||
required uint64 release_id = 4;
|
|
||||||
repeated uint64 genre_id = 5;
|
|
||||||
|
|
||||||
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 {
|
|
||||||
TypeRevision = 1;
|
|
||||||
TypeGenreList = 2;
|
|
||||||
TypeArtistList = 3;
|
|
||||||
TypeReleaseList = 4;
|
|
||||||
TypeTrackList = 5;
|
|
||||||
TypeCoverArt = 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
required Type type = 1;
|
|
||||||
|
|
||||||
optional Revision revision = 2;
|
|
||||||
optional GenreList genre_list = 3;
|
|
||||||
optional ArtistList artist_list = 4;
|
|
||||||
optional ReleaseList release_list = 5;
|
|
||||||
optional TrackList track_list = 6;
|
|
||||||
repeated CoverArt cover_art = 7;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package LmsAPI;
|
|
||||||
|
|
||||||
message MediaRequest
|
|
||||||
{
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
required int64 track_id = 1; // Id if the media
|
|
||||||
required AudioCodecType codec_type = 2;
|
|
||||||
required AudioBitrate bitrate = 3;
|
|
||||||
optional uint32 stream_idx = 4;
|
|
||||||
optional uint32 offset_secs = 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Video
|
|
||||||
{
|
|
||||||
required int64 video_id = 1; // Id if the media
|
|
||||||
required VideoCodecType codec_type = 2;
|
|
||||||
required AudioBitrate audio_bitrate = 3;
|
|
||||||
required VideoBitrate video_bitrate = 4;
|
|
||||||
optional uint32 offset_secs = 5;
|
|
||||||
optional uint32 audio_stream_idx = 6;
|
|
||||||
optional uint32 video_stream_idx = 7;
|
|
||||||
optional uint32 subtitle_stream_idx = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Type {
|
|
||||||
AudioRequest = 1;
|
|
||||||
VideoRequest = 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
required Type type = 1;
|
|
||||||
|
|
||||||
optional Audio audio = 2;
|
|
||||||
optional Video video = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetPart
|
|
||||||
{
|
|
||||||
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
|
|
||||||
{
|
|
||||||
required uint32 handle = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Type
|
|
||||||
{
|
|
||||||
TypeMediaPrepare = 1;
|
|
||||||
TypeMediaGetPart = 2;
|
|
||||||
TypeMediaTerminate = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
required Type type = 1;
|
|
||||||
|
|
||||||
optional Prepare prepare = 2;
|
|
||||||
optional GetPart get_part = 3;
|
|
||||||
optional Terminate terminate = 4;
|
|
||||||
|
|
||||||
} // MediaRequest
|
|
||||||
|
|
||||||
message MediaResponse
|
|
||||||
{
|
|
||||||
|
|
||||||
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
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Type
|
|
||||||
{
|
|
||||||
TypePrepareResult = 1;
|
|
||||||
TypePartResult = 2;
|
|
||||||
TypeTerminateResult = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
required Type type = 1;
|
|
||||||
|
|
||||||
optional PrepareResult prepare_result = 2;
|
|
||||||
optional PartResult part_result = 3;
|
|
||||||
optional TerminateResult terminate_result = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import "auth.proto";
|
|
||||||
import "collection.proto";
|
|
||||||
import "media.proto";
|
|
||||||
|
|
||||||
package LmsAPI;
|
|
||||||
|
|
||||||
// Every request sent by the client must be a ClientMessage
|
|
||||||
// The server will answer using a ServerMessage
|
|
||||||
//
|
|
||||||
// The client must authenticate first before querying data
|
|
||||||
// If an expected message is received, the connection is dropped by the server
|
|
||||||
//
|
|
||||||
// A header is to be added before the serialized protobuf message in order to keep
|
|
||||||
// track of where one message ends and the next begins.
|
|
||||||
// Format:
|
|
||||||
// MAGIC (4 bytes, 0xDE, 0xAD, 0xBE, 0xEF)- SIZE (4 bytes, big endian encoded) - serialized proto message
|
|
||||||
message ClientMessage
|
|
||||||
{
|
|
||||||
enum Type
|
|
||||||
{
|
|
||||||
AuthRequest = 1;
|
|
||||||
AudioCollectionRequest = 2;
|
|
||||||
MediaRequest = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
required Type type = 1;
|
|
||||||
|
|
||||||
optional AuthRequest auth_request = 2;
|
|
||||||
optional AudioCollectionRequest audio_collection_request = 3;
|
|
||||||
optional MediaRequest media_request = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
message ServerMessage
|
|
||||||
{
|
|
||||||
enum Type
|
|
||||||
{
|
|
||||||
AuthResponse = 1;
|
|
||||||
AudioCollectionResponse = 2;
|
|
||||||
MediaResponse = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
required Type type = 1;
|
|
||||||
|
|
||||||
optional AuthResponse auth_response = 2;
|
|
||||||
optional AudioCollectionResponse audio_collection_response = 3;
|
|
||||||
optional MediaResponse media_response = 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
+1
-47
@@ -59,52 +59,6 @@ lms_SOURCES += \
|
|||||||
$(srcdir)/ui/video/VideoParametersDialog.cpp
|
$(srcdir)/ui/video/VideoParametersDialog.cpp
|
||||||
endif
|
endif
|
||||||
|
|
||||||
if LMSAPI
|
lms_CXXFLAGS=-std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui $(MAGICKXX_CFLAGS)
|
||||||
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 \
|
|
||||||
$(builddir)/collection.pb.cc \
|
|
||||||
$(builddir)/collection.pb.h \
|
|
||||||
$(builddir)/media.pb.cc \
|
|
||||||
$(builddir)/media.pb.h \
|
|
||||||
$(builddir)/messages.pb.cc \
|
|
||||||
$(builddir)/messages.pb.h
|
|
||||||
|
|
||||||
BUILT_SOURCES = \
|
|
||||||
$(builddir)/auth.pb.cc \
|
|
||||||
$(builddir)/auth.pb.h \
|
|
||||||
$(builddir)/collection.pb.cc \
|
|
||||||
$(builddir)/collection.pb.h \
|
|
||||||
$(builddir)/media.pb.cc \
|
|
||||||
$(builddir)/media.pb.h \
|
|
||||||
$(builddir)/messages.pb.cc \
|
|
||||||
$(builddir)/messages.pb.h
|
|
||||||
|
|
||||||
MOSTLYCLEANFILES = \
|
|
||||||
$(builddir)/auth.pb.cc \
|
|
||||||
$(builddir)/auth.pb.h \
|
|
||||||
$(builddir)/collection.pb.cc \
|
|
||||||
$(builddir)/collection.pb.h \
|
|
||||||
$(builddir)/media.pb.cc \
|
|
||||||
$(builddir)/media.pb.h \
|
|
||||||
$(builddir)/messages.pb.cc \
|
|
||||||
$(builddir)/messages.pb.h
|
|
||||||
|
|
||||||
%.pb.cc %.pb.h: $(top_srcdir)/lms-api/proto/%.proto
|
|
||||||
$(PROTOC) --proto_path=$(top_srcdir)/lms-api/proto/ --cpp_out=$(builddir)/ $^
|
|
||||||
|
|
||||||
endif
|
|
||||||
|
|
||||||
lms_CXXFLAGS=-std=c++11 -Wall -I$(top_srcdir)/third-party -I$(srcdir)/ui -I$(srcdir)/lms-api $(MAGICKXX_CFLAGS)
|
|
||||||
|
|
||||||
lms_LDADD=$(MAGICKXX_LIBS)
|
lms_LDADD=$(MAGICKXX_LIBS)
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <sstream>
|
|
||||||
|
|
||||||
#include "ConfigReader.hpp"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigReader::ConfigReader()
|
|
||||||
: _config (nullptr)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigReader&
|
|
||||||
ConfigReader::instance()
|
|
||||||
{
|
|
||||||
static ConfigReader instance;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ConfigReader::setFile(boost::filesystem::path p)
|
|
||||||
{
|
|
||||||
if (_config != nullptr)
|
|
||||||
delete _config;
|
|
||||||
|
|
||||||
_config = new libconfig::Config();
|
|
||||||
|
|
||||||
_config->readFile(p.string().c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string
|
|
||||||
ConfigReader::getString(std::string setting, std::string def)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return _config->lookup(setting);
|
|
||||||
}
|
|
||||||
catch (std::exception &e)
|
|
||||||
{
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsigned long
|
|
||||||
ConfigReader::getULong(std::string setting, unsigned long def)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return static_cast<unsigned int>(_config->lookup(setting));
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
long
|
|
||||||
ConfigReader::getLong(std::string setting, long def)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return _config->lookup(setting);
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
ConfigReader::getBool(std::string setting, bool def)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return _config->lookup(setting);
|
|
||||||
}
|
|
||||||
catch (...)
|
|
||||||
{
|
|
||||||
return def;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef CONFIG_READER_HPP
|
|
||||||
#define CONFIG_READER_HPP
|
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
|
||||||
#include <libconfig.h++>
|
|
||||||
|
|
||||||
class ConfigReader
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
ConfigReader(const ConfigReader&) = delete;
|
|
||||||
ConfigReader& operator=(const ConfigReader&) = delete;
|
|
||||||
|
|
||||||
static ConfigReader& instance();
|
|
||||||
|
|
||||||
void setFile(boost::filesystem::path p);
|
|
||||||
|
|
||||||
/* Default values are returned in case of setting not found */
|
|
||||||
std::string getString(std::string setting, std::string def = "");
|
|
||||||
unsigned long getULong(std::string setting, unsigned long def = 0);
|
|
||||||
long getLong(std::string setting, long def = 0);
|
|
||||||
bool getBool(std::string setting, bool def = false);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
ConfigReader();
|
|
||||||
|
|
||||||
libconfig::Config *_config;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -18,7 +18,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "logger/Logger.hpp"
|
#include "logger/Logger.hpp"
|
||||||
#include "config/ConfigReader.hpp"
|
|
||||||
#include "av/AvInfo.hpp"
|
#include "av/AvInfo.hpp"
|
||||||
|
|
||||||
#include "CoverArtGrabber.hpp"
|
#include "CoverArtGrabber.hpp"
|
||||||
|
|||||||
@@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef REMOTE_HEADER_HPP
|
|
||||||
#define REMOTE_HEADER_HPP
|
|
||||||
|
|
||||||
#include <iomanip>
|
|
||||||
|
|
||||||
namespace LmsAPI
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
class Header
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
static const std::size_t size = 8; // HeaderSize
|
|
||||||
static const std::size_t max_data_size = 65536*64 - size;
|
|
||||||
|
|
||||||
Header() : _dataSize(0) {}
|
|
||||||
|
|
||||||
void setDataSize(std::size_t size) { _dataSize = size; }
|
|
||||||
std::size_t getDataSize(void) const {return _dataSize;}
|
|
||||||
|
|
||||||
bool from_istream(std::istream &is)
|
|
||||||
{
|
|
||||||
std::array<unsigned char, size> buffer;
|
|
||||||
|
|
||||||
bool res = is.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
|
|
||||||
|
|
||||||
if (res && is.gcount() == buffer.size())
|
|
||||||
return from_buffer(buffer);
|
|
||||||
else
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool from_buffer(const std::array<unsigned char, size>& buffer)
|
|
||||||
{
|
|
||||||
if (decode32(&buffer[0]) != _magic)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_dataSize = decode32(&buffer[4]);
|
|
||||||
return _dataSize <= max_data_size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* void to_ostream(std::ostream& os)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
}*/
|
|
||||||
|
|
||||||
void to_buffer(std::array<unsigned char, size>& buffer) const
|
|
||||||
{
|
|
||||||
encode32(_magic, &buffer[0]);
|
|
||||||
encode32(_dataSize, &buffer[4]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
static uint32_t decode32(const unsigned char* data)
|
|
||||||
{
|
|
||||||
return (static_cast<uint32_t>(data[0]) << 24)
|
|
||||||
+ (static_cast<uint32_t>(data[1]) << 16)
|
|
||||||
+ (static_cast<uint32_t>(data[2]) << 8)
|
|
||||||
+ (static_cast<uint32_t>(data[3]));
|
|
||||||
}
|
|
||||||
|
|
||||||
static void encode32(uint32_t value, unsigned char* data)
|
|
||||||
{
|
|
||||||
data[0] = (value >> 24) & 0xFF;
|
|
||||||
data[1] = (value >> 16) & 0xFF;
|
|
||||||
data[2] = (value >> 8) & 0xFF;
|
|
||||||
data[3] = (value) & 0xFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
static const uint32_t _magic = 0xdeadbeef;
|
|
||||||
|
|
||||||
uint32_t _dataSize;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace LmsAPI
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@@ -1,393 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <algorithm> // std::min
|
|
||||||
|
|
||||||
#include <boost/locale.hpp>
|
|
||||||
#include <boost/uuid/sha1.hpp>
|
|
||||||
|
|
||||||
#include "logger/Logger.hpp"
|
|
||||||
|
|
||||||
#include "AudioCollectionRequestHandler.hpp"
|
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
|
||||||
#include "cover/CoverArtGrabber.hpp"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
using namespace Database;
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
|
|
||||||
bool res = false;
|
|
||||||
|
|
||||||
switch (request.type())
|
|
||||||
{
|
|
||||||
case AudioCollectionRequest::TypeGetRevision:
|
|
||||||
// No payload
|
|
||||||
res = processGetRevision(*response.mutable_revision());
|
|
||||||
if (res)
|
|
||||||
response.set_type(AudioCollectionResponse::TypeRevision);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AudioCollectionRequest::TypeGetGenreList:
|
|
||||||
if (request.has_get_genres())
|
|
||||||
{
|
|
||||||
res = processGetGenres(request.get_genres(), *response.mutable_genre_list());
|
|
||||||
if (res)
|
|
||||||
response.set_type(AudioCollectionResponse::TypeArtistList);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetGenreList";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AudioCollectionRequest::TypeGetArtistList:
|
|
||||||
if (request.has_get_artists())
|
|
||||||
{
|
|
||||||
res = processGetArtists(request.get_artists(), *response.mutable_artist_list());
|
|
||||||
if (res)
|
|
||||||
response.set_type(AudioCollectionResponse::TypeArtistList);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetArtistList message!";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AudioCollectionRequest::TypeGetReleaseList:
|
|
||||||
if (request.has_get_releases())
|
|
||||||
{
|
|
||||||
res = processGetReleases(request.get_releases(), *response.mutable_release_list());
|
|
||||||
if (res)
|
|
||||||
response.set_type(AudioCollectionResponse::TypeReleaseList);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetReleaseList message!";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AudioCollectionRequest::TypeGetTrackList:
|
|
||||||
if (request.has_get_tracks())
|
|
||||||
{
|
|
||||||
res = processGetTracks(request.get_tracks(), *response.mutable_track_list());
|
|
||||||
if (res)
|
|
||||||
response.set_type(AudioCollectionResponse::TypeTrackList);
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetTrackList message!";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AudioCollectionRequest::TypeGetCoverArt:
|
|
||||||
if (request.has_get_cover_art())
|
|
||||||
res = processGetCoverArt(request.get_cover_art(), response);
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AudioCollectionRequest::TypeGetCoverArt message!";
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled AudioCollectionRequest_Type = " << request.type();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
AudioCollectionRequestHandler::processGetGenres(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response)
|
|
||||||
{
|
|
||||||
// sanity checks
|
|
||||||
if (!request.has_batch_parameter())
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t size = request.batch_parameter().size();
|
|
||||||
if (!size)
|
|
||||||
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() );
|
|
||||||
|
|
||||||
std::vector<Genre::pointer> genres = Genre::getByFilter( _db.getSession(), filter, request.batch_parameter().offset(), static_cast<int>(size));
|
|
||||||
|
|
||||||
for (Genre::pointer genre : genres)
|
|
||||||
{
|
|
||||||
AudioCollectionResponse_Genre* addGenre = response.add_genres();
|
|
||||||
|
|
||||||
addGenre->set_id(genre.id());
|
|
||||||
addGenre->set_name( std::string( boost::locale::conv::to_utf<char>(genre->getName(), "UTF-8") ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
AudioCollectionRequestHandler::processGetArtists(const AudioCollectionRequest::GetArtistList& request, AudioCollectionResponse::ArtistList& response)
|
|
||||||
{
|
|
||||||
// sanity checks
|
|
||||||
if (!request.has_batch_parameter())
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t size = request.batch_parameter().size();
|
|
||||||
if (!size)
|
|
||||||
size = _maxListArtists;
|
|
||||||
size = std::min(size, _maxListArtists);
|
|
||||||
|
|
||||||
// Get filters
|
|
||||||
SearchFilter filter;
|
|
||||||
if (request.has_search_filter())
|
|
||||||
filter = SearchFilterFromRequest(request.search_filter());
|
|
||||||
|
|
||||||
// Now fetch requested data...
|
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
|
||||||
|
|
||||||
std::vector<Artist::pointer> artists
|
|
||||||
= Artist::getByFilter(_db.getSession(), filter,
|
|
||||||
request.batch_parameter().offset(), static_cast<int>(size) );
|
|
||||||
|
|
||||||
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<char>(artist->getName(), "UTF-8") ) );
|
|
||||||
if (!artist->getMBID().empty())
|
|
||||||
addArtist->set_mbid(artist->getMBID());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
AudioCollectionRequestHandler::processGetReleases(const AudioCollectionRequest::GetReleaseList& request, AudioCollectionResponse::ReleaseList& response)
|
|
||||||
{
|
|
||||||
// sanity checks
|
|
||||||
if (!request.has_batch_parameter())
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t size = request.batch_parameter().size();
|
|
||||||
if (!size)
|
|
||||||
size = _maxListReleases;
|
|
||||||
size = std::min(size, _maxListReleases);
|
|
||||||
|
|
||||||
// Get filters
|
|
||||||
SearchFilter filter;
|
|
||||||
if (request.has_search_filter())
|
|
||||||
filter = SearchFilterFromRequest(request.search_filter());
|
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
|
||||||
|
|
||||||
std::vector<Release::pointer> releases
|
|
||||||
= Release::getByFilter( _db.getSession(), filter,
|
|
||||||
request.batch_parameter().offset(), static_cast<int>(size));
|
|
||||||
|
|
||||||
for (Release::pointer release : releases)
|
|
||||||
{
|
|
||||||
AudioCollectionResponse_Release *addRelease = response.add_releases();
|
|
||||||
|
|
||||||
addRelease->set_id(release.id());
|
|
||||||
addRelease->set_name( std::string( boost::locale::conv::to_utf<char>(release->getName(), "UTF-8")));
|
|
||||||
if (!release->getMBID().empty())
|
|
||||||
addRelease->set_mbid(release->getMBID());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
AudioCollectionRequestHandler::processGetTracks(const AudioCollectionRequest::GetTrackList& request, AudioCollectionResponse::TrackList& response)
|
|
||||||
{
|
|
||||||
// sanity checks
|
|
||||||
if (!request.has_batch_parameter())
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No batch parameters found!";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t size = request.batch_parameter().size();
|
|
||||||
if (!size)
|
|
||||||
size = _maxListTracks;
|
|
||||||
size = std::min(size, _maxListTracks);
|
|
||||||
|
|
||||||
// Get filters
|
|
||||||
SearchFilter filter;
|
|
||||||
if (request.has_search_filter())
|
|
||||||
filter = SearchFilterFromRequest(request.search_filter());
|
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction( _db.getSession() );
|
|
||||||
|
|
||||||
std::vector<Track::pointer> tracks
|
|
||||||
= Track::getByFilter( _db.getSession(), filter,
|
|
||||||
request.batch_parameter().offset(), static_cast<int>(size));
|
|
||||||
|
|
||||||
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_id( track->getArtist().id() );
|
|
||||||
newTrack->set_release_id( track->getRelease().id() );
|
|
||||||
|
|
||||||
newTrack->set_name( std::string( boost::locale::conv::to_utf<char>(track->getName(), "UTF-8") ) );
|
|
||||||
newTrack->set_duration_secs( track->getDuration().total_seconds() );
|
|
||||||
|
|
||||||
// Only send the year part of the release times
|
|
||||||
if (!track->getDate().is_special())
|
|
||||||
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());
|
|
||||||
|
|
||||||
for (Genre::pointer genre : track->getGenres())
|
|
||||||
newTrack->add_genre_id( genre.id() );
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
AudioCollectionRequestHandler::processGetCoverArt(const AudioCollectionRequest::GetCoverArt& request, AudioCollectionResponse& response)
|
|
||||||
{
|
|
||||||
bool res = false;
|
|
||||||
|
|
||||||
response.set_type(AudioCollectionResponse::TypeCoverArt);
|
|
||||||
|
|
||||||
std::vector<CoverArt::CoverArt> coverArts;
|
|
||||||
|
|
||||||
switch(request.type())
|
|
||||||
{
|
|
||||||
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease:
|
|
||||||
if (request.has_release_id())
|
|
||||||
{
|
|
||||||
coverArts = CoverArt::Grabber::instance().getFromRelease( _db.getSession(), request.release_id());
|
|
||||||
res = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case AudioCollectionRequest::GetCoverArt::TypeGetCoverArtTrack:
|
|
||||||
if (request.has_track_id())
|
|
||||||
{
|
|
||||||
coverArts = CoverArt::Grabber::instance().getFromTrack(_db.getSession(), request.track_id());
|
|
||||||
res = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (CoverArt::CoverArt& coverArt : coverArts)
|
|
||||||
{
|
|
||||||
AudioCollectionResponse_CoverArt* cover_art = response.add_cover_art();
|
|
||||||
|
|
||||||
if (request.has_size())
|
|
||||||
{
|
|
||||||
std::size_t size = request.size();
|
|
||||||
if (size > _maxCoverArtSize || size == 0)
|
|
||||||
size = _maxCoverArtSize;
|
|
||||||
if (size < _minCoverArtSize)
|
|
||||||
size = _minCoverArtSize;
|
|
||||||
|
|
||||||
coverArt.scale(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
cover_art->set_mime_type(coverArt.getMimeType());
|
|
||||||
cover_art->set_data( std::string( coverArt.getData().begin(), coverArt.getData().end()) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
AudioCollectionRequestHandler::processGetRevision(AudioCollectionResponse::Revision& response)
|
|
||||||
{
|
|
||||||
|
|
||||||
bool res = false;
|
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction( _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;
|
|
||||||
for (const char c : hashStr)
|
|
||||||
s.process_byte(c);
|
|
||||||
|
|
||||||
unsigned int digest[5];
|
|
||||||
s.get_digest(digest);
|
|
||||||
|
|
||||||
std::ostringstream oss;
|
|
||||||
for (std::size_t i = 0; i < 5; ++i)
|
|
||||||
oss << std::hex << std::setfill('0') << std::setw(4) << digest[i];
|
|
||||||
|
|
||||||
response.set_rev(oss.str());
|
|
||||||
|
|
||||||
res = true;
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace LmsAPI
|
|
||||||
} // namespace Server
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef REMOTE_AUDIO_COLLECTION_REQUEST_HANDLER
|
|
||||||
#define REMOTE_AUDIO_COLLECTION_REQUEST_HANDLER
|
|
||||||
|
|
||||||
#include "database/DatabaseHandler.hpp"
|
|
||||||
|
|
||||||
#include "messages.pb.h"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
class AudioCollectionRequestHandler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AudioCollectionRequestHandler(Database::Handler& db);
|
|
||||||
|
|
||||||
bool process(const AudioCollectionRequest& request, AudioCollectionResponse& response);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
bool processGetRevision(AudioCollectionResponse::Revision& response);
|
|
||||||
bool processGetArtists(const AudioCollectionRequest::GetArtistList& request, AudioCollectionResponse::ArtistList& response);
|
|
||||||
bool processGetGenres(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response);
|
|
||||||
bool processGetReleases(const AudioCollectionRequest::GetReleaseList& request, AudioCollectionResponse::ReleaseList& response);
|
|
||||||
bool processGetTracks(const AudioCollectionRequest::GetTrackList& request, AudioCollectionResponse::TrackList& response);
|
|
||||||
bool processGetCoverArt(const AudioCollectionRequest::GetCoverArt& request, AudioCollectionResponse& response);
|
|
||||||
|
|
||||||
Database::Handler& _db;
|
|
||||||
|
|
||||||
static const std::size_t _maxListArtists = 256;
|
|
||||||
static const std::size_t _maxListGenres = 256;
|
|
||||||
static const std::size_t _maxListReleases = 128;
|
|
||||||
static const std::size_t _maxListTracks = 128;
|
|
||||||
|
|
||||||
static const std::size_t _minCoverArtSize = 64; // in pixels, square
|
|
||||||
static const std::size_t _maxCoverArtSize = 512; // in pixels, square
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace LmsAPI
|
|
||||||
} // namespace Server
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <Wt/Auth/Identity>
|
|
||||||
|
|
||||||
#include "logger/Logger.hpp"
|
|
||||||
|
|
||||||
#include "AuthRequestHandler.hpp"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
AuthRequestHandler::AuthRequestHandler(Database::Handler& db)
|
|
||||||
: _db(db)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
AuthRequestHandler::process(const AuthRequest& request, AuthResponse& response)
|
|
||||||
{
|
|
||||||
bool res = false;
|
|
||||||
|
|
||||||
switch (request.type())
|
|
||||||
{
|
|
||||||
case AuthRequest::TypePassword:
|
|
||||||
if (request.has_password())
|
|
||||||
{
|
|
||||||
res = processPassword(request.password(), *response.mutable_password_result());
|
|
||||||
if (res)
|
|
||||||
response.set_type(AuthResponse::TypePasswordResult);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad AuthRequest::TypePassword";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
AuthRequestHandler::processPassword(const AuthRequest::Password& request, AuthResponse::PasswordResult& response)
|
|
||||||
{
|
|
||||||
bool res = false;
|
|
||||||
|
|
||||||
// Get the user
|
|
||||||
const Wt::Auth::User& user = _db.getUserDatabase().findWithIdentity(Wt::Auth::Identity::LoginName, request.user_login());
|
|
||||||
if (user.isValid())
|
|
||||||
{
|
|
||||||
// Now attempt to log the user in
|
|
||||||
|
|
||||||
Wt::Auth::PasswordResult result = _db.getPasswordService().verifyPassword(user, request.user_password());
|
|
||||||
switch( result )
|
|
||||||
{
|
|
||||||
case Wt::Auth::PasswordInvalid:
|
|
||||||
response.set_type(AuthResponse::PasswordResult::TypePasswordInvalid);
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_NOTICE) << "User '" << request.user_login() << "': invalid password";
|
|
||||||
res = true;
|
|
||||||
break;
|
|
||||||
case Wt::Auth::LoginThrottling:
|
|
||||||
response.set_type(AuthResponse::PasswordResult::TypeLoginThrottling);
|
|
||||||
response.set_delay(_db.getPasswordService().delayForNextAttempt(user));
|
|
||||||
res = true;
|
|
||||||
break;
|
|
||||||
case Wt::Auth::PasswordValid:
|
|
||||||
response.set_type(AuthResponse::PasswordResult::TypePasswordValid);
|
|
||||||
// Log the user in
|
|
||||||
_db.getLogin().login( user );
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_NOTICE) << "User '" << request.user_login() << "' successfully logged in";
|
|
||||||
res = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot handle password result!";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Invalid user '" << request.user_login();
|
|
||||||
response.set_type(AuthResponse::PasswordResult::TypePasswordInvalid);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Server
|
|
||||||
} // namespace LmsAPI
|
|
||||||
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef LMSAPI_AUTH_REQUEST_HANDLER
|
|
||||||
#define LMSAPI_AUTH_REQUEST_HANDLER
|
|
||||||
|
|
||||||
#include "database/DatabaseHandler.hpp"
|
|
||||||
|
|
||||||
#include "messages.pb.h"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
class AuthRequestHandler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
AuthRequestHandler(Database::Handler& db);
|
|
||||||
|
|
||||||
bool process(const AuthRequest& request, AuthResponse& response);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
bool processPassword(const AuthRequest::Password& request, AuthResponse::PasswordResult& response);
|
|
||||||
|
|
||||||
Database::Handler& _db;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Server
|
|
||||||
} // namespace LmsAPI
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <boost/bind.hpp>
|
|
||||||
#include <boost/foreach.hpp>
|
|
||||||
|
|
||||||
#include "logger/Logger.hpp"
|
|
||||||
|
|
||||||
#include "messages.pb.h"
|
|
||||||
|
|
||||||
#include "RequestHandler.hpp"
|
|
||||||
|
|
||||||
#include "ConnectionManager.hpp"
|
|
||||||
#include "Connection.hpp"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
Connection::Connection(boost::asio::io_service& ioService,
|
|
||||||
boost::asio::ssl::context& context,
|
|
||||||
ConnectionManager& manager,
|
|
||||||
Wt::Dbo::SqlConnectionPool& connectionPool)
|
|
||||||
: _closing(false),
|
|
||||||
_socket(ioService, context),
|
|
||||||
_connectionManager(manager),
|
|
||||||
_requestHandler(connectionPool)
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::Connection, Creating connection";
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Connection::start()
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Starting connection...";
|
|
||||||
_socket.async_handshake(boost::asio::ssl::stream_base::server,
|
|
||||||
boost::bind(&Connection::handleHandshake, this,
|
|
||||||
boost::asio::placeholders::error));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Connection::handleHandshake(const boost::system::error_code& error)
|
|
||||||
{
|
|
||||||
if (!error)
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Handshake successfully performed... Now reading messages";
|
|
||||||
readMsg();
|
|
||||||
}
|
|
||||||
else if (error != boost::asio::error::operation_aborted)
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleHandshake: " << error.message();
|
|
||||||
_connectionManager.stop(shared_from_this());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Handshake error: " << error.message();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
Connection::readMsg()
|
|
||||||
{
|
|
||||||
// Read a header first
|
|
||||||
boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(LmsAPI::Header::size);
|
|
||||||
|
|
||||||
boost::asio::async_read(_socket,
|
|
||||||
bufs,
|
|
||||||
boost::asio::transfer_exactly(LmsAPI::Header::size),
|
|
||||||
boost::bind(&Connection::handleReadHeader, shared_from_this(),
|
|
||||||
boost::asio::placeholders::error,
|
|
||||||
boost::asio::placeholders::bytes_transferred));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Connection::stop()
|
|
||||||
{
|
|
||||||
if (!_closing)
|
|
||||||
{
|
|
||||||
boost::system::error_code ec;
|
|
||||||
|
|
||||||
_closing = true;
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::stop, Stopping connection " << this;
|
|
||||||
_socket.shutdown(ec);
|
|
||||||
|
|
||||||
if (ec)
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Error while shutting down connection " << this << ": " << ec.message();
|
|
||||||
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Server::Connection::stop, connection stopped " << this;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "Stop: close already in progress...";
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Connection::handleReadHeader(const boost::system::error_code& error, std::size_t bytes_transferred)
|
|
||||||
{
|
|
||||||
if (!error)
|
|
||||||
{
|
|
||||||
if (bytes_transferred != LmsAPI::Header::size)
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "bytes_transferred (" << bytes_transferred << ") != LmsAPI::Header::size!";
|
|
||||||
_connectionManager.stop(shared_from_this());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_inputStreamBuf.commit(bytes_transferred);
|
|
||||||
|
|
||||||
std::istream is(&_inputStreamBuf);
|
|
||||||
|
|
||||||
LmsAPI::Header header;
|
|
||||||
if (!header.from_istream(is))
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot read header from buffer!";
|
|
||||||
_connectionManager.stop(shared_from_this());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now read the real message payload
|
|
||||||
boost::asio::streambuf::mutable_buffers_type bufs = _inputStreamBuf.prepare(header.getDataSize());
|
|
||||||
|
|
||||||
boost::asio::async_read(_socket,
|
|
||||||
bufs,
|
|
||||||
boost::asio::transfer_exactly(header.getDataSize()),
|
|
||||||
boost::bind(&Connection::handleReadMsg, shared_from_this(),
|
|
||||||
boost::asio::placeholders::error,
|
|
||||||
boost::asio::placeholders::bytes_transferred));
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (error != boost::asio::error::operation_aborted)
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleReadHeader: " << error.message();
|
|
||||||
_connectionManager.stop(shared_from_this());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Connection::handleReadMsg(const boost::system::error_code& error, std::size_t bytes_transferred)
|
|
||||||
{
|
|
||||||
if (!error)
|
|
||||||
{
|
|
||||||
_inputStreamBuf.commit(bytes_transferred);
|
|
||||||
|
|
||||||
std::istream is(&_inputStreamBuf);
|
|
||||||
std::ostream os(&_outputStreamBuf);
|
|
||||||
|
|
||||||
LmsAPI::ServerMessage response;
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
if (!request.ParseFromIstream(&is))
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Parse request failed!";
|
|
||||||
_connectionManager.stop(shared_from_this());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_requestHandler.process(request, response))
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Process request failed!";
|
|
||||||
_connectionManager.stop(shared_from_this());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
boost::system::error_code ec;
|
|
||||||
|
|
||||||
if (!response.SerializeToOstream(&os))
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Cannot serialize to ostream!";
|
|
||||||
_connectionManager.stop(shared_from_this());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_outputStreamBuf.size() >= LmsAPI::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<unsigned char, LmsAPI::Header::size> headerBuffer;
|
|
||||||
{
|
|
||||||
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(LmsAPI::Header::size),
|
|
||||||
ec);
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "cannot write header: " << error.message();
|
|
||||||
_connectionManager.stop(shared_from_this());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
assert(n == LmsAPI::Header::size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now send serialized payload
|
|
||||||
n = boost::asio::write(_socket,
|
|
||||||
_outputStreamBuf.data(),
|
|
||||||
boost::asio::transfer_exactly(_outputStreamBuf.size()),
|
|
||||||
ec);
|
|
||||||
|
|
||||||
if (ec)
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "cannot write msg: " << error.message();
|
|
||||||
_connectionManager.stop(shared_from_this());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
assert(n == _outputStreamBuf.size());
|
|
||||||
_outputStreamBuf.consume(n);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// All good here, read another message
|
|
||||||
readMsg();
|
|
||||||
}
|
|
||||||
else if (error != boost::asio::error::operation_aborted)
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Connection::handleRead: " << error.message();
|
|
||||||
_connectionManager.stop(shared_from_this());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace Server
|
|
||||||
} // namespace LmsAPI
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef LMSAPI_CONNECTION_HPP
|
|
||||||
#define LMSAPI_CONNECTION_HPP
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include <boost/asio/ssl.hpp>
|
|
||||||
|
|
||||||
#include "RequestHandler.hpp"
|
|
||||||
|
|
||||||
#include "messages/Header.hpp"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
class ConnectionManager;
|
|
||||||
|
|
||||||
/// Represents a single connection from a client.
|
|
||||||
class Connection : public std::enable_shared_from_this<Connection>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
|
|
||||||
|
|
||||||
Connection(const Connection&) = delete;
|
|
||||||
Connection& operator=(const Connection&) = delete;
|
|
||||||
|
|
||||||
typedef std::shared_ptr<Connection> pointer;
|
|
||||||
|
|
||||||
/// Construct a connection with the given io_service.
|
|
||||||
explicit Connection(boost::asio::io_service& ioService, boost::asio::ssl::context& context,
|
|
||||||
ConnectionManager& manager,
|
|
||||||
Wt::Dbo::SqlConnectionPool& connectionPool);
|
|
||||||
|
|
||||||
ssl_socket::lowest_layer_type& getSocket() {return _socket.lowest_layer();}
|
|
||||||
|
|
||||||
/// Start the first asynchronous operation for the connection.
|
|
||||||
void start();
|
|
||||||
|
|
||||||
/// Stop all asynchronous operations associated with the connection.
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool _closing;
|
|
||||||
|
|
||||||
/// Read a new message on the the connection
|
|
||||||
void readMsg();
|
|
||||||
|
|
||||||
/// Handle completion of ssl handshake
|
|
||||||
void handleHandshake(const boost::system::error_code& error);
|
|
||||||
|
|
||||||
/// Handle completion of a read operation.
|
|
||||||
void handleReadHeader(const boost::system::error_code& e,
|
|
||||||
std::size_t bytes_transferred);
|
|
||||||
|
|
||||||
void handleReadMsg(const boost::system::error_code& e,
|
|
||||||
std::size_t bytes_transferred);
|
|
||||||
|
|
||||||
/// Socket for the connection.
|
|
||||||
ssl_socket _socket;
|
|
||||||
|
|
||||||
/// The manager for this connection.
|
|
||||||
ConnectionManager& _connectionManager;
|
|
||||||
|
|
||||||
/// The handler used to process the incoming requests.
|
|
||||||
RequestHandler _requestHandler;
|
|
||||||
|
|
||||||
boost::asio::streambuf _inputStreamBuf;
|
|
||||||
boost::asio::streambuf _outputStreamBuf;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace Server
|
|
||||||
} // namespace LmsAPI
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
#include <boost/foreach.hpp>
|
|
||||||
|
|
||||||
#include "ConnectionManager.hpp"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
ConnectionManager::ConnectionManager()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
ConnectionManager::start(Connection::pointer c)
|
|
||||||
{
|
|
||||||
_connections.insert(c);
|
|
||||||
c->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ConnectionManager::stop(Connection::pointer c)
|
|
||||||
{
|
|
||||||
_connections.erase(c);
|
|
||||||
c->stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
ConnectionManager::stopAll()
|
|
||||||
{
|
|
||||||
BOOST_FOREACH(Connection::pointer c, _connections)
|
|
||||||
{
|
|
||||||
c->stop();
|
|
||||||
}
|
|
||||||
_connections.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace Server
|
|
||||||
} // namespace LmsAPI
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef REMOTE_CONNECTION_MANAGER_HPP
|
|
||||||
#define REMOTE_CONNECTION_MANAGER_HPP
|
|
||||||
|
|
||||||
#include <set>
|
|
||||||
|
|
||||||
#include "Connection.hpp"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
/// Manages open connections so that they may be cleanly stopped when the server
|
|
||||||
/// needs to shut down.
|
|
||||||
|
|
||||||
class ConnectionManager
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
ConnectionManager(const ConnectionManager&) = delete;
|
|
||||||
ConnectionManager& operator=(const ConnectionManager&) = delete;
|
|
||||||
|
|
||||||
ConnectionManager();
|
|
||||||
|
|
||||||
/// Add the specified connection to the manager and start it.
|
|
||||||
void start(Connection::pointer c);
|
|
||||||
|
|
||||||
/// Stop the specified connection.
|
|
||||||
void stop(Connection::pointer c);
|
|
||||||
|
|
||||||
/// Stop all connections.
|
|
||||||
void stopAll();
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// The managed connections.
|
|
||||||
std::set<Connection::pointer> _connections;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Server
|
|
||||||
} // namespace LmsAPI
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -1,217 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "logger/Logger.hpp"
|
|
||||||
|
|
||||||
#include "MediaRequestHandler.hpp"
|
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
MediaRequestHandler::MediaRequestHandler(Database::Handler& db)
|
|
||||||
: _db(db)
|
|
||||||
{}
|
|
||||||
|
|
||||||
bool
|
|
||||||
MediaRequestHandler::process(const MediaRequest& request, MediaResponse& response)
|
|
||||||
{
|
|
||||||
|
|
||||||
bool res = false;
|
|
||||||
|
|
||||||
switch (request.type())
|
|
||||||
{
|
|
||||||
case MediaRequest::TypeMediaPrepare:
|
|
||||||
if (request.has_prepare())
|
|
||||||
{
|
|
||||||
if (request.prepare().has_audio())
|
|
||||||
res = processAudioPrepare(request.prepare().audio(), *response.mutable_prepare_result());
|
|
||||||
else if (request.prepare().has_video())
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Video prepare not supported!";
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
|
|
||||||
|
|
||||||
if (res)
|
|
||||||
response.set_type(MediaResponse::TypePrepareResult);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaPrepare!";
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MediaRequest::TypeMediaGetPart:
|
|
||||||
if (request.has_get_part())
|
|
||||||
{
|
|
||||||
res = processGetPart(request.get_part(), *response.mutable_part_result());
|
|
||||||
if (res)
|
|
||||||
response.set_type(MediaResponse::TypePartResult);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaGet!";
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case MediaRequest::TypeMediaTerminate:
|
|
||||||
if (request.has_terminate())
|
|
||||||
{
|
|
||||||
res = processTerminate(request.terminate(), *response.mutable_terminate_result());
|
|
||||||
if (res)
|
|
||||||
response.set_type(MediaResponse::TypePartResult);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad MediaRequest::TypeMediaTerminate!";
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled MediaRequest type = " << request.type();
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
MediaRequestHandler::processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse::PrepareResult& response)
|
|
||||||
{
|
|
||||||
std::size_t bitrate;
|
|
||||||
Transcode::Format::Encoding format;
|
|
||||||
|
|
||||||
switch( request.codec_type())
|
|
||||||
{
|
|
||||||
case MediaRequest::Prepare::AudioCodecTypeOGA: format = Transcode::Format::OGA; break;
|
|
||||||
default:
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled codec type = " << request.codec_type();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch( request.bitrate() )
|
|
||||||
{
|
|
||||||
case MediaRequest::Prepare::AudioBitrate_32_kbps: bitrate = 32000; break;
|
|
||||||
case MediaRequest::Prepare::AudioBitrate_64_kbps: bitrate = 64000; break;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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::Parameters parameters(inputFile, Transcode::Format::get( format ));
|
|
||||||
parameters.setBitrate( Transcode::Stream::Audio, bitrate);
|
|
||||||
|
|
||||||
std::shared_ptr<Transcode::AvConvTranscoder> transcoder = std::make_shared<Transcode::AvConvTranscoder>( parameters );
|
|
||||||
|
|
||||||
// now get a unique id (relative to this connection!)
|
|
||||||
uint32_t handle = _curHandle++;
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Caught exception: " << e.what();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
MediaRequestHandler::processGetPart(const MediaRequest::GetPart& request, MediaResponse::PartResult& response)
|
|
||||||
{
|
|
||||||
std::size_t dataSize = request.requested_data_size();
|
|
||||||
std::vector<unsigned char> data;
|
|
||||||
if (dataSize > _maxPartSize)
|
|
||||||
dataSize = _maxPartSize;
|
|
||||||
|
|
||||||
TranscoderMap::iterator itTranscoder = _transcoders.find(request.handle());
|
|
||||||
if (itTranscoder == _transcoders.end())
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::shared_ptr<Transcode::AvConvTranscoder> transcoder = itTranscoder->second;
|
|
||||||
|
|
||||||
if (!transcoder->isComplete())
|
|
||||||
{
|
|
||||||
data.reserve(dataSize);
|
|
||||||
transcoder->process(data, dataSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler::processGetPart, handle = " << request.handle() << ", isComplete = " << std::boolalpha << transcoder->isComplete() << ", size = " << data.size();
|
|
||||||
|
|
||||||
std::copy(data.begin(), data.end(), std::back_inserter(*response.mutable_data()));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool
|
|
||||||
MediaRequestHandler::processTerminate(const MediaRequest::Terminate& request, MediaResponse::TerminateResult& response)
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_DEBUG) << "MediaRequestHandler: resetting transcoder for handle " << request.handle();
|
|
||||||
|
|
||||||
if (_transcoders.find(request.handle()) == _transcoders.end())
|
|
||||||
{
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "No transcoder found for handle " << request.handle();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_transcoders.erase(request.handle());
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace LmsAPI
|
|
||||||
} // namespace Server
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef REMOTE_MEDIA_REQUEST_HANDLER
|
|
||||||
#define REMOTE_MEDIA_REQUEST_HANDLER
|
|
||||||
|
|
||||||
#include <map>
|
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
#include "database/DatabaseHandler.hpp"
|
|
||||||
#include "transcode/AvConvTranscoder.hpp"
|
|
||||||
|
|
||||||
#include "media.pb.h"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
class MediaRequestHandler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
MediaRequestHandler(Database::Handler& db);
|
|
||||||
|
|
||||||
bool process(const MediaRequest& request, MediaResponse& response);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
bool processAudioPrepare(const MediaRequest::Prepare::Audio& request, MediaResponse::PrepareResult& response);
|
|
||||||
bool processGetPart(const MediaRequest::GetPart& request, MediaResponse::PartResult& response);
|
|
||||||
bool processTerminate(const MediaRequest::Terminate& request, MediaResponse::TerminateResult& response);
|
|
||||||
|
|
||||||
// bool processVideoPrepare(const AudioCollectionRequest::GetGenreList& request, AudioCollectionResponse::GenreList& response);
|
|
||||||
|
|
||||||
typedef std::map<uint32_t, std::shared_ptr<Transcode::AvConvTranscoder> > TranscoderMap;
|
|
||||||
|
|
||||||
TranscoderMap _transcoders;
|
|
||||||
|
|
||||||
Database::Handler& _db;
|
|
||||||
|
|
||||||
uint32_t _curHandle = 0;
|
|
||||||
|
|
||||||
static const std::size_t _maxPartSize = 65536 - 128;
|
|
||||||
static const std::size_t _maxTranscoders = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Server
|
|
||||||
} // namespace LmsAPI
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include "logger/Logger.hpp"
|
|
||||||
|
|
||||||
#include "RequestHandler.hpp"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
RequestHandler::RequestHandler(Wt::Dbo::SqlConnectionPool &connectionPool)
|
|
||||||
: _db( connectionPool ),
|
|
||||||
_authRequestHandler(_db),
|
|
||||||
_audioCollectionRequestHandler(_db),
|
|
||||||
_mediaRequestHandler(_db)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
RequestHandler::~RequestHandler()
|
|
||||||
{
|
|
||||||
// TODO manually log out user if needed?
|
|
||||||
_db.getLogin().logout();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
RequestHandler::process(const ClientMessage& request, ServerMessage& response)
|
|
||||||
{
|
|
||||||
bool res = false;
|
|
||||||
|
|
||||||
switch(request.type())
|
|
||||||
{
|
|
||||||
|
|
||||||
case ClientMessage::AuthRequest:
|
|
||||||
if (request.has_auth_request())
|
|
||||||
{
|
|
||||||
res = _authRequestHandler.process(request.auth_request(), *response.mutable_auth_response());
|
|
||||||
if (res)
|
|
||||||
response.set_type(ServerMessage::AuthResponse);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad ClientMessage::AuthRequest !";
|
|
||||||
break;
|
|
||||||
case ClientMessage::AudioCollectionRequest:
|
|
||||||
// Not allowed if the user is not logged in
|
|
||||||
if (_db.getLogin().loggedIn())
|
|
||||||
{
|
|
||||||
if (request.has_audio_collection_request())
|
|
||||||
{
|
|
||||||
res = _audioCollectionRequestHandler.process(request.audio_collection_request(), *response.mutable_audio_collection_response());
|
|
||||||
if (res)
|
|
||||||
response.set_type( ServerMessage::AudioCollectionResponse);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Bad ClientMessage::AudioCollectionRequest message!";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ClientMessage::MediaRequest:
|
|
||||||
// Not allowed if the user is not logged in
|
|
||||||
if (_db.getLogin().loggedIn())
|
|
||||||
{
|
|
||||||
if (request.has_media_request())
|
|
||||||
{
|
|
||||||
res = _mediaRequestHandler.process(request.media_request(), *response.mutable_media_response());
|
|
||||||
if (res)
|
|
||||||
response.set_type( ServerMessage::MediaResponse);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Malformed ClientMessage::MediaRequest message!";
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "Unhandled message type = " << request.type();
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace Server
|
|
||||||
} // namespace LmsAPI
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef REMOTE_REQUEST_HANDLER
|
|
||||||
#define REMOTE_REQUEST_HANDLER
|
|
||||||
|
|
||||||
#include <Wt/Dbo/SqlConnectionPool>
|
|
||||||
|
|
||||||
#include "messages.pb.h"
|
|
||||||
|
|
||||||
#include "database/DatabaseHandler.hpp"
|
|
||||||
|
|
||||||
#include "AuthRequestHandler.hpp"
|
|
||||||
#include "AudioCollectionRequestHandler.hpp"
|
|
||||||
#include "MediaRequestHandler.hpp"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
class RequestHandler
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
RequestHandler(Wt::Dbo::SqlConnectionPool& connectionPool);
|
|
||||||
~RequestHandler();
|
|
||||||
|
|
||||||
bool process(const ClientMessage& request, ServerMessage& response);
|
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
Database::Handler _db;
|
|
||||||
|
|
||||||
AuthRequestHandler _authRequestHandler;
|
|
||||||
AudioCollectionRequestHandler _audioCollectionRequestHandler;
|
|
||||||
MediaRequestHandler _mediaRequestHandler;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Server
|
|
||||||
} // namespace LmsAPI
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#include <boost/asio/placeholders.hpp>
|
|
||||||
#include <boost/bind.hpp>
|
|
||||||
|
|
||||||
#include "logger/Logger.hpp"
|
|
||||||
|
|
||||||
#include "Server.hpp"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
Server::Server(const endpoint_type& bindEndpoint,
|
|
||||||
boost::filesystem::path certPath,
|
|
||||||
boost::filesystem::path privKeyPath,
|
|
||||||
boost::filesystem::path dhPath,
|
|
||||||
Wt::Dbo::SqlConnectionPool& connectionPool)
|
|
||||||
:
|
|
||||||
_acceptor(_ioService, bindEndpoint, true /*SO_REUSEADDR*/),
|
|
||||||
_connectionManager(),
|
|
||||||
_context(boost::asio::ssl::context::tlsv1_server),
|
|
||||||
_connectionPool(connectionPool)
|
|
||||||
{
|
|
||||||
_ioService.setThreadCount(1); // TODO parametrize
|
|
||||||
|
|
||||||
_context.set_options( boost::asio::ssl::context::default_workarounds // TODO check this thing
|
|
||||||
| boost::asio::ssl::context::single_dh_use
|
|
||||||
| boost::asio::ssl::context::no_sslv2
|
|
||||||
| boost::asio::ssl::context::no_sslv3
|
|
||||||
);
|
|
||||||
// context_.set_password_callback(boost::bind(&server::get_password, this));
|
|
||||||
_context.use_certificate_chain_file(certPath.string());
|
|
||||||
_context.use_private_key_file(privKeyPath.string(), boost::asio::ssl::context::pem);
|
|
||||||
_context.use_tmp_dh_file(dhPath.string());
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Server::start()
|
|
||||||
{
|
|
||||||
// While the server is running, there is always at least one
|
|
||||||
// asynchronous operation outstanding: the asynchronous accept call waiting
|
|
||||||
// for new incoming connections.
|
|
||||||
asyncAccept();
|
|
||||||
|
|
||||||
_ioService.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Server::asyncAccept()
|
|
||||||
{
|
|
||||||
std::shared_ptr<Connection> newConnection = std::make_shared<Connection>(_ioService, _context, _connectionManager, _connectionPool);
|
|
||||||
|
|
||||||
_acceptor.async_accept(newConnection->getSocket(),
|
|
||||||
boost::bind(&Server::handleAccept, this, newConnection, boost::asio::placeholders::error));
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
Server::handleAccept(std::shared_ptr<Connection> newConnection, boost::system::error_code ec)
|
|
||||||
{
|
|
||||||
// Check whether the server was stopped before this
|
|
||||||
// completion handler had a chance to run.
|
|
||||||
if (!_acceptor.is_open())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!ec)
|
|
||||||
{
|
|
||||||
_connectionManager.start(newConnection);
|
|
||||||
|
|
||||||
// Accept another connection
|
|
||||||
// TODO: add some limit?
|
|
||||||
asyncAccept();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
LMS_LOG(MOD_REMOTE, SEV_ERROR) << "handleAccept: " << ec.message();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
Server::stop()
|
|
||||||
{
|
|
||||||
// The server is stopped by cancelling all outstanding asynchronous
|
|
||||||
// operations.
|
|
||||||
_acceptor.close();
|
|
||||||
_connectionManager.stopAll();
|
|
||||||
|
|
||||||
_ioService.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Server
|
|
||||||
} // namespace LmsAPI
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef REMOTE_SERVER_HPP
|
|
||||||
#define REMOTE_SERVER_HPP
|
|
||||||
|
|
||||||
#include <Wt/WIOService>
|
|
||||||
#include <Wt/Dbo/SqlConnectionPool>
|
|
||||||
|
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include <boost/asio/ssl.hpp>
|
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
|
||||||
|
|
||||||
#include "Connection.hpp"
|
|
||||||
#include "ConnectionManager.hpp"
|
|
||||||
|
|
||||||
#include "RequestHandler.hpp"
|
|
||||||
|
|
||||||
namespace LmsAPI {
|
|
||||||
namespace Server {
|
|
||||||
|
|
||||||
class Server
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
|
|
||||||
Server(const Server&) = delete;
|
|
||||||
Server& operator=(const Server&) = delete;
|
|
||||||
|
|
||||||
typedef boost::asio::ip::tcp::endpoint endpoint_type;
|
|
||||||
|
|
||||||
// Serve up data from the given database
|
|
||||||
Server(const endpoint_type& bindEndpoint,
|
|
||||||
boost::filesystem::path certPath,
|
|
||||||
boost::filesystem::path privKeyPath,
|
|
||||||
boost::filesystem::path dhPath,
|
|
||||||
Wt::Dbo::SqlConnectionPool& connectionPool);
|
|
||||||
|
|
||||||
// Run the server's io_service loop.
|
|
||||||
void start();
|
|
||||||
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
private:
|
|
||||||
/// Perform an asynchronous accept operation.
|
|
||||||
void asyncAccept();
|
|
||||||
void handleAccept(std::shared_ptr<Connection> newConnection, boost::system::error_code ec);
|
|
||||||
|
|
||||||
Wt::WIOService _ioService;
|
|
||||||
|
|
||||||
/// Acceptor used to listen for incoming connections.
|
|
||||||
boost::asio::ip::tcp::acceptor _acceptor;
|
|
||||||
|
|
||||||
/// The connection manager which owns all live connections.
|
|
||||||
ConnectionManager _connectionManager;
|
|
||||||
|
|
||||||
boost::asio::ssl::context _context;
|
|
||||||
|
|
||||||
/// The database to be used for requests
|
|
||||||
Wt::Dbo::SqlConnectionPool& _connectionPool;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Server
|
|
||||||
} // namespace LmsAPI
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
#include <boost/thread.hpp>
|
#include <boost/thread.hpp>
|
||||||
|
|
||||||
#include "config/ConfigReader.hpp"
|
|
||||||
|
|
||||||
#include "DatabaseUpdateService.hpp"
|
#include "DatabaseUpdateService.hpp"
|
||||||
|
|
||||||
namespace Service {
|
namespace Service {
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ int main(void)
|
|||||||
|
|
||||||
track.modify()->setArtist(artist);
|
track.modify()->setArtist(artist);
|
||||||
track.modify()->setRelease(release);
|
track.modify()->setRelease(release);
|
||||||
track.modify()->setGenres( { genre });
|
track.modify()->setGenres( std::vector<Genre::pointer>({ genre }));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search
|
// Search
|
||||||
|
|||||||
@@ -3,35 +3,8 @@ TESTS = database-basics database-integrity sql-query database-user
|
|||||||
|
|
||||||
check_PROGRAMS = database-basics database-integrity sql-query database-user
|
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
|
|
||||||
|
|
||||||
nodist_remote_client_SOURCES = \
|
|
||||||
$(builddir)/auth.pb.cc \
|
|
||||||
$(builddir)/collection.pb.cc \
|
|
||||||
$(builddir)/media.pb.cc \
|
|
||||||
$(builddir)/messages.pb.cc
|
|
||||||
|
|
||||||
BUILT_SOURCES = \
|
|
||||||
$(builddir)/auth.pb.h \
|
|
||||||
$(builddir)/collection.pb.h \
|
|
||||||
$(builddir)/media.pb.h \
|
|
||||||
$(builddir)/messages.pb.h
|
|
||||||
|
|
||||||
remote_client_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_srcdir)/src
|
|
||||||
|
|
||||||
%.pb.cc %.pb.h: $(top_srcdir)/lms-api/proto/%.proto
|
|
||||||
$(PROTOC) --proto_path=$(top_srcdir)/lms-api/proto/ --cpp_out=$(builddir)/ $^
|
|
||||||
|
|
||||||
endif
|
|
||||||
|
|
||||||
database_basics_SOURCES = \
|
database_basics_SOURCES = \
|
||||||
$(srcdir)/CheckDbBasics.cpp \
|
$(srcdir)/CheckDbBasics.cpp \
|
||||||
$(top_srcdir)/src/config/ConfigReader.cpp \
|
|
||||||
$(top_srcdir)/src/logger/Logger.cpp \
|
$(top_srcdir)/src/logger/Logger.cpp \
|
||||||
$(top_srcdir)/src/database/Artist.cpp \
|
$(top_srcdir)/src/database/Artist.cpp \
|
||||||
$(top_srcdir)/src/database/DatabaseHandler.cpp \
|
$(top_srcdir)/src/database/DatabaseHandler.cpp \
|
||||||
@@ -48,7 +21,6 @@ database_basics_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_s
|
|||||||
|
|
||||||
database_user_SOURCES = \
|
database_user_SOURCES = \
|
||||||
$(srcdir)/CheckDatabaseUser.cpp \
|
$(srcdir)/CheckDatabaseUser.cpp \
|
||||||
$(top_srcdir)/src/config/ConfigReader.cpp \
|
|
||||||
$(top_srcdir)/src/logger/Logger.cpp \
|
$(top_srcdir)/src/logger/Logger.cpp \
|
||||||
$(top_srcdir)/src/database/Artist.cpp \
|
$(top_srcdir)/src/database/Artist.cpp \
|
||||||
$(top_srcdir)/src/database/Playlist.cpp \
|
$(top_srcdir)/src/database/Playlist.cpp \
|
||||||
@@ -66,7 +38,6 @@ database_user_CXXFLAGS=-std=c++11 -Wall -Wextra -DBOOST_LOG_DYN_LINK -I$(top_src
|
|||||||
|
|
||||||
database_integrity_SOURCES = \
|
database_integrity_SOURCES = \
|
||||||
$(srcdir)/DatabaseIntegrity.cpp \
|
$(srcdir)/DatabaseIntegrity.cpp \
|
||||||
$(top_srcdir)/src/config/ConfigReader.cpp \
|
|
||||||
$(top_srcdir)/src/logger/Logger.cpp \
|
$(top_srcdir)/src/logger/Logger.cpp \
|
||||||
$(top_srcdir)/src/database/Artist.cpp \
|
$(top_srcdir)/src/database/Artist.cpp \
|
||||||
$(top_srcdir)/src/database/Playlist.cpp \
|
$(top_srcdir)/src/database/Playlist.cpp \
|
||||||
|
|||||||
@@ -1,986 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2013 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <iostream>
|
|
||||||
#include <stdexcept>
|
|
||||||
#include <thread>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
#include <boost/filesystem.hpp>
|
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include <boost/asio/ssl.hpp>
|
|
||||||
#include <boost/foreach.hpp>
|
|
||||||
|
|
||||||
#include "lms-api/messages/Header.hpp"
|
|
||||||
#include "messages.pb.h"
|
|
||||||
|
|
||||||
#include "TestDatabase.hpp"
|
|
||||||
|
|
||||||
struct GenreInfo
|
|
||||||
{
|
|
||||||
uint64_t id;
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
GenreInfo() : id(0) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, const GenreInfo& info)
|
|
||||||
{
|
|
||||||
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 << "'(" << 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 << "'(" << info.id << ")";
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct TrackInfo
|
|
||||||
{
|
|
||||||
uint64_t id;
|
|
||||||
std::string mbid;
|
|
||||||
|
|
||||||
uint64_t release_id;
|
|
||||||
uint64_t artist_id;
|
|
||||||
std::vector<uint64_t> genre_id;
|
|
||||||
|
|
||||||
uint32_t disc_number;
|
|
||||||
uint32_t track_number;
|
|
||||||
|
|
||||||
std::string name;
|
|
||||||
|
|
||||||
boost::posix_time::time_duration duration;
|
|
||||||
|
|
||||||
std::string date;
|
|
||||||
std::string original_date;
|
|
||||||
|
|
||||||
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;
|
|
||||||
if (!info.date.empty())
|
|
||||||
os << ", date = " << info.date;
|
|
||||||
if (!info.original_date.empty())
|
|
||||||
os << ", original date = " << info.original_date;
|
|
||||||
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Cover
|
|
||||||
{
|
|
||||||
std::string mimeType;
|
|
||||||
std::vector<unsigned char> data;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SearchFilter
|
|
||||||
{
|
|
||||||
std::vector<uint64_t> artistIds;
|
|
||||||
std::vector<uint64_t> genreIds;
|
|
||||||
std::vector<uint64_t> releaseIds;
|
|
||||||
std::vector<uint64_t> trackIds;
|
|
||||||
};
|
|
||||||
|
|
||||||
void SearchFilterToRequest(const SearchFilter& filter, LmsAPI::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:
|
|
||||||
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<ArtistInfo>& artists, const SearchFilter& filter = SearchFilter())
|
|
||||||
{
|
|
||||||
|
|
||||||
const std::size_t requestedBatchSize = 128;
|
|
||||||
std::size_t offset = 0;
|
|
||||||
std::size_t res = 0;
|
|
||||||
|
|
||||||
|
|
||||||
while ((res = getArtists(artists, filter, offset, requestedBatchSize) ) > 0)
|
|
||||||
offset += res;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t getArtists(std::vector<ArtistInfo>& artists, const SearchFilter& filter, std::size_t offset, std::size_t size)
|
|
||||||
{
|
|
||||||
std::size_t nbArtists = 0;
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
request.set_type( LmsAPI::ClientMessage_Type_AudioCollectionRequest );
|
|
||||||
|
|
||||||
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest_Type_TypeGetArtistList);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_artists()->mutable_batch_parameter()->set_size(size);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_artists()->mutable_batch_parameter()->set_offset(offset);
|
|
||||||
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_artists()->mutable_search_filter());
|
|
||||||
|
|
||||||
sendMsg(request);
|
|
||||||
|
|
||||||
// Receive responses
|
|
||||||
LmsAPI::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)
|
|
||||||
{
|
|
||||||
const LmsAPI::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 = respArtist.name();
|
|
||||||
artist.id = respArtist.id();
|
|
||||||
if (respArtist.has_mbid())
|
|
||||||
artist.mbid = respArtist.mbid();
|
|
||||||
|
|
||||||
artists.push_back( artist );
|
|
||||||
nbArtists++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nbArtists;
|
|
||||||
}
|
|
||||||
|
|
||||||
void getGenres(std::vector<GenreInfo>& 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, filter, offset, requestedBatchSize) ) > 0)
|
|
||||||
offset += res;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t getGenres(std::vector<GenreInfo>& genres, const SearchFilter& filter, std::size_t offset, std::size_t size)
|
|
||||||
{
|
|
||||||
std::size_t nbAdded = 0;
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
request.set_type( LmsAPI::ClientMessage_Type_AudioCollectionRequest );
|
|
||||||
|
|
||||||
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest_Type_TypeGetGenreList);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_genres()->mutable_batch_parameter()->set_size(size);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_genres()->mutable_batch_parameter()->set_offset(offset);
|
|
||||||
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_genres()->mutable_search_filter());
|
|
||||||
|
|
||||||
sendMsg(request);
|
|
||||||
|
|
||||||
// Receive responses
|
|
||||||
LmsAPI::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)
|
|
||||||
{
|
|
||||||
const LmsAPI::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.id = respGenre.id();
|
|
||||||
genre.name = respGenre.name();
|
|
||||||
|
|
||||||
genres.push_back( genre );
|
|
||||||
nbAdded++;
|
|
||||||
}
|
|
||||||
return nbAdded;
|
|
||||||
}
|
|
||||||
|
|
||||||
void getReleases(std::vector<ReleaseInfo>& 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, filter, offset, requestedBatchSize) ) > 0)
|
|
||||||
offset += res;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t getReleases(std::vector<ReleaseInfo>& releases, const SearchFilter& filter, std::size_t offset, std::size_t size)
|
|
||||||
{
|
|
||||||
std::size_t nbAdded = 0;
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
request.set_type( LmsAPI::ClientMessage_Type_AudioCollectionRequest );
|
|
||||||
|
|
||||||
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest_Type_TypeGetReleaseList);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_releases()->mutable_batch_parameter()->set_size(size);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_releases()->mutable_batch_parameter()->set_offset(offset);
|
|
||||||
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_releases()->mutable_search_filter());
|
|
||||||
|
|
||||||
sendMsg(request);
|
|
||||||
|
|
||||||
// Receive responses
|
|
||||||
LmsAPI::ServerMessage response;
|
|
||||||
recvMsg(response);
|
|
||||||
|
|
||||||
// Process message
|
|
||||||
if (!response.audio_collection_response().has_type())
|
|
||||||
throw std::runtime_error("Missing type!");
|
|
||||||
|
|
||||||
// if (!response.audio_collection_response().type() != LmsAPI::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)
|
|
||||||
{
|
|
||||||
const LmsAPI::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.id = respRelease.id();
|
|
||||||
release.name = respRelease.name();
|
|
||||||
|
|
||||||
releases.push_back( release );
|
|
||||||
nbAdded++;
|
|
||||||
}
|
|
||||||
return nbAdded;
|
|
||||||
}
|
|
||||||
|
|
||||||
void getTracks(std::vector<TrackInfo>& 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, filter, offset, requestedBatchSize) ) > 0)
|
|
||||||
offset += res;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t getTracks(std::vector<TrackInfo>& tracks, const SearchFilter& filter, std::size_t offset, std::size_t size)
|
|
||||||
{
|
|
||||||
std::size_t nbAdded = 0;
|
|
||||||
|
|
||||||
// Send request
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
request.set_type( LmsAPI::ClientMessage_Type_AudioCollectionRequest );
|
|
||||||
|
|
||||||
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest_Type_TypeGetTrackList);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_batch_parameter()->set_size(size);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_batch_parameter()->set_offset(offset);
|
|
||||||
SearchFilterToRequest(filter, *request.mutable_audio_collection_request()->mutable_get_tracks()->mutable_search_filter());
|
|
||||||
|
|
||||||
sendMsg(request);
|
|
||||||
|
|
||||||
// Receive responses
|
|
||||||
LmsAPI::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)
|
|
||||||
{
|
|
||||||
const LmsAPI::AudioCollectionResponse_Track& respTrack = response.audio_collection_response().track_list().tracks(i);;
|
|
||||||
|
|
||||||
TrackInfo track;
|
|
||||||
track.id = respTrack.id();
|
|
||||||
if (respTrack.has_mbid())
|
|
||||||
track.mbid = respTrack.mbid();
|
|
||||||
|
|
||||||
track.name = respTrack.name();
|
|
||||||
track.duration = boost::posix_time::seconds(respTrack.duration_secs());
|
|
||||||
|
|
||||||
if (respTrack.has_track_number())
|
|
||||||
track.track_number = respTrack.track_number();
|
|
||||||
|
|
||||||
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 );
|
|
||||||
nbAdded++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nbAdded;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void getMediaAudio(uint64_t audioId, std::vector<unsigned char>& data)
|
|
||||||
{
|
|
||||||
|
|
||||||
// Prepare
|
|
||||||
uint32_t handle = mediaAudioPrepare( audioId );
|
|
||||||
|
|
||||||
// Get while available
|
|
||||||
mediaGet( handle, data );
|
|
||||||
|
|
||||||
// Terminate
|
|
||||||
mediaTerminate( handle );
|
|
||||||
}
|
|
||||||
|
|
||||||
void getCoverTrack(std::vector<Cover>& coverArt, uint64_t trackId)
|
|
||||||
{
|
|
||||||
// Send request
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
request.set_type( LmsAPI::ClientMessage::AudioCollectionRequest );
|
|
||||||
|
|
||||||
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest::TypeGetCoverArt);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_type( LmsAPI::AudioCollectionRequest::GetCoverArt::TypeGetCoverArtTrack);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_track_id( trackId );
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_size( 256 );
|
|
||||||
sendMsg(request);
|
|
||||||
|
|
||||||
// Receive responses
|
|
||||||
LmsAPI::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<Cover>& coverArt, uint64_t releaseId)
|
|
||||||
{
|
|
||||||
// Send request
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
request.set_type( LmsAPI::ClientMessage::AudioCollectionRequest );
|
|
||||||
|
|
||||||
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest::TypeGetCoverArt);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_type( LmsAPI::AudioCollectionRequest::GetCoverArt::TypeGetCoverArtRelease);
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_release_id( releaseId );
|
|
||||||
request.mutable_audio_collection_request()->mutable_get_cover_art()->set_size( 256 );
|
|
||||||
|
|
||||||
sendMsg(request);
|
|
||||||
|
|
||||||
// Receive responses
|
|
||||||
LmsAPI::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
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
request.set_type( LmsAPI::ClientMessage::AudioCollectionRequest );
|
|
||||||
|
|
||||||
request.mutable_audio_collection_request()->set_type( LmsAPI::AudioCollectionRequest::TypeGetRevision);
|
|
||||||
|
|
||||||
sendMsg(request);
|
|
||||||
|
|
||||||
// Receive responses
|
|
||||||
LmsAPI::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
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
request.set_type( LmsAPI::ClientMessage::AuthRequest );
|
|
||||||
|
|
||||||
request.mutable_auth_request()->set_type( LmsAPI::AuthRequest::TypePassword);
|
|
||||||
request.mutable_auth_request()->mutable_password()->set_user_login( username );
|
|
||||||
request.mutable_auth_request()->mutable_password()->set_user_password( password );
|
|
||||||
|
|
||||||
sendMsg(request);
|
|
||||||
|
|
||||||
// Receive responses
|
|
||||||
LmsAPI::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 LmsAPI::AuthResponse::PasswordResult::TypePasswordValid:
|
|
||||||
return true;
|
|
||||||
case LmsAPI::AuthResponse::PasswordResult::TypePasswordInvalid:
|
|
||||||
return false;
|
|
||||||
case LmsAPI::AuthResponse::PasswordResult::TypeLoginThrottling:
|
|
||||||
if (response.auth_response().password_result().has_delay())
|
|
||||||
std::cerr << "Has to wait for " << response.auth_response().password_result().delay() << " seconds" << std::endl;
|
|
||||||
|
|
||||||
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<char, 256> 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
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
request.set_type( LmsAPI::ClientMessage_Type_MediaRequest);
|
|
||||||
|
|
||||||
request.mutable_media_request()->set_type( LmsAPI::MediaRequest_Type_TypeMediaPrepare);
|
|
||||||
request.mutable_media_request()->mutable_prepare()->set_type(LmsAPI::MediaRequest_Prepare_Type_AudioRequest);
|
|
||||||
|
|
||||||
// Set fields
|
|
||||||
request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_track_id( audioId );
|
|
||||||
request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_codec_type( LmsAPI::MediaRequest::Prepare::AudioCodecTypeOGA );
|
|
||||||
request.mutable_media_request()->mutable_prepare()->mutable_audio()->set_bitrate( LmsAPI::MediaRequest::Prepare::AudioBitrate_64_kbps );
|
|
||||||
|
|
||||||
std::cout << "Sending prepare request" << std::endl;
|
|
||||||
sendMsg(request);
|
|
||||||
|
|
||||||
std::cout << "Waiting for response" << std::endl;
|
|
||||||
// Receive response
|
|
||||||
LmsAPI::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<unsigned char>& data)
|
|
||||||
{
|
|
||||||
while (mediaGetPart(handle, data) > 0)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::size_t mediaGetPart(uint32_t handle, std::vector<unsigned char>& data)
|
|
||||||
{
|
|
||||||
// Send request
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
request.set_type( LmsAPI::ClientMessage::MediaRequest);
|
|
||||||
|
|
||||||
request.mutable_media_request()->set_type( LmsAPI::MediaRequest::TypeMediaGetPart);
|
|
||||||
request.mutable_media_request()->mutable_get_part()->set_handle(handle);
|
|
||||||
request.mutable_media_request()->mutable_get_part()->set_requested_data_size(65536);
|
|
||||||
|
|
||||||
std::cout << "Sending GetPart request" << std::endl;
|
|
||||||
sendMsg(request);
|
|
||||||
|
|
||||||
std::cout << "Waiting for response" << std::endl;
|
|
||||||
// Receive response
|
|
||||||
LmsAPI::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() != LmsAPI::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
|
|
||||||
LmsAPI::ClientMessage request;
|
|
||||||
|
|
||||||
request.set_type( LmsAPI::ClientMessage::MediaRequest);
|
|
||||||
|
|
||||||
request.mutable_media_request()->set_type( LmsAPI::MediaRequest::TypeMediaTerminate);
|
|
||||||
request.mutable_media_request()->mutable_terminate()->set_handle(handle);
|
|
||||||
|
|
||||||
sendMsg(request);
|
|
||||||
|
|
||||||
// Receive response
|
|
||||||
LmsAPI::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() > LmsAPI::Header::max_data_size)
|
|
||||||
{
|
|
||||||
std::ostringstream oss; oss << "Message too big = " << _outputStreamBuf.size() << " bytes! (max is " << LmsAPI::Header::max_data_size << ")" << std::endl;
|
|
||||||
throw std::runtime_error("Message to big!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send message header
|
|
||||||
std::array<unsigned char, LmsAPI::Header::size> headerBuffer;
|
|
||||||
|
|
||||||
// Generate header content
|
|
||||||
{
|
|
||||||
LmsAPI::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(LmsAPI::Header::size);
|
|
||||||
|
|
||||||
std::size_t n = boost::asio::read(_socket,
|
|
||||||
bufs,
|
|
||||||
boost::asio::transfer_exactly(LmsAPI::Header::size));
|
|
||||||
|
|
||||||
assert(n == LmsAPI::Header::size);
|
|
||||||
_inputStreamBuf.commit(n);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
LmsAPI::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<boost::asio::ip::tcp::socket> 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,
|
|
||||||
CoverByTrack,
|
|
||||||
CoverByRelease,
|
|
||||||
Transcode,
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Uncomment test to be disabled */
|
|
||||||
std::set<Test> tests = {
|
|
||||||
Test::ArtistFilters,
|
|
||||||
Test::ReleaseFilterArtist,
|
|
||||||
Test::ReleaseFilterGenre,
|
|
||||||
Test::TrackFilters,
|
|
||||||
// Test::CoverByTrack,
|
|
||||||
Test::CoverByRelease,
|
|
||||||
// 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", "totoadmin"))
|
|
||||||
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<ArtistInfo> artists;
|
|
||||||
client.getArtists(artists);
|
|
||||||
|
|
||||||
std::cout << "Got " << artists.size() << " artists!" << std::endl;
|
|
||||||
for (const ArtistInfo& artist : artists)
|
|
||||||
std::cout << "Artist: '" << artist << "'" << std::endl;
|
|
||||||
|
|
||||||
// ***** Genres *********
|
|
||||||
std::cout << "Getting genres..." << std::endl;
|
|
||||||
std::vector<GenreInfo> genres;
|
|
||||||
client.getGenres(genres);
|
|
||||||
|
|
||||||
std::cout << "Got " << genres.size() << " genres!" << std::endl;
|
|
||||||
for (const GenreInfo& genre : genres)
|
|
||||||
std::cout << "Genre: '" << genre << "'" << std::endl;
|
|
||||||
|
|
||||||
// **** Releases ******
|
|
||||||
std::cout << "Getting releases..." << std::endl;
|
|
||||||
std::vector<ReleaseInfo> releases;
|
|
||||||
client.getReleases(releases);
|
|
||||||
for (const ReleaseInfo& release : releases)
|
|
||||||
std::cout << "Release: '" << release << "'" << std::endl;
|
|
||||||
|
|
||||||
// **** Tracks ******
|
|
||||||
std::cout << "Getting tracks..." << std::endl;
|
|
||||||
std::vector<TrackInfo> tracks;
|
|
||||||
client.getTracks(tracks);
|
|
||||||
|
|
||||||
std::cout << "Got " << tracks.size() << " tracks!" << std::endl;
|
|
||||||
for (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
|
|
||||||
for (const GenreInfo& genre : genres)
|
|
||||||
{
|
|
||||||
std::cout << "Getting artists from genre '" << genre.name << "'... ";
|
|
||||||
SearchFilter filter;
|
|
||||||
filter.genreIds.push_back(genre.id);
|
|
||||||
|
|
||||||
std::vector<ArtistInfo> artists;
|
|
||||||
client.getArtists(artists, filter);
|
|
||||||
|
|
||||||
std::cout << "Found " << artists.size() << " artists!" << std::endl;
|
|
||||||
for (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;
|
|
||||||
for (const ArtistInfo& artist : artists)
|
|
||||||
{
|
|
||||||
std::cout << "Getting release from artist '" << artist.name << "'... ";
|
|
||||||
SearchFilter filter;
|
|
||||||
filter.artistIds.push_back(artist.id);
|
|
||||||
|
|
||||||
std::vector<ReleaseInfo> releases;
|
|
||||||
client.getReleases(releases, filter);
|
|
||||||
|
|
||||||
std::cout << "Found " << releases.size() << " releases!" << std::endl;
|
|
||||||
for (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;
|
|
||||||
for (const GenreInfo& genre : genres)
|
|
||||||
{
|
|
||||||
std::cout << "Getting release from genre '" << genre.name << "'... ";
|
|
||||||
SearchFilter filter;
|
|
||||||
filter.genreIds.push_back(genre.id);
|
|
||||||
|
|
||||||
std::vector<ReleaseInfo> releases;
|
|
||||||
client.getReleases(releases, filter);
|
|
||||||
|
|
||||||
std::cout << "Found " << releases.size() << " releases!" << std::endl;
|
|
||||||
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
|
|
||||||
for (const ArtistInfo& artist : artists)
|
|
||||||
{
|
|
||||||
SearchFilter filter;
|
|
||||||
filter.artistIds.push_back(artist.id);
|
|
||||||
|
|
||||||
std::vector<TrackInfo> tracks;
|
|
||||||
client.getTracks(tracks, filter);
|
|
||||||
|
|
||||||
std::cout << "Artist '" << artist.name << "', nb tracks = " << tracks.size() << std::endl;
|
|
||||||
for (const TrackInfo& track : tracks)
|
|
||||||
std::cout << "Artist '" << artist.name << "', track: '" << track << "'" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ***** Covers *******
|
|
||||||
if (test(Test::CoverByRelease))
|
|
||||||
{
|
|
||||||
std::cout << "Getting cover for each release..." << std::endl;
|
|
||||||
for (const ReleaseInfo& release : releases)
|
|
||||||
{
|
|
||||||
std::vector<Cover> coverArts;
|
|
||||||
client.getCoverRelease(coverArts, release.id);
|
|
||||||
|
|
||||||
if (writeCovers)
|
|
||||||
{
|
|
||||||
boost::filesystem::create_directory("cover");
|
|
||||||
for (const Cover coverArt : coverArts)
|
|
||||||
{
|
|
||||||
std::ostringstream oss; oss << "cover/" << release.name << ".jpeg";
|
|
||||||
std::ofstream out(oss.str().c_str());
|
|
||||||
for (unsigned char c : coverArt.data)
|
|
||||||
out.put(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::cout << "Release '" << release << "', spotted " << coverArts.size() << " covers!" << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (test(Test::CoverByTrack))
|
|
||||||
{
|
|
||||||
std::cout << "Getting cover for each track..." << std::endl;
|
|
||||||
for (const TrackInfo& track : tracks)
|
|
||||||
{
|
|
||||||
std::vector<Cover> coverArt;
|
|
||||||
client.getCoverTrack(coverArt, track.id);
|
|
||||||
|
|
||||||
std::cout << "Track '" << track << "', spotted " << coverArt.size() << " covers!" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (test(Test::Transcode))
|
|
||||||
{
|
|
||||||
for (const TrackInfo& track : tracks)
|
|
||||||
{
|
|
||||||
std::vector<unsigned char> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user