mirror of
https://github.com/badaix/snapcast.git
synced 2025-05-11 08:06:41 +02:00
Move control requests into Request factory
This commit is contained in:
parent
45f189432d
commit
a0d88e8856
5 changed files with 1249 additions and 560 deletions
|
@ -2,6 +2,7 @@ set(SERVER_SOURCES
|
||||||
authinfo.cpp
|
authinfo.cpp
|
||||||
config.cpp
|
config.cpp
|
||||||
control_server.cpp
|
control_server.cpp
|
||||||
|
control_requests.cpp
|
||||||
control_session_tcp.cpp
|
control_session_tcp.cpp
|
||||||
control_session_http.cpp
|
control_session_http.cpp
|
||||||
control_session_ws.cpp
|
control_session_ws.cpp
|
||||||
|
|
897
server/control_requests.cpp
Normal file
897
server/control_requests.cpp
Normal file
|
@ -0,0 +1,897 @@
|
||||||
|
/***
|
||||||
|
This file is part of snapcast
|
||||||
|
Copyright (C) 2014-2024 Johannes Pohl
|
||||||
|
|
||||||
|
This program 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.
|
||||||
|
|
||||||
|
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
***/
|
||||||
|
|
||||||
|
// prototype/interface header file
|
||||||
|
#include "control_requests.hpp"
|
||||||
|
|
||||||
|
// local headers
|
||||||
|
#include "common/aixlog.hpp"
|
||||||
|
#include "common/message/server_settings.hpp"
|
||||||
|
#include "server.hpp"
|
||||||
|
|
||||||
|
// 3rd party headers
|
||||||
|
|
||||||
|
// standard headers
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
|
||||||
|
static constexpr auto LOG_TAG = "ControlRequest";
|
||||||
|
|
||||||
|
|
||||||
|
Request::Request(const Server& server, const std::string& method, const std::string& ressource) : server_(server), method_(method), ressource_(ressource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Request::hasPermission(const AuthInfo& authinfo) const
|
||||||
|
{
|
||||||
|
return authinfo.hasPermission(ressource_);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Request::method() const
|
||||||
|
{
|
||||||
|
return method_;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const StreamServer& Request::getStreamServer() const
|
||||||
|
{
|
||||||
|
return *server_.streamServer_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StreamManager& Request::getStreamManager() const
|
||||||
|
{
|
||||||
|
return *server_.streamManager_;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ServerSettings& Request::getSettings() const
|
||||||
|
{
|
||||||
|
return server_.settings_;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ControlRequestFactory::ControlRequestFactory(const Server& server)
|
||||||
|
{
|
||||||
|
auto add_request = [&](std::shared_ptr<Request>&& request) { request_map_[request->method()] = std::move(request); };
|
||||||
|
|
||||||
|
// Client requests
|
||||||
|
add_request(std::make_shared<ClientGetStatusRequest>(server));
|
||||||
|
add_request(std::make_shared<ClientSetVolumeRequest>(server));
|
||||||
|
add_request(std::make_shared<ClientSetLatencyRequest>(server));
|
||||||
|
add_request(std::make_shared<ClientSetNameRequest>(server));
|
||||||
|
|
||||||
|
// Group requests
|
||||||
|
add_request(std::make_shared<GroupGetStatusRequest>(server));
|
||||||
|
add_request(std::make_shared<GroupSetNameRequest>(server));
|
||||||
|
add_request(std::make_shared<GroupSetMuteRequest>(server));
|
||||||
|
add_request(std::make_shared<GroupSetStreamRequest>(server));
|
||||||
|
add_request(std::make_shared<GroupSetClientsRequest>(server));
|
||||||
|
|
||||||
|
// Stream requests
|
||||||
|
add_request(std::make_shared<StreamControlRequest>(server));
|
||||||
|
add_request(std::make_shared<StreamSetPropertyRequest>(server));
|
||||||
|
add_request(std::make_shared<StreamAddRequest>(server));
|
||||||
|
add_request(std::make_shared<StreamRemoveRequest>(server));
|
||||||
|
|
||||||
|
// Server requests
|
||||||
|
add_request(std::make_shared<ServerGetRpcVersionRequest>(server));
|
||||||
|
add_request(std::make_shared<ServerGetStatusRequest>(server));
|
||||||
|
add_request(std::make_shared<ServerDeleteClientRequest>(server));
|
||||||
|
add_request(std::make_shared<ServerAuthenticateRequest>(server));
|
||||||
|
add_request(std::make_shared<ServerGetTokenRequest>(server));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<Request> ControlRequestFactory::getRequest(const std::string& method) const
|
||||||
|
{
|
||||||
|
auto iter = request_map_.find(method);
|
||||||
|
if (iter != request_map_.end())
|
||||||
|
return iter->second;
|
||||||
|
return nullptr;
|
||||||
|
// if (method == "Client.GetStatus")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Client.SetVolume")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Client.SetLatency")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Client.SetName")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Group.GetStatus")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Group.SetName")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Group.SetMute")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Group.SetStream")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Group.SetClients")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Server.GetRPCVersion")
|
||||||
|
// return std::make_unique<ServerGetRpcVersionRequest>(server);
|
||||||
|
// else if (method == "Server.GetStatus")
|
||||||
|
// return std::make_unique<ServerGetStatusRequest>(server);
|
||||||
|
// else if (method == "Server.DeleteClient")
|
||||||
|
// return std::make_unique<ServerDeleteClientRequest>(server);
|
||||||
|
// else if (method == "Server.Authenticate")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Server.GetToken")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Stream.AddStream")
|
||||||
|
// return nullptr;
|
||||||
|
// else if (method == "Stream.RemoveStream")
|
||||||
|
// return nullptr;
|
||||||
|
// else
|
||||||
|
// return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////// Client requests /////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
ClientRequest::ClientRequest(const Server& server, const std::string& method, const std::string& ressource) : Request(server, method, ressource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientInfoPtr ClientRequest::getClient(const jsonrpcpp::request_ptr& request)
|
||||||
|
{
|
||||||
|
if (!request->params().has("id"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Parameter 'id' is missing", request->id());
|
||||||
|
|
||||||
|
ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get<std::string>("id"));
|
||||||
|
if (clientInfo == nullptr)
|
||||||
|
throw jsonrpcpp::InternalErrorException("Client not found", request->id());
|
||||||
|
return clientInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ClientRequest::updateClient(const jsonrpcpp::request_ptr& request)
|
||||||
|
{
|
||||||
|
ClientInfoPtr clientInfo = getClient(request);
|
||||||
|
session_ptr session = getStreamServer().getStreamSession(clientInfo->id);
|
||||||
|
if (session != nullptr)
|
||||||
|
{
|
||||||
|
auto serverSettings = std::make_shared<msg::ServerSettings>();
|
||||||
|
serverSettings->setBufferMs(getSettings().stream.bufferMs);
|
||||||
|
serverSettings->setVolume(clientInfo->config.volume.percent);
|
||||||
|
GroupPtr group = Config::instance().getGroupFromClient(clientInfo);
|
||||||
|
serverSettings->setMuted(clientInfo->config.volume.muted || group->muted);
|
||||||
|
serverSettings->setLatency(clientInfo->config.latency);
|
||||||
|
session->send(serverSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ClientGetStatusRequest::ClientGetStatusRequest(const Server& server) : ClientRequest(server, "Client.GetStatus", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientGetStatusRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":8,"jsonrpc":"2.0","method":"Client.GetStatus","params":{"id":"00:21:6a:7d:74:fc"}}
|
||||||
|
// Response: {"id":8,"jsonrpc":"2.0","result":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026416,"usec":135973},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
|
||||||
|
Json result;
|
||||||
|
result["client"] = getClient(request)->toJson();
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
on_response(std::move(response), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ClientSetVolumeRequest::ClientSetVolumeRequest(const Server& server) : ClientRequest(server, "Client.SetVolume", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientSetVolumeRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":8,"jsonrpc":"2.0","method":"Client.SetVolume","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":74}}}
|
||||||
|
// Response: {"id":8,"jsonrpc":"2.0","result":{"volume":{"muted":false,"percent":74}}}
|
||||||
|
// Notification: {"jsonrpc":"2.0","method":"Client.OnVolumeChanged","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":74}}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
auto client_info = getClient(request);
|
||||||
|
client_info->config.volume.fromJson(request->params().get("volume"));
|
||||||
|
Json result;
|
||||||
|
result["volume"] = client_info->config.volume.toJson();
|
||||||
|
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
auto notification = std::make_shared<jsonrpcpp::Notification>("Client.OnVolumeChanged",
|
||||||
|
jsonrpcpp::Parameter("id", client_info->id, "volume", client_info->config.volume.toJson()));
|
||||||
|
on_response(std::move(response), std::move(notification));
|
||||||
|
updateClient(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ClientSetLatencyRequest::ClientSetLatencyRequest(const Server& server) : ClientRequest(server, "Client.SetLatency", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientSetLatencyRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":7,"jsonrpc":"2.0","method":"Client.SetLatency","params":{"id":"00:21:6a:7d:74:fc#2","latency":10}}
|
||||||
|
// Response: {"id":7,"jsonrpc":"2.0","result":{"latency":10}}
|
||||||
|
// Notification: {"jsonrpc":"2.0","method":"Client.OnLatencyChanged","params":{"id":"00:21:6a:7d:74:fc#2","latency":10}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
int latency = request->params().get("latency");
|
||||||
|
if (latency < -10000)
|
||||||
|
latency = -10000;
|
||||||
|
else if (latency > getSettings().stream.bufferMs)
|
||||||
|
latency = getSettings().stream.bufferMs;
|
||||||
|
auto client_info = getClient(request);
|
||||||
|
client_info->config.latency = latency; //, -10000, settings_.stream.bufferMs);
|
||||||
|
Json result;
|
||||||
|
result["latency"] = client_info->config.latency;
|
||||||
|
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
auto notification = std::make_shared<jsonrpcpp::Notification>("Client.OnLatencyChanged",
|
||||||
|
jsonrpcpp::Parameter("id", client_info->id, "latency", client_info->config.latency));
|
||||||
|
on_response(std::move(response), std::move(notification));
|
||||||
|
updateClient(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ClientSetNameRequest::ClientSetNameRequest(const Server& server) : ClientRequest(server, "Client.SetName", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClientSetNameRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":6,"jsonrpc":"2.0","method":"Client.SetName","params":{"id":"00:21:6a:7d:74:fc#2","name":"Laptop"}}
|
||||||
|
// Response: {"id":6,"jsonrpc":"2.0","result":{"name":"Laptop"}}
|
||||||
|
// Notification: {"jsonrpc":"2.0","method":"Client.OnNameChanged","params":{"id":"00:21:6a:7d:74:fc#2","name":"Laptop"}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
auto client_info = getClient(request);
|
||||||
|
client_info->config.name = request->params().get<std::string>("name");
|
||||||
|
Json result;
|
||||||
|
result["name"] = client_info->config.name;
|
||||||
|
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
auto notification =
|
||||||
|
std::make_shared<jsonrpcpp::Notification>("Client.OnNameChanged", jsonrpcpp::Parameter("id", client_info->id, "name", client_info->config.name));
|
||||||
|
on_response(std::move(response), std::move(notification));
|
||||||
|
updateClient(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////// Group requests //////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
GroupRequest::GroupRequest(const Server& server, const std::string& method, const std::string& ressource) : Request(server, method, ressource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupPtr GroupRequest::getGroup(const jsonrpcpp::request_ptr& request)
|
||||||
|
{
|
||||||
|
if (!request->params().has("id"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Parameter 'id' is missing", request->id());
|
||||||
|
|
||||||
|
GroupPtr group = Config::instance().getGroup(request->params().get<std::string>("id"));
|
||||||
|
if (group == nullptr)
|
||||||
|
throw jsonrpcpp::InternalErrorException("Group not found", request->id());
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
GroupGetStatusRequest::GroupGetStatusRequest(const Server& server) : GroupRequest(server, "Group.GetStatus", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupGetStatusRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":5,"jsonrpc":"2.0","method":"Group.GetStatus","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}}
|
||||||
|
// Response: {"id":5,"jsonrpc":"2.0","result":{"group":{"clients":[{"config":{"instance":2,"latency":10,"name":"Laptop","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488026485,"usec":644997},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026481,"usec":223747},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":true,"name":"","stream_id":"stream 1"}}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
Json result;
|
||||||
|
result["group"] = getGroup(request)->toJson();
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
on_response(std::move(response), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GroupSetNameRequest::GroupSetNameRequest(const Server& server) : GroupRequest(server, "Group.SetName", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupSetNameRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":6,"jsonrpc":"2.0","method":"Group.SetName","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","name":"Laptop"}}
|
||||||
|
// Response: {"id":6,"jsonrpc":"2.0","result":{"name":"MediaPlayer"}}
|
||||||
|
// Notification: {"jsonrpc":"2.0","method":"Group.OnNameChanged","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","MediaPlayer":"Laptop"}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
Json result;
|
||||||
|
auto group = getGroup(request);
|
||||||
|
group->name = request->params().get<std::string>("name");
|
||||||
|
result["name"] = group->name;
|
||||||
|
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
auto notification = std::make_shared<jsonrpcpp::Notification>("Group.OnNameChanged", jsonrpcpp::Parameter("id", group->id, "name", group->name));
|
||||||
|
on_response(std::move(response), std::move(notification));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GroupSetMuteRequest::GroupSetMuteRequest(const Server& server) : GroupRequest(server, "Group.SetMute", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupSetMuteRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":5,"jsonrpc":"2.0","method":"Group.SetMute","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","mute":true}}
|
||||||
|
// Response: {"id":5,"jsonrpc":"2.0","result":{"mute":true}}
|
||||||
|
// Notification: {"jsonrpc":"2.0","method":"Group.OnMute","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","mute":true}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
bool muted = request->params().get<bool>("mute");
|
||||||
|
auto group = getGroup(request);
|
||||||
|
group->muted = muted;
|
||||||
|
|
||||||
|
// Update clients
|
||||||
|
for (const auto& client : group->clients)
|
||||||
|
{
|
||||||
|
session_ptr session = getStreamServer().getStreamSession(client->id);
|
||||||
|
if (session != nullptr)
|
||||||
|
{
|
||||||
|
auto serverSettings = std::make_shared<msg::ServerSettings>();
|
||||||
|
serverSettings->setBufferMs(getSettings().stream.bufferMs);
|
||||||
|
serverSettings->setVolume(client->config.volume.percent);
|
||||||
|
GroupPtr group = Config::instance().getGroupFromClient(client);
|
||||||
|
serverSettings->setMuted(client->config.volume.muted || group->muted);
|
||||||
|
serverSettings->setLatency(client->config.latency);
|
||||||
|
session->send(serverSettings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Json result;
|
||||||
|
result["mute"] = group->muted;
|
||||||
|
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
auto notification = std::make_shared<jsonrpcpp::Notification>("Group.OnMute", jsonrpcpp::Parameter("id", group->id, "mute", group->muted));
|
||||||
|
on_response(std::move(response), std::move(notification));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GroupSetStreamRequest::GroupSetStreamRequest(const Server& server) : GroupRequest(server, "Group.SetStream", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupSetStreamRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":4,"jsonrpc":"2.0","method":"Group.SetStream","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream 1"}}
|
||||||
|
// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"stream 1"}}
|
||||||
|
// Notification: {"jsonrpc":"2.0","method":"Group.OnStreamChanged","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream 1"}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
auto streamId = request->params().get<std::string>("stream_id");
|
||||||
|
PcmStreamPtr stream = getStreamManager().getStream(streamId);
|
||||||
|
if (stream == nullptr)
|
||||||
|
throw jsonrpcpp::InternalErrorException("Stream not found", request->id());
|
||||||
|
|
||||||
|
auto group = getGroup(request);
|
||||||
|
group->streamId = streamId;
|
||||||
|
|
||||||
|
// Update clients
|
||||||
|
for (const auto& client : group->clients)
|
||||||
|
{
|
||||||
|
session_ptr session = getStreamServer().getStreamSession(client->id);
|
||||||
|
if (session && (session->pcmStream() != stream))
|
||||||
|
{
|
||||||
|
// session->send(stream->getMeta());
|
||||||
|
session->send(stream->getHeader());
|
||||||
|
session->setPcmStream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify others
|
||||||
|
Json result;
|
||||||
|
result["stream_id"] = group->streamId;
|
||||||
|
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
auto notification = std::make_shared<jsonrpcpp::Notification>("Group.OnStreamChanged", jsonrpcpp::Parameter("id", group->id, "stream_id", group->streamId));
|
||||||
|
on_response(std::move(response), std::move(notification));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GroupSetClientsRequest::GroupSetClientsRequest(const Server& server) : GroupRequest(server, "Group.SetClients", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void GroupSetClientsRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":3,"jsonrpc":"2.0","method":"Group.SetClients","params":{"clients":["00:21:6a:7d:74:fc#2","00:21:6a:7d:74:fc"],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}}
|
||||||
|
// Response: {"id":3,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
||||||
|
// Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
std::vector<std::string> clients = request->params().get("clients");
|
||||||
|
|
||||||
|
// Remove clients from group
|
||||||
|
auto group = getGroup(request);
|
||||||
|
for (auto iter = group->clients.begin(); iter != group->clients.end();)
|
||||||
|
{
|
||||||
|
auto client = *iter;
|
||||||
|
if (find(clients.begin(), clients.end(), client->id) != clients.end())
|
||||||
|
{
|
||||||
|
++iter;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
iter = group->clients.erase(iter);
|
||||||
|
GroupPtr newGroup = Config::instance().addClientInfo(client);
|
||||||
|
newGroup->streamId = group->streamId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add clients to group
|
||||||
|
PcmStreamPtr stream = getStreamManager().getStream(group->streamId);
|
||||||
|
for (const auto& clientId : clients)
|
||||||
|
{
|
||||||
|
ClientInfoPtr client = Config::instance().getClientInfo(clientId);
|
||||||
|
if (!client)
|
||||||
|
continue;
|
||||||
|
GroupPtr oldGroup = Config::instance().getGroupFromClient(client);
|
||||||
|
if (oldGroup && (oldGroup->id == group->id))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (oldGroup)
|
||||||
|
{
|
||||||
|
oldGroup->removeClient(client);
|
||||||
|
Config::instance().remove(oldGroup);
|
||||||
|
}
|
||||||
|
|
||||||
|
group->addClient(client);
|
||||||
|
|
||||||
|
// assign new stream
|
||||||
|
session_ptr session = getStreamServer().getStreamSession(client->id);
|
||||||
|
if (session && stream && (session->pcmStream() != stream))
|
||||||
|
{
|
||||||
|
// session->send(stream->getMeta());
|
||||||
|
session->send(stream->getHeader());
|
||||||
|
session->setPcmStream(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (group->empty())
|
||||||
|
Config::instance().remove(group);
|
||||||
|
|
||||||
|
json server = Config::instance().getServerStatus(getStreamManager().toJson());
|
||||||
|
Json result;
|
||||||
|
result["server"] = server;
|
||||||
|
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
// Notify others: since at least two groups are affected, send a complete server update
|
||||||
|
auto notification = std::make_shared<jsonrpcpp::Notification>("Server.OnUpdate", jsonrpcpp::Parameter("server", server));
|
||||||
|
on_response(std::move(response), std::move(notification));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////// Stream requests /////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
StreamRequest::StreamRequest(const Server& server, const std::string& method, const std::string& ressource) : Request(server, method, ressource)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PcmStreamPtr StreamRequest::getStream(const StreamManager& stream_manager, const jsonrpcpp::request_ptr& request)
|
||||||
|
{
|
||||||
|
PcmStreamPtr stream = stream_manager.getStream(getStreamId(request));
|
||||||
|
if (stream == nullptr)
|
||||||
|
throw jsonrpcpp::InternalErrorException("Stream not found", request->id());
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string StreamRequest::getStreamId(const jsonrpcpp::request_ptr& request)
|
||||||
|
{
|
||||||
|
if (!request->params().has("id"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Parameter 'id' is missing", request->id());
|
||||||
|
|
||||||
|
return request->params().get<std::string>("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StreamControlRequest::StreamControlRequest(const Server& server) : StreamRequest(server, "Stream.Control", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamControlRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.Control","params":{"id":"Spotify", "command": "next", params: {}}}
|
||||||
|
// Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}}
|
||||||
|
//
|
||||||
|
// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.Control","params":{"id":"Spotify", "command": "seek", "param": "60000"}}
|
||||||
|
// Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
LOG(INFO, LOG_TAG) << "Stream.Control id: " << request->params().get<std::string>("id") << ", command: " << request->params().get("command")
|
||||||
|
<< ", params: " << (request->params().has("params") ? request->params().get("params") : "") << "\n";
|
||||||
|
|
||||||
|
// Find stream
|
||||||
|
PcmStreamPtr stream = getStream(getStreamManager(), request);
|
||||||
|
|
||||||
|
if (!request->params().has("command"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Parameter 'commmand' is missing", request->id());
|
||||||
|
|
||||||
|
auto command = request->params().get<std::string>("command");
|
||||||
|
|
||||||
|
auto handle_response = [request, on_response, command](const snapcast::ErrorCode& ec)
|
||||||
|
{
|
||||||
|
auto log_level = AixLog::Severity::debug;
|
||||||
|
if (ec)
|
||||||
|
log_level = AixLog::Severity::error;
|
||||||
|
LOG(log_level, LOG_TAG) << "Response to '" << command << "': " << ec << ", message: " << ec.detailed_message() << ", msg: " << ec.message()
|
||||||
|
<< ", category: " << ec.category().name() << "\n";
|
||||||
|
std::shared_ptr<jsonrpcpp::Response> response;
|
||||||
|
if (ec)
|
||||||
|
response = std::make_shared<jsonrpcpp::Response>(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
|
||||||
|
else
|
||||||
|
response = std::make_shared<jsonrpcpp::Response>(request->id(), "ok");
|
||||||
|
// LOG(DEBUG, LOG_TAG) << response->to_json().dump() << "\n";
|
||||||
|
on_response(std::move(response), nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (command == "setPosition")
|
||||||
|
{
|
||||||
|
if (!request->params().has("params") || !request->params().get("params").contains("position"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("setPosition requires parameter 'position'");
|
||||||
|
auto seconds = request->params().get("params")["position"].get<float>();
|
||||||
|
stream->setPosition(std::chrono::milliseconds(static_cast<int>(seconds * 1000)),
|
||||||
|
[handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
||||||
|
}
|
||||||
|
else if (command == "seek")
|
||||||
|
{
|
||||||
|
if (!request->params().has("params") || !request->params().get("params").contains("offset"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Seek requires parameter 'offset'");
|
||||||
|
auto offset = request->params().get("params")["offset"].get<float>();
|
||||||
|
stream->seek(std::chrono::milliseconds(static_cast<int>(offset * 1000)), [handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
||||||
|
}
|
||||||
|
else if (command == "next")
|
||||||
|
{
|
||||||
|
stream->next([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
||||||
|
}
|
||||||
|
else if (command == "previous")
|
||||||
|
{
|
||||||
|
stream->previous([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
||||||
|
}
|
||||||
|
else if (command == "pause")
|
||||||
|
{
|
||||||
|
stream->pause([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
||||||
|
}
|
||||||
|
else if (command == "playPause")
|
||||||
|
{
|
||||||
|
stream->playPause([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
||||||
|
}
|
||||||
|
else if (command == "stop")
|
||||||
|
{
|
||||||
|
stream->stop([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
||||||
|
}
|
||||||
|
else if (command == "play")
|
||||||
|
{
|
||||||
|
stream->play([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Command '" + command + "' not supported", request->id());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StreamSetPropertyRequest::StreamSetPropertyRequest(const Server& server) : StreamRequest(server, "Stream.SetProperty", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamSetPropertyRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
LOG(INFO, LOG_TAG) << "Stream.SetProperty id: " << request->params().get<std::string>("id") << ", property: " << request->params().get("property")
|
||||||
|
<< ", value: " << request->params().get("value") << "\n";
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
// Find stream
|
||||||
|
std::string streamId = getStreamId(request);
|
||||||
|
PcmStreamPtr stream = getStream(getStreamManager(), request);
|
||||||
|
|
||||||
|
if (!request->params().has("property"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Parameter 'property' is missing", request->id());
|
||||||
|
|
||||||
|
if (!request->params().has("value"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Parameter 'value' is missing", request->id());
|
||||||
|
|
||||||
|
auto name = request->params().get<std::string>("property");
|
||||||
|
auto value = request->params().get("value");
|
||||||
|
LOG(INFO, LOG_TAG) << "Stream '" << streamId << "' set property: " << name << " = " << value << "\n";
|
||||||
|
|
||||||
|
auto handle_response = [request, on_response](const std::string& command, const snapcast::ErrorCode& ec)
|
||||||
|
{
|
||||||
|
LOG(ERROR, LOG_TAG) << "Result for '" << command << "': " << ec << ", message: " << ec.detailed_message() << ", msg: " << ec.message()
|
||||||
|
<< ", category: " << ec.category().name() << "\n";
|
||||||
|
std::shared_ptr<jsonrpcpp::Response> response;
|
||||||
|
if (ec)
|
||||||
|
response = std::make_shared<jsonrpcpp::Response>(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
|
||||||
|
else
|
||||||
|
response = std::make_shared<jsonrpcpp::Response>(request->id(), "ok");
|
||||||
|
on_response(response, nullptr);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (name == "loopStatus")
|
||||||
|
{
|
||||||
|
auto val = value.get<std::string>();
|
||||||
|
LoopStatus loop_status = loop_status_from_string(val);
|
||||||
|
if (loop_status == LoopStatus::kUnknown)
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Value for loopStatus must be one of 'none', 'track', 'playlist'", request->id());
|
||||||
|
stream->setLoopStatus(loop_status, [handle_response, name](const snapcast::ErrorCode& ec) { handle_response(name, ec); });
|
||||||
|
}
|
||||||
|
else if (name == "shuffle")
|
||||||
|
{
|
||||||
|
if (!value.is_boolean())
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Value for shuffle must be bool", request->id());
|
||||||
|
stream->setShuffle(value.get<bool>(), [handle_response, name](const snapcast::ErrorCode& ec) { handle_response(name, ec); });
|
||||||
|
}
|
||||||
|
else if (name == "volume")
|
||||||
|
{
|
||||||
|
if (!value.is_number_integer())
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Value for volume must be an int", request->id());
|
||||||
|
stream->setVolume(value.get<int16_t>(), [handle_response, name](const snapcast::ErrorCode& ec) { handle_response(name, ec); });
|
||||||
|
}
|
||||||
|
else if (name == "mute")
|
||||||
|
{
|
||||||
|
if (!value.is_boolean())
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Value for mute must be bool", request->id());
|
||||||
|
stream->setMute(value.get<bool>(), [handle_response, name](const snapcast::ErrorCode& ec) { handle_response(name, ec); });
|
||||||
|
}
|
||||||
|
else if (name == "rate")
|
||||||
|
{
|
||||||
|
if (!value.is_number_float())
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Value for rate must be float", request->id());
|
||||||
|
stream->setRate(value.get<float>(), [handle_response, name](const snapcast::ErrorCode& ec) { handle_response(name, ec); });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Property '" + name + "' not supported", request->id());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StreamAddRequest::StreamAddRequest(const Server& server) : StreamRequest(server, "Stream.AddStream", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamAddRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.AddStream","params":{"streamUri":"uri"}}
|
||||||
|
// Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
LOG(INFO, LOG_TAG) << "Stream.AddStream(" << request->params().get("streamUri") << ")\n";
|
||||||
|
|
||||||
|
// Add stream
|
||||||
|
std::string streamUri = request->params().get("streamUri");
|
||||||
|
PcmStreamPtr stream = getStreamManager().addStream(streamUri);
|
||||||
|
if (stream == nullptr)
|
||||||
|
throw jsonrpcpp::InternalErrorException("Stream not created", request->id());
|
||||||
|
stream->start(); // We start the stream, otherwise it would be silent
|
||||||
|
|
||||||
|
// Setup response
|
||||||
|
Json result;
|
||||||
|
result["id"] = stream->getId();
|
||||||
|
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
on_response(std::move(response), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StreamRemoveRequest::StreamRemoveRequest(const Server& server) : StreamRequest(server, "Stream.RemoveStream", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamRemoveRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.RemoveStream","params":{"id":"Spotify"}}
|
||||||
|
// Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
LOG(INFO, LOG_TAG) << "Stream.RemoveStream(" << request->params().get("id") << ")\n";
|
||||||
|
|
||||||
|
// Find stream
|
||||||
|
std::string streamId = getStreamId(request);
|
||||||
|
getStreamManager().removeStream(streamId);
|
||||||
|
|
||||||
|
// Setup response
|
||||||
|
Json result;
|
||||||
|
result["id"] = streamId;
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
on_response(std::move(response), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////// Server requests /////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ServerGetRpcVersionRequest::ServerGetRpcVersionRequest(const Server& server) : Request(server, "Server.GetRPCVersion", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerGetRpcVersionRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":8,"jsonrpc":"2.0","method":"Server.GetRPCVersion"}
|
||||||
|
// Response: {"id":8,"jsonrpc":"2.0","result":{"major":2,"minor":0,"patch":0}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
Json result;
|
||||||
|
// <major>: backwards incompatible change
|
||||||
|
result["major"] = 23;
|
||||||
|
// <minor>: feature addition to the API
|
||||||
|
result["minor"] = 0;
|
||||||
|
// <patch>: bugfix release
|
||||||
|
result["patch"] = 0;
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
on_response(std::move(response), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ServerGetStatusRequest::ServerGetStatusRequest(const Server& server) : Request(server, "Server.GetStatus", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerGetStatusRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":1,"jsonrpc":"2.0","method":"Server.GetStatus"}
|
||||||
|
// Response: {"id":1,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025696,"usec":578142},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025696,"usec":611255},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
Json result;
|
||||||
|
result["server"] = Config::instance().getServerStatus(getStreamManager().toJson());
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
on_response(std::move(response), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
ServerDeleteClientRequest::ServerDeleteClientRequest(const Server& server) : Request(server, "Server.DeleteClient", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerDeleteClientRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":2,"jsonrpc":"2.0","method":"Server.DeleteClient","params":{"id":"00:21:6a:7d:74:fc"}}
|
||||||
|
// Response: {"id":2,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
||||||
|
// Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
std::ignore = authinfo;
|
||||||
|
ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get<std::string>("id"));
|
||||||
|
if (clientInfo == nullptr)
|
||||||
|
throw jsonrpcpp::InternalErrorException("Client not found", request->id());
|
||||||
|
|
||||||
|
Config::instance().remove(clientInfo);
|
||||||
|
|
||||||
|
json server = Config::instance().getServerStatus(getStreamManager().toJson());
|
||||||
|
Json result;
|
||||||
|
result["server"] = server;
|
||||||
|
|
||||||
|
auto response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
auto notification = std::make_shared<jsonrpcpp::Notification>("Server.OnUpdate", jsonrpcpp::Parameter("server", server));
|
||||||
|
on_response(std::move(response), std::move(notification));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ServerAuthenticateRequest::ServerAuthenticateRequest(const Server& server) : Request(server, "Server.Authenticate", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerAuthenticateRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":8,"jsonrpc":"2.0","method":"Server.Authenticate","params":{"scheme":"Basic","param":"YmFkYWl4OnNlY3JldA=="}}
|
||||||
|
// Response: {"id":8,"jsonrpc":"2.0","result":"ok"}
|
||||||
|
// Request: {"id":8,"jsonrpc":"2.0","method":"Server.Authenticate","params":{"scheme":"Bearer","param":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTg1NjQ1MTYsImlhdCI6MTcxODUyODUxNiwic3ViIjoiQmFkYWl4In0.gHrMVp7jTAg8aCSg3cttcfIxswqmOPuqVNOb5p79Cn0NmAqRmLXtDLX4QjOoOqqb66ezBBeikpNjPi_aO18YPoNmX9fPxSwcObTHBupnm5eugEpneMPDASFUSE2hg8rrD_OEoAVxx6hCLln7Z3ILyWDmR6jcmy7z0bp0BiAqOywUrFoVIsnlDZRs3wOaap5oS9J2oaA_gNi_7OuvAhrydn26LDhm0KiIqEcyIholkpRHrDYODkz98h2PkZdZ2U429tTvVhzDBJ1cBq2Zq3cvuMZT6qhwaUc8eYA8fUJ7g65iP4o2OZtUzlfEUqX1TKyuWuSK6CUlsZooNE-MSCT7_w"}}
|
||||||
|
// Response: {"id":8,"jsonrpc":"2.0","result":"ok"}
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
|
if (!request->params().has("scheme"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Parameter 'scheme' is missing", request->id());
|
||||||
|
if (!request->params().has("param"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Parameter 'param' is missing", request->id());
|
||||||
|
|
||||||
|
auto scheme = request->params().get<std::string>("scheme");
|
||||||
|
auto param = request->params().get<std::string>("param");
|
||||||
|
LOG(INFO, LOG_TAG) << "Authorization scheme: " << scheme << ", param: " << param << "\n";
|
||||||
|
auto ec = authinfo.authenticate(scheme, param);
|
||||||
|
|
||||||
|
std::shared_ptr<jsonrpcpp::Response> response;
|
||||||
|
if (ec)
|
||||||
|
response = std::make_shared<jsonrpcpp::Response>(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
|
||||||
|
else
|
||||||
|
response = std::make_shared<jsonrpcpp::Response>(request->id(), "ok");
|
||||||
|
// LOG(DEBUG, LOG_TAG) << response->to_json().dump() << "\n";
|
||||||
|
|
||||||
|
on_response(std::move(response), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ServerGetTokenRequest::ServerGetTokenRequest(const Server& server) : Request(server, "Server.GetToken", "xxx")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerGetTokenRequest::execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response)
|
||||||
|
{
|
||||||
|
// clang-format off
|
||||||
|
// Request: {"id":8,"jsonrpc":"2.0","method":"Server.GetToken","params":{"username":"Badaix","password":"secret"}}
|
||||||
|
// Response: {"id":8,"jsonrpc":"2.0","result":{"token":"<token>"}}
|
||||||
|
// clang-format on
|
||||||
|
if (!request->params().has("username"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Parameter 'username' is missing", request->id());
|
||||||
|
if (!request->params().has("password"))
|
||||||
|
throw jsonrpcpp::InvalidParamsException("Parameter 'password' is missing", request->id());
|
||||||
|
|
||||||
|
auto username = request->params().get<std::string>("username");
|
||||||
|
auto password = request->params().get<std::string>("password");
|
||||||
|
LOG(INFO, LOG_TAG) << "GetToken username: " << username << ", password: " << password << "\n";
|
||||||
|
auto token = authinfo.getToken(username, password);
|
||||||
|
|
||||||
|
std::shared_ptr<jsonrpcpp::Response> response;
|
||||||
|
if (token.hasError())
|
||||||
|
{
|
||||||
|
response = std::make_shared<jsonrpcpp::Response>(request->id(), jsonrpcpp::Error(token.getError().detailed_message(), token.getError().value()));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Json result;
|
||||||
|
result["token"] = token.takeValue();
|
||||||
|
response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
||||||
|
}
|
||||||
|
// LOG(DEBUG, LOG_TAG) << response->to_json().dump() << "\n";
|
||||||
|
|
||||||
|
on_response(std::move(response), nullptr);
|
||||||
|
}
|
322
server/control_requests.hpp
Normal file
322
server/control_requests.hpp
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
/***
|
||||||
|
This file is part of snapcast
|
||||||
|
Copyright (C) 2014-2024 Johannes Pohl
|
||||||
|
|
||||||
|
This program 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.
|
||||||
|
|
||||||
|
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
***/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
// local headers
|
||||||
|
#include "authinfo.hpp"
|
||||||
|
#include "config.hpp"
|
||||||
|
#include "jsonrpcpp.hpp"
|
||||||
|
#include "stream_server.hpp"
|
||||||
|
#include "streamreader/stream_manager.hpp"
|
||||||
|
|
||||||
|
// 3rd party headers
|
||||||
|
|
||||||
|
// standard headers
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Server;
|
||||||
|
|
||||||
|
/// Base class of a Snapserver control request
|
||||||
|
class Request
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// TODO: revise handler names
|
||||||
|
/// Response handler for json control requests, returning a @p response and/or a @p notification broadcast
|
||||||
|
using OnResponse = std::function<void(jsonrpcpp::entity_ptr response, jsonrpcpp::notification_ptr notification)>;
|
||||||
|
|
||||||
|
/// No default c'tor
|
||||||
|
Request() = delete;
|
||||||
|
|
||||||
|
/// c'tor
|
||||||
|
explicit Request(const Server& server, const std::string& method, const std::string& ressource);
|
||||||
|
|
||||||
|
/// d'tor
|
||||||
|
virtual ~Request() = default;
|
||||||
|
|
||||||
|
/// Execute the Request
|
||||||
|
virtual void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) = 0;
|
||||||
|
|
||||||
|
/// @return true if the user has the permission for the request
|
||||||
|
bool hasPermission(const AuthInfo& authinfo) const;
|
||||||
|
|
||||||
|
/// @return the name of the method
|
||||||
|
const std::string& method() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// @return the server's stream server
|
||||||
|
const StreamServer& getStreamServer() const;
|
||||||
|
/// @return the server's stream manager
|
||||||
|
const StreamManager& getStreamManager() const;
|
||||||
|
/// @return server settings
|
||||||
|
const ServerSettings& getSettings() const;
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// the server
|
||||||
|
const Server& server_;
|
||||||
|
/// the command
|
||||||
|
std::string method_;
|
||||||
|
/// the ressource
|
||||||
|
std::string ressource_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Control request factory
|
||||||
|
class ControlRequestFactory
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit ControlRequestFactory(const Server& server);
|
||||||
|
/// @return Request instance to handle @p method
|
||||||
|
std::shared_ptr<Request> getRequest(const std::string& method) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// storage for all available requests
|
||||||
|
std::map<std::string, std::shared_ptr<Request>> request_map_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Base for "Client." requests
|
||||||
|
class ClientRequest : public Request
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
ClientRequest(const Server& server, const std::string& method, const std::string& ressource);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// update the client that is referenced in the @p request
|
||||||
|
void updateClient(const jsonrpcpp::request_ptr& request);
|
||||||
|
|
||||||
|
/// @return the client referenced in the request
|
||||||
|
static ClientInfoPtr getClient(const jsonrpcpp::request_ptr& request);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Base for "Client.GetStatus" requests
|
||||||
|
class ClientGetStatusRequest : public ClientRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit ClientGetStatusRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Base for "Client.SetVolume" requests
|
||||||
|
class ClientSetVolumeRequest : public ClientRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit ClientSetVolumeRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Base for "Client.SetLatency" requests
|
||||||
|
class ClientSetLatencyRequest : public ClientRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit ClientSetLatencyRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// Base for "Client.SetName" requests
|
||||||
|
class ClientSetNameRequest : public ClientRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit ClientSetNameRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Base for "Group." requests
|
||||||
|
class GroupRequest : public Request
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
GroupRequest(const Server& server, const std::string& method, const std::string& ressource);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// @return the group referenced in the request
|
||||||
|
static GroupPtr getGroup(const jsonrpcpp::request_ptr& request);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Group.GetStatus" request
|
||||||
|
class GroupGetStatusRequest : public GroupRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit GroupGetStatusRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Group.SetName" request
|
||||||
|
class GroupSetNameRequest : public GroupRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit GroupSetNameRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Group.SetMute" request
|
||||||
|
class GroupSetMuteRequest : public GroupRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit GroupSetMuteRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Group.SetStream" request
|
||||||
|
class GroupSetStreamRequest : public GroupRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit GroupSetStreamRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Group.SetClients" request
|
||||||
|
class GroupSetClientsRequest : public GroupRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit GroupSetClientsRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// Base for "Stream." requests
|
||||||
|
class StreamRequest : public Request
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
StreamRequest(const Server& server, const std::string& method, const std::string& ressource);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/// @return the stream referenced in the request
|
||||||
|
static PcmStreamPtr getStream(const StreamManager& stream_manager, const jsonrpcpp::request_ptr& request);
|
||||||
|
/// @return the stream id referenced in the request
|
||||||
|
static std::string getStreamId(const jsonrpcpp::request_ptr& request);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Stream.Control" request
|
||||||
|
class StreamControlRequest : public StreamRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit StreamControlRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Stream.SetProperty" request
|
||||||
|
class StreamSetPropertyRequest : public StreamRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit StreamSetPropertyRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Stream.AddStream" request
|
||||||
|
class StreamAddRequest : public StreamRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit StreamAddRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Stream.RemoveStream" request
|
||||||
|
class StreamRemoveRequest : public StreamRequest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit StreamRemoveRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// "Server.GetRPCVersion" request
|
||||||
|
class ServerGetRpcVersionRequest : public Request
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit ServerGetRpcVersionRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Server.GetStatus" request
|
||||||
|
class ServerGetStatusRequest : public Request
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit ServerGetStatusRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Server.DeleteClient" request
|
||||||
|
class ServerDeleteClientRequest : public Request
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit ServerDeleteClientRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Server.Authenticate" request
|
||||||
|
class ServerAuthenticateRequest : public Request
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit ServerAuthenticateRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/// "Server.GetToken" request
|
||||||
|
class ServerGetTokenRequest : public Request
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// c'tor
|
||||||
|
explicit ServerGetTokenRequest(const Server& server);
|
||||||
|
void execute(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const OnResponse& on_response) override;
|
||||||
|
};
|
|
@ -21,13 +21,11 @@
|
||||||
|
|
||||||
// local headers
|
// local headers
|
||||||
#include "common/aixlog.hpp"
|
#include "common/aixlog.hpp"
|
||||||
#include "common/base64.h"
|
|
||||||
#include "common/jwt.hpp"
|
#include "common/jwt.hpp"
|
||||||
#include "common/message/client_info.hpp"
|
#include "common/message/client_info.hpp"
|
||||||
#include "common/message/hello.hpp"
|
#include "common/message/hello.hpp"
|
||||||
#include "common/message/server_settings.hpp"
|
#include "common/message/server_settings.hpp"
|
||||||
#include "common/message/time.hpp"
|
#include "common/message/time.hpp"
|
||||||
#include "common/utils/string_utils.hpp"
|
|
||||||
#include "config.hpp"
|
#include "config.hpp"
|
||||||
#include "jsonrpcpp.hpp"
|
#include "jsonrpcpp.hpp"
|
||||||
|
|
||||||
|
@ -36,6 +34,7 @@
|
||||||
// standard headers
|
// standard headers
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -45,15 +44,14 @@ using json = nlohmann::json;
|
||||||
|
|
||||||
static constexpr auto LOG_TAG = "Server";
|
static constexpr auto LOG_TAG = "Server";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Server::Server(boost::asio::io_context& io_context, const ServerSettings& serverSettings)
|
Server::Server(boost::asio::io_context& io_context, const ServerSettings& serverSettings)
|
||||||
: io_context_(io_context), config_timer_(io_context), settings_(serverSettings)
|
: io_context_(io_context), config_timer_(io_context), settings_(serverSettings), request_factory_(*this)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Server::~Server() = default;
|
|
||||||
|
|
||||||
|
|
||||||
void Server::onNewSession(std::shared_ptr<StreamSession> session)
|
void Server::onNewSession(std::shared_ptr<StreamSession> session)
|
||||||
{
|
{
|
||||||
LOG(DEBUG, LOG_TAG) << "onNewSession\n";
|
LOG(DEBUG, LOG_TAG) << "onNewSession\n";
|
||||||
|
@ -134,562 +132,34 @@ void Server::onDisconnect(StreamSession* streamSession)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Server::processRequest(const jsonrpcpp::request_ptr request, AuthInfo& authinfo, const OnResponse& on_response) const
|
void Server::processRequest(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const Request::OnResponse& on_response) const
|
||||||
{
|
{
|
||||||
jsonrpcpp::entity_ptr response = nullptr;
|
auto req = request_factory_.getRequest(request->method());
|
||||||
jsonrpcpp::notification_ptr notification;
|
if (req)
|
||||||
try
|
|
||||||
{
|
{
|
||||||
// LOG(INFO, LOG_TAG) << "Server::processRequest method: " << request->method << ", " << "id: " << request->id() << "\n";
|
try
|
||||||
Json result;
|
|
||||||
|
|
||||||
if (request->method().find("Client.") == 0)
|
|
||||||
{
|
{
|
||||||
ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get<std::string>("id"));
|
req->execute(request, authinfo, on_response);
|
||||||
if (clientInfo == nullptr)
|
|
||||||
throw jsonrpcpp::InternalErrorException("Client not found", request->id());
|
|
||||||
|
|
||||||
if (request->method() == "Client.GetStatus")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":8,"jsonrpc":"2.0","method":"Client.GetStatus","params":{"id":"00:21:6a:7d:74:fc"}}
|
|
||||||
// Response: {"id":8,"jsonrpc":"2.0","result":{"client":{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026416,"usec":135973},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}}}
|
|
||||||
// clang-format on
|
|
||||||
result["client"] = clientInfo->toJson();
|
|
||||||
}
|
|
||||||
else if (request->method() == "Client.SetVolume")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":8,"jsonrpc":"2.0","method":"Client.SetVolume","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":74}}}
|
|
||||||
// Response: {"id":8,"jsonrpc":"2.0","result":{"volume":{"muted":false,"percent":74}}}
|
|
||||||
// Notification: {"jsonrpc":"2.0","method":"Client.OnVolumeChanged","params":{"id":"00:21:6a:7d:74:fc","volume":{"muted":false,"percent":74}}}
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
clientInfo->config.volume.fromJson(request->params().get("volume"));
|
|
||||||
result["volume"] = clientInfo->config.volume.toJson();
|
|
||||||
notification = std::make_shared<jsonrpcpp::Notification>(
|
|
||||||
"Client.OnVolumeChanged", jsonrpcpp::Parameter("id", clientInfo->id, "volume", clientInfo->config.volume.toJson()));
|
|
||||||
}
|
|
||||||
else if (request->method() == "Client.SetLatency")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":7,"jsonrpc":"2.0","method":"Client.SetLatency","params":{"id":"00:21:6a:7d:74:fc#2","latency":10}}
|
|
||||||
// Response: {"id":7,"jsonrpc":"2.0","result":{"latency":10}}
|
|
||||||
// Notification: {"jsonrpc":"2.0","method":"Client.OnLatencyChanged","params":{"id":"00:21:6a:7d:74:fc#2","latency":10}}
|
|
||||||
// clang-format on
|
|
||||||
int latency = request->params().get("latency");
|
|
||||||
if (latency < -10000)
|
|
||||||
latency = -10000;
|
|
||||||
else if (latency > settings_.stream.bufferMs)
|
|
||||||
latency = settings_.stream.bufferMs;
|
|
||||||
clientInfo->config.latency = latency; //, -10000, settings_.stream.bufferMs);
|
|
||||||
result["latency"] = clientInfo->config.latency;
|
|
||||||
notification = std::make_shared<jsonrpcpp::Notification>("Client.OnLatencyChanged",
|
|
||||||
jsonrpcpp::Parameter("id", clientInfo->id, "latency", clientInfo->config.latency));
|
|
||||||
}
|
|
||||||
else if (request->method() == "Client.SetName")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":6,"jsonrpc":"2.0","method":"Client.SetName","params":{"id":"00:21:6a:7d:74:fc#2","name":"Laptop"}}
|
|
||||||
// Response: {"id":6,"jsonrpc":"2.0","result":{"name":"Laptop"}}
|
|
||||||
// Notification: {"jsonrpc":"2.0","method":"Client.OnNameChanged","params":{"id":"00:21:6a:7d:74:fc#2","name":"Laptop"}}
|
|
||||||
// clang-format on
|
|
||||||
clientInfo->config.name = request->params().get<std::string>("name");
|
|
||||||
result["name"] = clientInfo->config.name;
|
|
||||||
notification = std::make_shared<jsonrpcpp::Notification>("Client.OnNameChanged",
|
|
||||||
jsonrpcpp::Parameter("id", clientInfo->id, "name", clientInfo->config.name));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw jsonrpcpp::MethodNotFoundException(request->id());
|
|
||||||
|
|
||||||
|
|
||||||
if (request->method().find("Client.Set") == 0)
|
|
||||||
{
|
|
||||||
/// Update client
|
|
||||||
session_ptr session = streamServer_->getStreamSession(clientInfo->id);
|
|
||||||
if (session != nullptr)
|
|
||||||
{
|
|
||||||
auto serverSettings = make_shared<msg::ServerSettings>();
|
|
||||||
serverSettings->setBufferMs(settings_.stream.bufferMs);
|
|
||||||
serverSettings->setVolume(clientInfo->config.volume.percent);
|
|
||||||
GroupPtr group = Config::instance().getGroupFromClient(clientInfo);
|
|
||||||
serverSettings->setMuted(clientInfo->config.volume.muted || group->muted);
|
|
||||||
serverSettings->setLatency(clientInfo->config.latency);
|
|
||||||
session->send(serverSettings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (request->method().find("Group.") == 0)
|
catch (const jsonrpcpp::RequestException& e)
|
||||||
{
|
{
|
||||||
GroupPtr group = Config::instance().getGroup(request->params().get<std::string>("id"));
|
LOG(ERROR, LOG_TAG) << "Server::onMessageReceived JsonRequestException: " << e.to_json().dump() << ", message: " << request->to_json().dump()
|
||||||
if (group == nullptr)
|
<< "\n";
|
||||||
throw jsonrpcpp::InternalErrorException("Group not found", request->id());
|
auto response = std::make_shared<jsonrpcpp::RequestException>(e);
|
||||||
|
on_response(std::move(response), nullptr);
|
||||||
if (request->method() == "Group.GetStatus")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":5,"jsonrpc":"2.0","method":"Group.GetStatus","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}}
|
|
||||||
// Response: {"id":5,"jsonrpc":"2.0","result":{"group":{"clients":[{"config":{"instance":2,"latency":10,"name":"Laptop","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488026485,"usec":644997},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":74}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488026481,"usec":223747},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":true,"name":"","stream_id":"stream 1"}}}
|
|
||||||
// clang-format on
|
|
||||||
result["group"] = group->toJson();
|
|
||||||
}
|
|
||||||
else if (request->method() == "Group.SetName")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":6,"jsonrpc":"2.0","method":"Group.SetName","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","name":"Laptop"}}
|
|
||||||
// Response: {"id":6,"jsonrpc":"2.0","result":{"name":"MediaPlayer"}}
|
|
||||||
// Notification: {"jsonrpc":"2.0","method":"Group.OnNameChanged","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","MediaPlayer":"Laptop"}}
|
|
||||||
// clang-format on
|
|
||||||
group->name = request->params().get<std::string>("name");
|
|
||||||
result["name"] = group->name;
|
|
||||||
notification = std::make_shared<jsonrpcpp::Notification>("Group.OnNameChanged", jsonrpcpp::Parameter("id", group->id, "name", group->name));
|
|
||||||
}
|
|
||||||
else if (request->method() == "Group.SetMute")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":5,"jsonrpc":"2.0","method":"Group.SetMute","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","mute":true}}
|
|
||||||
// Response: {"id":5,"jsonrpc":"2.0","result":{"mute":true}}
|
|
||||||
// Notification: {"jsonrpc":"2.0","method":"Group.OnMute","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","mute":true}}
|
|
||||||
// clang-format on
|
|
||||||
bool muted = request->params().get<bool>("mute");
|
|
||||||
group->muted = muted;
|
|
||||||
|
|
||||||
/// Update clients
|
|
||||||
for (const auto& client : group->clients)
|
|
||||||
{
|
|
||||||
session_ptr session = streamServer_->getStreamSession(client->id);
|
|
||||||
if (session != nullptr)
|
|
||||||
{
|
|
||||||
auto serverSettings = make_shared<msg::ServerSettings>();
|
|
||||||
serverSettings->setBufferMs(settings_.stream.bufferMs);
|
|
||||||
serverSettings->setVolume(client->config.volume.percent);
|
|
||||||
GroupPtr group = Config::instance().getGroupFromClient(client);
|
|
||||||
serverSettings->setMuted(client->config.volume.muted || group->muted);
|
|
||||||
serverSettings->setLatency(client->config.latency);
|
|
||||||
session->send(serverSettings);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result["mute"] = group->muted;
|
|
||||||
notification = std::make_shared<jsonrpcpp::Notification>("Group.OnMute", jsonrpcpp::Parameter("id", group->id, "mute", group->muted));
|
|
||||||
}
|
|
||||||
else if (request->method() == "Group.SetStream")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":4,"jsonrpc":"2.0","method":"Group.SetStream","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream 1"}}
|
|
||||||
// Response: {"id":4,"jsonrpc":"2.0","result":{"stream_id":"stream 1"}}
|
|
||||||
// Notification: {"jsonrpc":"2.0","method":"Group.OnStreamChanged","params":{"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","stream_id":"stream 1"}}
|
|
||||||
// clang-format on
|
|
||||||
string streamId = request->params().get<std::string>("stream_id");
|
|
||||||
PcmStreamPtr stream = streamManager_->getStream(streamId);
|
|
||||||
if (stream == nullptr)
|
|
||||||
throw jsonrpcpp::InternalErrorException("Stream not found", request->id());
|
|
||||||
|
|
||||||
group->streamId = streamId;
|
|
||||||
|
|
||||||
// Update clients
|
|
||||||
for (const auto& client : group->clients)
|
|
||||||
{
|
|
||||||
session_ptr session = streamServer_->getStreamSession(client->id);
|
|
||||||
if (session && (session->pcmStream() != stream))
|
|
||||||
{
|
|
||||||
// session->send(stream->getMeta());
|
|
||||||
session->send(stream->getHeader());
|
|
||||||
session->setPcmStream(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify others
|
|
||||||
result["stream_id"] = group->streamId;
|
|
||||||
notification =
|
|
||||||
std::make_shared<jsonrpcpp::Notification>("Group.OnStreamChanged", jsonrpcpp::Parameter("id", group->id, "stream_id", group->streamId));
|
|
||||||
}
|
|
||||||
else if (request->method() == "Group.SetClients")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":3,"jsonrpc":"2.0","method":"Group.SetClients","params":{"clients":["00:21:6a:7d:74:fc#2","00:21:6a:7d:74:fc"],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1"}}
|
|
||||||
// Response: {"id":3,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
|
||||||
// Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025901,"usec":864472},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":100}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025905,"usec":45238},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
|
||||||
// clang-format on
|
|
||||||
vector<string> clients = request->params().get("clients");
|
|
||||||
// Remove clients from group
|
|
||||||
for (auto iter = group->clients.begin(); iter != group->clients.end();)
|
|
||||||
{
|
|
||||||
auto client = *iter;
|
|
||||||
if (find(clients.begin(), clients.end(), client->id) != clients.end())
|
|
||||||
{
|
|
||||||
++iter;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
iter = group->clients.erase(iter);
|
|
||||||
GroupPtr newGroup = Config::instance().addClientInfo(client);
|
|
||||||
newGroup->streamId = group->streamId;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add clients to group
|
|
||||||
PcmStreamPtr stream = streamManager_->getStream(group->streamId);
|
|
||||||
for (const auto& clientId : clients)
|
|
||||||
{
|
|
||||||
ClientInfoPtr client = Config::instance().getClientInfo(clientId);
|
|
||||||
if (!client)
|
|
||||||
continue;
|
|
||||||
GroupPtr oldGroup = Config::instance().getGroupFromClient(client);
|
|
||||||
if (oldGroup && (oldGroup->id == group->id))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (oldGroup)
|
|
||||||
{
|
|
||||||
oldGroup->removeClient(client);
|
|
||||||
Config::instance().remove(oldGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
group->addClient(client);
|
|
||||||
|
|
||||||
// assign new stream
|
|
||||||
session_ptr session = streamServer_->getStreamSession(client->id);
|
|
||||||
if (session && stream && (session->pcmStream() != stream))
|
|
||||||
{
|
|
||||||
// session->send(stream->getMeta());
|
|
||||||
session->send(stream->getHeader());
|
|
||||||
session->setPcmStream(stream);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (group->empty())
|
|
||||||
Config::instance().remove(group);
|
|
||||||
|
|
||||||
json server = Config::instance().getServerStatus(streamManager_->toJson());
|
|
||||||
result["server"] = server;
|
|
||||||
|
|
||||||
// Notify others: since at least two groups are affected, send a complete server update
|
|
||||||
notification = std::make_shared<jsonrpcpp::Notification>("Server.OnUpdate", jsonrpcpp::Parameter("server", server));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw jsonrpcpp::MethodNotFoundException(request->id());
|
|
||||||
}
|
}
|
||||||
else if (request->method().find("Server.") == 0)
|
catch (const exception& e)
|
||||||
{
|
{
|
||||||
if (request->method().find("Server.GetRPCVersion") == 0)
|
LOG(ERROR, LOG_TAG) << "Server::onMessageReceived exception: " << e.what() << ", message: " << request->to_json().dump() << "\n";
|
||||||
{
|
auto response = std::make_shared<jsonrpcpp::InternalErrorException>(e.what(), request->id());
|
||||||
// Request: {"id":8,"jsonrpc":"2.0","method":"Server.GetRPCVersion"}
|
on_response(std::move(response), nullptr);
|
||||||
// Response: {"id":8,"jsonrpc":"2.0","result":{"major":2,"minor":0,"patch":0}}
|
|
||||||
// <major>: backwards incompatible change
|
|
||||||
result["major"] = 2;
|
|
||||||
// <minor>: feature addition to the API
|
|
||||||
result["minor"] = 0;
|
|
||||||
// <patch>: bugfix release
|
|
||||||
result["patch"] = 0;
|
|
||||||
}
|
|
||||||
else if (request->method() == "Server.GetStatus")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":1,"jsonrpc":"2.0","method":"Server.GetStatus"}
|
|
||||||
// Response: {"id":1,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025696,"usec":578142},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}},{"config":{"instance":1,"latency":0,"name":"","volume":{"muted":false,"percent":81}},"connected":true,"host":{"arch":"x86_64","ip":"192.168.0.54","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc","lastSeen":{"sec":1488025696,"usec":611255},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
|
||||||
// clang-format on
|
|
||||||
result["server"] = Config::instance().getServerStatus(streamManager_->toJson());
|
|
||||||
}
|
|
||||||
else if (request->method() == "Server.DeleteClient")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":2,"jsonrpc":"2.0","method":"Server.DeleteClient","params":{"id":"00:21:6a:7d:74:fc"}}
|
|
||||||
// Response: {"id":2,"jsonrpc":"2.0","result":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
|
||||||
// Notification: {"jsonrpc":"2.0","method":"Server.OnUpdate","params":{"server":{"groups":[{"clients":[{"config":{"instance":2,"latency":6,"name":"123 456","volume":{"muted":false,"percent":48}},"connected":true,"host":{"arch":"x86_64","ip":"127.0.0.1","mac":"00:21:6a:7d:74:fc","name":"T400","os":"Linux Mint 17.3 Rosa"},"id":"00:21:6a:7d:74:fc#2","lastSeen":{"sec":1488025751,"usec":654777},"snapclient":{"name":"Snapclient","protocolVersion":2,"version":"0.10.0"}}],"id":"4dcc4e3b-c699-a04b-7f0c-8260d23c43e1","muted":false,"name":"","stream_id":"stream 2"}],"server":{"host":{"arch":"x86_64","ip":"","mac":"","name":"T400","os":"Linux Mint 17.3 Rosa"},"snapserver":{"controlProtocolVersion":1,"name":"Snapserver","protocolVersion":1,"version":"0.10.0"}},"streams":[{"id":"stream 1","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 1","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 1","scheme":"pipe"}},{"id":"stream 2","status":"idle","uri":{"fragment":"","host":"","path":"/tmp/snapfifo","query":{"chunk_ms":"20","codec":"flac","name":"stream 2","sampleformat":"48000:16:2"},"raw":"pipe:///tmp/snapfifo?name=stream 2","scheme":"pipe"}}]}}}
|
|
||||||
// clang-format on
|
|
||||||
ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get<std::string>("id"));
|
|
||||||
if (clientInfo == nullptr)
|
|
||||||
throw jsonrpcpp::InternalErrorException("Client not found", request->id());
|
|
||||||
|
|
||||||
Config::instance().remove(clientInfo);
|
|
||||||
|
|
||||||
json server = Config::instance().getServerStatus(streamManager_->toJson());
|
|
||||||
result["server"] = server;
|
|
||||||
|
|
||||||
/// Notify others
|
|
||||||
notification = std::make_shared<jsonrpcpp::Notification>("Server.OnUpdate", jsonrpcpp::Parameter("server", server));
|
|
||||||
}
|
|
||||||
else if (request->method() == "Server.Authenticate")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":8,"jsonrpc":"2.0","method":"Server.Authenticate","params":{"scheme":"Basic","param":"YmFkYWl4OnNlY3JldA=="}}
|
|
||||||
// Response: {"id":8,"jsonrpc":"2.0","result":"ok"}
|
|
||||||
// Request: {"id":8,"jsonrpc":"2.0","method":"Server.Authenticate","params":{"scheme":"Bearer","param":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3MTg1NjQ1MTYsImlhdCI6MTcxODUyODUxNiwic3ViIjoiQmFkYWl4In0.gHrMVp7jTAg8aCSg3cttcfIxswqmOPuqVNOb5p79Cn0NmAqRmLXtDLX4QjOoOqqb66ezBBeikpNjPi_aO18YPoNmX9fPxSwcObTHBupnm5eugEpneMPDASFUSE2hg8rrD_OEoAVxx6hCLln7Z3ILyWDmR6jcmy7z0bp0BiAqOywUrFoVIsnlDZRs3wOaap5oS9J2oaA_gNi_7OuvAhrydn26LDhm0KiIqEcyIholkpRHrDYODkz98h2PkZdZ2U429tTvVhzDBJ1cBq2Zq3cvuMZT6qhwaUc8eYA8fUJ7g65iP4o2OZtUzlfEUqX1TKyuWuSK6CUlsZooNE-MSCT7_w"}}
|
|
||||||
// Response: {"id":8,"jsonrpc":"2.0","result":"ok"}
|
|
||||||
// clang-format on
|
|
||||||
if (!request->params().has("scheme"))
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Parameter 'scheme' is missing", request->id());
|
|
||||||
if (!request->params().has("param"))
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Parameter 'param' is missing", request->id());
|
|
||||||
|
|
||||||
auto scheme = request->params().get<std::string>("scheme");
|
|
||||||
auto param = request->params().get<std::string>("param");
|
|
||||||
LOG(INFO, LOG_TAG) << "Authorization scheme: " << scheme << ", param: " << param << "\n";
|
|
||||||
auto ec = authinfo.authenticate(scheme, param);
|
|
||||||
|
|
||||||
if (ec)
|
|
||||||
response = make_shared<jsonrpcpp::Response>(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
|
|
||||||
else
|
|
||||||
response = make_shared<jsonrpcpp::Response>(request->id(), "ok");
|
|
||||||
// LOG(DEBUG, LOG_TAG) << response->to_json().dump() << "\n";
|
|
||||||
}
|
|
||||||
else if (request->method() == "Server.GetToken")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":8,"jsonrpc":"2.0","method":"Server.GetToken","params":{"username":"Badaix","password":"secret"}}
|
|
||||||
// Response: {"id":8,"jsonrpc":"2.0","result":{"token":"<token>"}}
|
|
||||||
// clang-format on
|
|
||||||
if (!request->params().has("username"))
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Parameter 'username' is missing", request->id());
|
|
||||||
if (!request->params().has("password"))
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Parameter 'password' is missing", request->id());
|
|
||||||
|
|
||||||
auto username = request->params().get<std::string>("username");
|
|
||||||
auto password = request->params().get<std::string>("password");
|
|
||||||
LOG(INFO, LOG_TAG) << "GetToken username: " << username << ", password: " << password << "\n";
|
|
||||||
auto token = authinfo.getToken(username, password);
|
|
||||||
|
|
||||||
if (token.hasError())
|
|
||||||
response = make_shared<jsonrpcpp::Response>(request->id(), jsonrpcpp::Error(token.getError().detailed_message(), token.getError().value()));
|
|
||||||
else
|
|
||||||
result["token"] = token.takeValue();
|
|
||||||
// LOG(DEBUG, LOG_TAG) << response->to_json().dump() << "\n";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw jsonrpcpp::MethodNotFoundException(request->id());
|
|
||||||
}
|
}
|
||||||
else if (request->method().find("Stream.") == 0)
|
|
||||||
{
|
|
||||||
// if (request->method().find("Stream.SetMeta") == 0)
|
|
||||||
// {
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.SetMeta","params":{"id":"Spotify", "metadata": {"album": "some album", "artist": "some artist", "track": "some track"...}}}
|
|
||||||
// Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}}
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
// LOG(INFO, LOG_TAG) << "Stream.SetMeta id: " << request->params().get<std::string>("id") << ", meta: " << request->params().get("metadata") <<
|
|
||||||
// "\n";
|
|
||||||
|
|
||||||
// // Find stream
|
|
||||||
// string streamId = request->params().get<std::string>("id");
|
|
||||||
// PcmStreamPtr stream = streamManager_->getStream(streamId);
|
|
||||||
// if (stream == nullptr)
|
|
||||||
// throw jsonrpcpp::InternalErrorException("Stream not found", request->id());
|
|
||||||
|
|
||||||
// // Set metadata from request
|
|
||||||
// stream->setMetadata(request->params().get("metadata"));
|
|
||||||
|
|
||||||
// // Setup response
|
|
||||||
// result["id"] = streamId;
|
|
||||||
// }
|
|
||||||
if (request->method().find("Stream.Control") == 0)
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.Control","params":{"id":"Spotify", "command": "next", params: {}}}
|
|
||||||
// Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}}
|
|
||||||
//
|
|
||||||
// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.Control","params":{"id":"Spotify", "command": "seek", "param": "60000"}}
|
|
||||||
// Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}}
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
LOG(INFO, LOG_TAG) << "Stream.Control id: " << request->params().get<std::string>("id") << ", command: " << request->params().get("command")
|
|
||||||
<< ", params: " << (request->params().has("params") ? request->params().get("params") : "") << "\n";
|
|
||||||
|
|
||||||
// Find stream
|
|
||||||
auto streamId = request->params().get<std::string>("id");
|
|
||||||
PcmStreamPtr stream = streamManager_->getStream(streamId);
|
|
||||||
if (stream == nullptr)
|
|
||||||
throw jsonrpcpp::InternalErrorException("Stream not found", request->id());
|
|
||||||
|
|
||||||
if (!request->params().has("command"))
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Parameter 'commmand' is missing", request->id());
|
|
||||||
|
|
||||||
auto command = request->params().get<string>("command");
|
|
||||||
|
|
||||||
auto handle_response = [request, on_response, command](const snapcast::ErrorCode& ec)
|
|
||||||
{
|
|
||||||
auto log_level = AixLog::Severity::debug;
|
|
||||||
if (ec)
|
|
||||||
log_level = AixLog::Severity::error;
|
|
||||||
LOG(log_level, LOG_TAG) << "Response to '" << command << "': " << ec << ", message: " << ec.detailed_message() << ", msg: " << ec.message()
|
|
||||||
<< ", category: " << ec.category().name() << "\n";
|
|
||||||
std::shared_ptr<jsonrpcpp::Response> response;
|
|
||||||
if (ec)
|
|
||||||
response = make_shared<jsonrpcpp::Response>(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
|
|
||||||
else
|
|
||||||
response = make_shared<jsonrpcpp::Response>(request->id(), "ok");
|
|
||||||
// LOG(DEBUG, LOG_TAG) << response->to_json().dump() << "\n";
|
|
||||||
on_response(response, nullptr);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (command == "setPosition")
|
|
||||||
{
|
|
||||||
if (!request->params().has("params") || !request->params().get("params").contains("position"))
|
|
||||||
throw jsonrpcpp::InvalidParamsException("setPosition requires parameter 'position'");
|
|
||||||
auto seconds = request->params().get("params")["position"].get<float>();
|
|
||||||
stream->setPosition(std::chrono::milliseconds(static_cast<int>(seconds * 1000)),
|
|
||||||
[handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
|
||||||
}
|
|
||||||
else if (command == "seek")
|
|
||||||
{
|
|
||||||
if (!request->params().has("params") || !request->params().get("params").contains("offset"))
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Seek requires parameter 'offset'");
|
|
||||||
auto offset = request->params().get("params")["offset"].get<float>();
|
|
||||||
stream->seek(std::chrono::milliseconds(static_cast<int>(offset * 1000)),
|
|
||||||
[handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
|
||||||
}
|
|
||||||
else if (command == "next")
|
|
||||||
{
|
|
||||||
stream->next([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
|
||||||
}
|
|
||||||
else if (command == "previous")
|
|
||||||
{
|
|
||||||
stream->previous([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
|
||||||
}
|
|
||||||
else if (command == "pause")
|
|
||||||
{
|
|
||||||
stream->pause([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
|
||||||
}
|
|
||||||
else if (command == "playPause")
|
|
||||||
{
|
|
||||||
stream->playPause([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
|
||||||
}
|
|
||||||
else if (command == "stop")
|
|
||||||
{
|
|
||||||
stream->stop([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
|
||||||
}
|
|
||||||
else if (command == "play")
|
|
||||||
{
|
|
||||||
stream->play([handle_response](const snapcast::ErrorCode& ec) { handle_response(ec); });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Command '" + command + "' not supported", request->id());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (request->method().find("Stream.SetProperty") == 0)
|
|
||||||
{
|
|
||||||
LOG(INFO, LOG_TAG) << "Stream.SetProperty id: " << request->params().get<std::string>("id")
|
|
||||||
<< ", property: " << request->params().get("property") << ", value: " << request->params().get("value") << "\n";
|
|
||||||
|
|
||||||
// Find stream
|
|
||||||
string streamId = request->params().get<std::string>("id");
|
|
||||||
PcmStreamPtr stream = streamManager_->getStream(streamId);
|
|
||||||
if (stream == nullptr)
|
|
||||||
throw jsonrpcpp::InternalErrorException("Stream not found", request->id());
|
|
||||||
|
|
||||||
if (!request->params().has("property"))
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Parameter 'property' is missing", request->id());
|
|
||||||
|
|
||||||
if (!request->params().has("value"))
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Parameter 'value' is missing", request->id());
|
|
||||||
|
|
||||||
auto name = request->params().get<string>("property");
|
|
||||||
auto value = request->params().get("value");
|
|
||||||
LOG(INFO, LOG_TAG) << "Stream '" << streamId << "' set property: " << name << " = " << value << "\n";
|
|
||||||
|
|
||||||
auto handle_response = [request, on_response](const std::string& command, const snapcast::ErrorCode& ec)
|
|
||||||
{
|
|
||||||
LOG(ERROR, LOG_TAG) << "Result for '" << command << "': " << ec << ", message: " << ec.detailed_message() << ", msg: " << ec.message()
|
|
||||||
<< ", category: " << ec.category().name() << "\n";
|
|
||||||
std::shared_ptr<jsonrpcpp::Response> response;
|
|
||||||
if (ec)
|
|
||||||
response = make_shared<jsonrpcpp::Response>(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
|
|
||||||
else
|
|
||||||
response = make_shared<jsonrpcpp::Response>(request->id(), "ok");
|
|
||||||
on_response(response, nullptr);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (name == "loopStatus")
|
|
||||||
{
|
|
||||||
auto val = value.get<std::string>();
|
|
||||||
LoopStatus loop_status = loop_status_from_string(val);
|
|
||||||
if (loop_status == LoopStatus::kUnknown)
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Value for loopStatus must be one of 'none', 'track', 'playlist'", request->id());
|
|
||||||
stream->setLoopStatus(loop_status, [handle_response, name](const snapcast::ErrorCode& ec) { handle_response(name, ec); });
|
|
||||||
}
|
|
||||||
else if (name == "shuffle")
|
|
||||||
{
|
|
||||||
if (!value.is_boolean())
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Value for shuffle must be bool", request->id());
|
|
||||||
stream->setShuffle(value.get<bool>(), [handle_response, name](const snapcast::ErrorCode& ec) { handle_response(name, ec); });
|
|
||||||
}
|
|
||||||
else if (name == "volume")
|
|
||||||
{
|
|
||||||
if (!value.is_number_integer())
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Value for volume must be an int", request->id());
|
|
||||||
stream->setVolume(value.get<int16_t>(), [handle_response, name](const snapcast::ErrorCode& ec) { handle_response(name, ec); });
|
|
||||||
}
|
|
||||||
else if (name == "mute")
|
|
||||||
{
|
|
||||||
if (!value.is_boolean())
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Value for mute must be bool", request->id());
|
|
||||||
stream->setMute(value.get<bool>(), [handle_response, name](const snapcast::ErrorCode& ec) { handle_response(name, ec); });
|
|
||||||
}
|
|
||||||
else if (name == "rate")
|
|
||||||
{
|
|
||||||
if (!value.is_number_float())
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Value for rate must be float", request->id());
|
|
||||||
stream->setRate(value.get<float>(), [handle_response, name](const snapcast::ErrorCode& ec) { handle_response(name, ec); });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw jsonrpcpp::InvalidParamsException("Property '" + name + "' not supported", request->id());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else if (request->method() == "Stream.AddStream")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.AddStream","params":{"streamUri":"uri"}}
|
|
||||||
// Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}}
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
LOG(INFO, LOG_TAG) << "Stream.AddStream(" << request->params().get("streamUri") << ")"
|
|
||||||
<< "\n";
|
|
||||||
|
|
||||||
// Find stream
|
|
||||||
string streamUri = request->params().get("streamUri");
|
|
||||||
PcmStreamPtr stream = streamManager_->addStream(streamUri);
|
|
||||||
if (stream == nullptr)
|
|
||||||
throw jsonrpcpp::InternalErrorException("Stream not created", request->id());
|
|
||||||
stream->start(); // We start the stream, otherwise it would be silent
|
|
||||||
// Setup response
|
|
||||||
result["id"] = stream->getId();
|
|
||||||
}
|
|
||||||
else if (request->method() == "Stream.RemoveStream")
|
|
||||||
{
|
|
||||||
// clang-format off
|
|
||||||
// Request: {"id":4,"jsonrpc":"2.0","method":"Stream.RemoveStream","params":{"id":"Spotify"}}
|
|
||||||
// Response: {"id":4,"jsonrpc":"2.0","result":{"id":"Spotify"}}
|
|
||||||
// clang-format on
|
|
||||||
|
|
||||||
LOG(INFO, LOG_TAG) << "Stream.RemoveStream(" << request->params().get("id") << ")"
|
|
||||||
<< "\n";
|
|
||||||
|
|
||||||
// Find stream
|
|
||||||
string streamId = request->params().get("id");
|
|
||||||
streamManager_->removeStream(streamId);
|
|
||||||
// Setup response
|
|
||||||
result["id"] = streamId;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw jsonrpcpp::MethodNotFoundException(request->id());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
throw jsonrpcpp::MethodNotFoundException(request->id());
|
|
||||||
|
|
||||||
if (!response)
|
|
||||||
response = std::make_shared<jsonrpcpp::Response>(*request, result);
|
|
||||||
}
|
}
|
||||||
catch (const jsonrpcpp::RequestException& e)
|
else
|
||||||
{
|
{
|
||||||
LOG(ERROR, LOG_TAG) << "Server::onMessageReceived JsonRequestException: " << e.to_json().dump() << ", message: " << request->to_json().dump() << "\n";
|
LOG(ERROR, LOG_TAG) << "Method not found: " << request->method() << "\n";
|
||||||
response = std::make_shared<jsonrpcpp::RequestException>(e);
|
throw jsonrpcpp::MethodNotFoundException(request->id());
|
||||||
}
|
}
|
||||||
catch (const exception& e)
|
|
||||||
{
|
|
||||||
LOG(ERROR, LOG_TAG) << "Server::onMessageReceived exception: " << e.what() << ", message: " << request->to_json().dump() << "\n";
|
|
||||||
response = std::make_shared<jsonrpcpp::InternalErrorException>(e.what(), request->id());
|
|
||||||
}
|
|
||||||
on_response(std::move(response), std::move(notification));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "authinfo.hpp"
|
#include "authinfo.hpp"
|
||||||
#include "common/message/message.hpp"
|
#include "common/message/message.hpp"
|
||||||
#include "common/queue.hpp"
|
#include "common/queue.hpp"
|
||||||
|
#include "control_requests.hpp"
|
||||||
#include "control_server.hpp"
|
#include "control_server.hpp"
|
||||||
#include "jsonrpcpp.hpp"
|
#include "jsonrpcpp.hpp"
|
||||||
#include "server_settings.hpp"
|
#include "server_settings.hpp"
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
#include "stream_session.hpp"
|
#include "stream_session.hpp"
|
||||||
#include "streamreader/stream_manager.hpp"
|
#include "streamreader/stream_manager.hpp"
|
||||||
|
|
||||||
|
|
||||||
// 3rd party headers
|
// 3rd party headers
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
#include <boost/asio/steady_timer.hpp>
|
#include <boost/asio/steady_timer.hpp>
|
||||||
|
@ -38,9 +40,6 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
|
||||||
using namespace streamreader;
|
|
||||||
|
|
||||||
using boost::asio::ip::tcp;
|
|
||||||
using acceptor_ptr = std::unique_ptr<tcp::acceptor>;
|
using acceptor_ptr = std::unique_ptr<tcp::acceptor>;
|
||||||
using session_ptr = std::shared_ptr<StreamSession>;
|
using session_ptr = std::shared_ptr<StreamSession>;
|
||||||
|
|
||||||
|
@ -51,14 +50,13 @@ using session_ptr = std::shared_ptr<StreamSession>;
|
||||||
*/
|
*/
|
||||||
class Server : public StreamMessageReceiver, public ControlMessageReceiver, public PcmStream::Listener
|
class Server : public StreamMessageReceiver, public ControlMessageReceiver, public PcmStream::Listener
|
||||||
{
|
{
|
||||||
public:
|
friend class Request;
|
||||||
// TODO: revise handler names
|
|
||||||
/// Response handler for json control requests, returning a @p response and/or a @p notification broadcast
|
|
||||||
using OnResponse = std::function<void(jsonrpcpp::entity_ptr response, jsonrpcpp::notification_ptr notification)>;
|
|
||||||
|
|
||||||
|
public:
|
||||||
/// c'tor
|
/// c'tor
|
||||||
Server(boost::asio::io_context& io_context, const ServerSettings& serverSettings);
|
Server(boost::asio::io_context& io_context, const ServerSettings& serverSettings);
|
||||||
virtual ~Server();
|
/// d'tor
|
||||||
|
virtual ~Server() = default;
|
||||||
|
|
||||||
/// Start the server (control server, stream server and stream manager)
|
/// Start the server (control server, stream server and stream manager)
|
||||||
void start();
|
void start();
|
||||||
|
@ -86,7 +84,7 @@ private:
|
||||||
void onResync(const PcmStream* pcmStream, double ms) override;
|
void onResync(const PcmStream* pcmStream, double ms) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void processRequest(const jsonrpcpp::request_ptr request, AuthInfo& authinfo, const OnResponse& on_response) const;
|
void processRequest(const jsonrpcpp::request_ptr& request, AuthInfo& authinfo, const Request::OnResponse& on_response) const;
|
||||||
/// Save the server state deferred to prevent blocking and lower disk io
|
/// Save the server state deferred to prevent blocking and lower disk io
|
||||||
/// @param deferred the delay after the last call to saveConfig
|
/// @param deferred the delay after the last call to saveConfig
|
||||||
void saveConfig(const std::chrono::milliseconds& deferred = std::chrono::seconds(2));
|
void saveConfig(const std::chrono::milliseconds& deferred = std::chrono::seconds(2));
|
||||||
|
@ -99,4 +97,5 @@ private:
|
||||||
std::unique_ptr<ControlServer> controlServer_;
|
std::unique_ptr<ControlServer> controlServer_;
|
||||||
std::unique_ptr<StreamServer> streamServer_;
|
std::unique_ptr<StreamServer> streamServer_;
|
||||||
std::unique_ptr<StreamManager> streamManager_;
|
std::unique_ptr<StreamManager> streamManager_;
|
||||||
|
ControlRequestFactory request_factory_;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue