diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt
index 539805fd..19f2ce99 100644
--- a/server/CMakeLists.txt
+++ b/server/CMakeLists.txt
@@ -2,6 +2,7 @@ set(SERVER_SOURCES
authinfo.cpp
config.cpp
control_server.cpp
+ control_requests.cpp
control_session_tcp.cpp
control_session_http.cpp
control_session_ws.cpp
diff --git a/server/control_requests.cpp b/server/control_requests.cpp
new file mode 100644
index 00000000..6601ea75
--- /dev/null
+++ b/server/control_requests.cpp
@@ -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 .
+***/
+
+// 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
+
+
+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_map_[request->method()] = std::move(request); };
+
+ // Client requests
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+
+ // Group requests
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+
+ // Stream requests
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+
+ // Server requests
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+ add_request(std::make_shared(server));
+}
+
+
+std::shared_ptr 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(server);
+ // else if (method == "Server.GetStatus")
+ // return std::make_unique(server);
+ // else if (method == "Server.DeleteClient")
+ // return std::make_unique(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("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();
+ 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(*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(*request, result);
+ auto notification = std::make_shared("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(*request, result);
+ auto notification = std::make_shared("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("name");
+ Json result;
+ result["name"] = client_info->config.name;
+
+ auto response = std::make_shared(*request, result);
+ auto notification =
+ std::make_shared("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("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(*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("name");
+ result["name"] = group->name;
+
+ auto response = std::make_shared(*request, result);
+ auto notification = std::make_shared("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("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();
+ 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(*request, result);
+ auto notification = std::make_shared("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("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(*request, result);
+ auto notification = std::make_shared("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 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(*request, result);
+ // Notify others: since at least two groups are affected, send a complete server update
+ auto notification = std::make_shared("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("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("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("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 response;
+ if (ec)
+ response = std::make_shared(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
+ else
+ response = std::make_shared(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();
+ stream->setPosition(std::chrono::milliseconds(static_cast(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();
+ stream->seek(std::chrono::milliseconds(static_cast(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("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("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 response;
+ if (ec)
+ response = std::make_shared(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
+ else
+ response = std::make_shared(request->id(), "ok");
+ on_response(response, nullptr);
+ };
+
+ if (name == "loopStatus")
+ {
+ auto val = value.get();
+ 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(), [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(), [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(), [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(), [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(*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(*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;
+ // : backwards incompatible change
+ result["major"] = 23;
+ // : feature addition to the API
+ result["minor"] = 0;
+ // : bugfix release
+ result["patch"] = 0;
+ auto response = std::make_shared(*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(*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("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(*request, result);
+ auto notification = std::make_shared("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("scheme");
+ auto param = request->params().get("param");
+ LOG(INFO, LOG_TAG) << "Authorization scheme: " << scheme << ", param: " << param << "\n";
+ auto ec = authinfo.authenticate(scheme, param);
+
+ std::shared_ptr response;
+ if (ec)
+ response = std::make_shared(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
+ else
+ response = std::make_shared(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":""}}
+ // 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("username");
+ auto password = request->params().get("password");
+ LOG(INFO, LOG_TAG) << "GetToken username: " << username << ", password: " << password << "\n";
+ auto token = authinfo.getToken(username, password);
+
+ std::shared_ptr response;
+ if (token.hasError())
+ {
+ response = std::make_shared(request->id(), jsonrpcpp::Error(token.getError().detailed_message(), token.getError().value()));
+ }
+ else
+ {
+ Json result;
+ result["token"] = token.takeValue();
+ response = std::make_shared(*request, result);
+ }
+ // LOG(DEBUG, LOG_TAG) << response->to_json().dump() << "\n";
+
+ on_response(std::move(response), nullptr);
+}
diff --git a/server/control_requests.hpp b/server/control_requests.hpp
new file mode 100644
index 00000000..570cd9ef
--- /dev/null
+++ b/server/control_requests.hpp
@@ -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 .
+***/
+
+#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
+#include
+
+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;
+
+ /// 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 getRequest(const std::string& method) const;
+
+private:
+ /// storage for all available requests
+ std::map> 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;
+};
diff --git a/server/server.cpp b/server/server.cpp
index dcbc54a0..d2b02cea 100644
--- a/server/server.cpp
+++ b/server/server.cpp
@@ -21,13 +21,11 @@
// local headers
#include "common/aixlog.hpp"
-#include "common/base64.h"
#include "common/jwt.hpp"
#include "common/message/client_info.hpp"
#include "common/message/hello.hpp"
#include "common/message/server_settings.hpp"
#include "common/message/time.hpp"
-#include "common/utils/string_utils.hpp"
#include "config.hpp"
#include "jsonrpcpp.hpp"
@@ -36,6 +34,7 @@
// standard headers
#include
#include
+#include
using namespace std;
@@ -45,15 +44,14 @@ using json = nlohmann::json;
static constexpr auto LOG_TAG = "Server";
+
+
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 session)
{
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;
- jsonrpcpp::notification_ptr notification;
- try
+ auto req = request_factory_.getRequest(request->method());
+ if (req)
{
- // LOG(INFO, LOG_TAG) << "Server::processRequest method: " << request->method << ", " << "id: " << request->id() << "\n";
- Json result;
-
- if (request->method().find("Client.") == 0)
+ try
{
- ClientInfoPtr clientInfo = Config::instance().getClientInfo(request->params().get("id"));
- 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(
- "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("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("name");
- result["name"] = clientInfo->config.name;
- notification = std::make_shared("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();
- 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);
- }
- }
+ req->execute(request, authinfo, on_response);
}
- else if (request->method().find("Group.") == 0)
+ catch (const jsonrpcpp::RequestException& e)
{
- GroupPtr group = Config::instance().getGroup(request->params().get("id"));
- if (group == nullptr)
- throw jsonrpcpp::InternalErrorException("Group not found", request->id());
-
- 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("name");
- result["name"] = group->name;
- notification = std::make_shared("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("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();
- 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("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("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("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 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("Server.OnUpdate", jsonrpcpp::Parameter("server", server));
- }
- else
- throw jsonrpcpp::MethodNotFoundException(request->id());
+ LOG(ERROR, LOG_TAG) << "Server::onMessageReceived JsonRequestException: " << e.to_json().dump() << ", message: " << request->to_json().dump()
+ << "\n";
+ auto response = std::make_shared(e);
+ on_response(std::move(response), nullptr);
}
- else if (request->method().find("Server.") == 0)
+ catch (const exception& e)
{
- if (request->method().find("Server.GetRPCVersion") == 0)
- {
- // Request: {"id":8,"jsonrpc":"2.0","method":"Server.GetRPCVersion"}
- // Response: {"id":8,"jsonrpc":"2.0","result":{"major":2,"minor":0,"patch":0}}
- // : backwards incompatible change
- result["major"] = 2;
- // : feature addition to the API
- result["minor"] = 0;
- // : 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("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("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("scheme");
- auto param = request->params().get("param");
- LOG(INFO, LOG_TAG) << "Authorization scheme: " << scheme << ", param: " << param << "\n";
- auto ec = authinfo.authenticate(scheme, param);
-
- if (ec)
- response = make_shared(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
- else
- response = make_shared(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":""}}
- // 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("username");
- auto password = request->params().get("password");
- LOG(INFO, LOG_TAG) << "GetToken username: " << username << ", password: " << password << "\n";
- auto token = authinfo.getToken(username, password);
-
- if (token.hasError())
- response = make_shared(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());
+ LOG(ERROR, LOG_TAG) << "Server::onMessageReceived exception: " << e.what() << ", message: " << request->to_json().dump() << "\n";
+ auto response = std::make_shared(e.what(), request->id());
+ on_response(std::move(response), nullptr);
}
- 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("id") << ", meta: " << request->params().get("metadata") <<
- // "\n";
-
- // // Find stream
- // string streamId = request->params().get("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("id") << ", command: " << request->params().get("command")
- << ", params: " << (request->params().has("params") ? request->params().get("params") : "") << "\n";
-
- // Find stream
- auto streamId = request->params().get("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("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 response;
- if (ec)
- response = make_shared(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
- else
- response = make_shared(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();
- stream->setPosition(std::chrono::milliseconds(static_cast(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();
- stream->seek(std::chrono::milliseconds(static_cast(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("id")
- << ", property: " << request->params().get("property") << ", value: " << request->params().get("value") << "\n";
-
- // Find stream
- string streamId = request->params().get("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("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 response;
- if (ec)
- response = make_shared(request->id(), jsonrpcpp::Error(ec.detailed_message(), ec.value()));
- else
- response = make_shared(request->id(), "ok");
- on_response(response, nullptr);
- };
-
- if (name == "loopStatus")
- {
- auto val = value.get();
- 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(), [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(), [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(), [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(), [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(*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";
- response = std::make_shared(e);
+ LOG(ERROR, LOG_TAG) << "Method not found: " << request->method() << "\n";
+ 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(e.what(), request->id());
- }
- on_response(std::move(response), std::move(notification));
}
diff --git a/server/server.hpp b/server/server.hpp
index 9e23a417..767c1718 100644
--- a/server/server.hpp
+++ b/server/server.hpp
@@ -23,6 +23,7 @@
#include "authinfo.hpp"
#include "common/message/message.hpp"
#include "common/queue.hpp"
+#include "control_requests.hpp"
#include "control_server.hpp"
#include "jsonrpcpp.hpp"
#include "server_settings.hpp"
@@ -30,6 +31,7 @@
#include "stream_session.hpp"
#include "streamreader/stream_manager.hpp"
+
// 3rd party headers
#include
#include
@@ -38,9 +40,6 @@
#include
-using namespace streamreader;
-
-using boost::asio::ip::tcp;
using acceptor_ptr = std::unique_ptr;
using session_ptr = std::shared_ptr;
@@ -51,14 +50,13 @@ using session_ptr = std::shared_ptr;
*/
class Server : public StreamMessageReceiver, public ControlMessageReceiver, public PcmStream::Listener
{
-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;
+ friend class Request;
+public:
/// c'tor
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)
void start();
@@ -86,7 +84,7 @@ private:
void onResync(const PcmStream* pcmStream, double ms) override;
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
/// @param deferred the delay after the last call to saveConfig
void saveConfig(const std::chrono::milliseconds& deferred = std::chrono::seconds(2));
@@ -99,4 +97,5 @@ private:
std::unique_ptr controlServer_;
std::unique_ptr streamServer_;
std::unique_ptr streamManager_;
+ ControlRequestFactory request_factory_;
};