mirror of
https://github.com/badaix/snapcast.git
synced 2025-04-28 17:57:05 +02:00
Add client system info message
This commit is contained in:
parent
142afabad4
commit
c6a8f5d888
9 changed files with 210 additions and 2 deletions
|
@ -24,6 +24,7 @@
|
|||
|
||||
// standard headers
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
|
||||
|
||||
|
@ -73,10 +74,24 @@ struct ClientSettings
|
|||
std::string filter{"*:info"};
|
||||
};
|
||||
|
||||
struct SystemInfo
|
||||
{
|
||||
enum class Mode
|
||||
{
|
||||
file,
|
||||
script,
|
||||
none
|
||||
};
|
||||
Mode mode{Mode::none};
|
||||
std::string path;
|
||||
int interval_secs;
|
||||
};
|
||||
|
||||
size_t instance{1};
|
||||
std::string host_id;
|
||||
|
||||
Server server;
|
||||
Player player;
|
||||
Logging logging;
|
||||
SystemInfo systemInfo;
|
||||
};
|
||||
|
|
|
@ -64,20 +64,25 @@
|
|||
#include "common/snap_exception.hpp"
|
||||
#include "time_provider.hpp"
|
||||
|
||||
#include <boost/process.hpp>
|
||||
|
||||
// standard headers
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std;
|
||||
using namespace player;
|
||||
|
||||
namespace bp = boost::process;
|
||||
|
||||
static constexpr auto LOG_TAG = "Controller";
|
||||
static constexpr auto TIME_SYNC_INTERVAL = 1s;
|
||||
|
||||
Controller::Controller(boost::asio::io_context& io_context, const ClientSettings& settings) //, std::unique_ptr<MetadataAdapter> meta)
|
||||
: io_context_(io_context), timer_(io_context), settings_(settings), stream_(nullptr), decoder_(nullptr), player_(nullptr),
|
||||
: io_context_(io_context), timer_(io_context), systemInfoTimer_(io_context), settings_(settings), stream_(nullptr), decoder_(nullptr), player_(nullptr),
|
||||
serverSettings_(nullptr) // meta_(std::move(meta)),
|
||||
{
|
||||
}
|
||||
|
@ -304,6 +309,90 @@ void Controller::sendTimeSyncMessage(int quick_syncs)
|
|||
});
|
||||
}
|
||||
|
||||
void Controller::sendSystemInfoMessage()
|
||||
{
|
||||
auto data = getSystemInfo();
|
||||
if (data != nullptr)
|
||||
{
|
||||
auto sysInfo = std::make_shared<msg::ClientSystemInfo>();
|
||||
sysInfo->msg = data;
|
||||
|
||||
LOG(TRACE, LOG_TAG) << "Sending system info: " << sysInfo->msg.dump() << "\n";
|
||||
clientConnection_->send(sysInfo,
|
||||
[this](const boost::system::error_code& ec)
|
||||
{
|
||||
if (ec)
|
||||
{
|
||||
LOG(ERROR, LOG_TAG) << "Failed to send client system info, error: " << ec.message() << "\n";
|
||||
reconnect();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
auto interval = std::chrono::seconds(settings_.systemInfo.interval_secs);
|
||||
systemInfoTimer_.expires_after(interval);
|
||||
systemInfoTimer_.async_wait(
|
||||
[this](const boost::system::error_code& ec)
|
||||
{
|
||||
if (!ec)
|
||||
{
|
||||
sendSystemInfoMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
json Controller::getSystemInfo()
|
||||
{
|
||||
|
||||
if (settings_.systemInfo.mode == ClientSettings::SystemInfo::Mode::script)
|
||||
{
|
||||
try
|
||||
{
|
||||
bp::ipstream istream;
|
||||
auto ret = bp::system(settings_.systemInfo.path, bp::std_out > istream);
|
||||
if (ret != 0)
|
||||
{
|
||||
LOG(ERROR, LOG_TAG)
|
||||
<< "System info process returned with exit code "
|
||||
<< ret << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
return json::parse(istream);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
LOG(ERROR, LOG_TAG)
|
||||
<< "Unable to read system info from process ("
|
||||
<< settings_.systemInfo.path
|
||||
<< "): "
|
||||
<< e.what() << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (settings_.systemInfo.mode == ClientSettings::SystemInfo::Mode::file)
|
||||
{
|
||||
try
|
||||
{
|
||||
ifstream fJson(settings_.systemInfo.path);
|
||||
stringstream buffer;
|
||||
buffer << fJson.rdbuf();
|
||||
return json::parse(buffer.str());
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
LOG(ERROR, LOG_TAG)
|
||||
<< "Unable to read system info file ("
|
||||
<< settings_.systemInfo.path
|
||||
<< "): "
|
||||
<< e.what() << std::endl;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void Controller::browseMdns(const MdnsHandler& handler)
|
||||
{
|
||||
#if defined(HAS_AVAHI) || defined(HAS_BONJOUR)
|
||||
|
@ -382,6 +471,7 @@ void Controller::start()
|
|||
void Controller::reconnect()
|
||||
{
|
||||
timer_.cancel();
|
||||
systemInfoTimer_.cancel();
|
||||
clientConnection_->disconnect();
|
||||
player_.reset();
|
||||
stream_.reset();
|
||||
|
@ -431,6 +521,11 @@ void Controller::worker()
|
|||
|
||||
// Do initial time sync with the server
|
||||
sendTimeSyncMessage(50);
|
||||
// start system info updates, if configured
|
||||
if (settings_.systemInfo.mode != ClientSettings::SystemInfo::Mode::none)
|
||||
{
|
||||
sendSystemInfoMessage();
|
||||
}
|
||||
// Start receiver loop
|
||||
getNextMessage();
|
||||
}
|
||||
|
|
|
@ -59,9 +59,12 @@ private:
|
|||
|
||||
void getNextMessage();
|
||||
void sendTimeSyncMessage(int quick_syncs);
|
||||
void sendSystemInfoMessage();
|
||||
json getSystemInfo();
|
||||
|
||||
boost::asio::io_context& io_context_;
|
||||
boost::asio::steady_timer timer_;
|
||||
boost::asio::steady_timer systemInfoTimer_;
|
||||
ClientSettings settings_;
|
||||
SampleFormat sampleFormat_;
|
||||
std::unique_ptr<ClientConnection> clientConnection_;
|
||||
|
|
|
@ -179,6 +179,10 @@ int main(int argc, char** argv)
|
|||
#endif
|
||||
mixer_mode = op.add<Value<string>>("", "mixer", mixers + "|none|?[:<options>]", "software");
|
||||
|
||||
// system info
|
||||
auto sysInfo = op.add<Value<string>>("", "sysinfo", "source for JSON-formatted system infomation [file:<filename>,script:<executable>]", "none");
|
||||
op.add<Value<int>>("", "sysinfo-interval", "interval in seconds between system info updates", 10, &settings.systemInfo.interval_secs);
|
||||
|
||||
// daemon settings
|
||||
#ifdef HAS_DAEMON
|
||||
int processPriority(-3);
|
||||
|
@ -427,6 +431,22 @@ int main(int argc, char** argv)
|
|||
else
|
||||
throw SnapException("Mixer mode not supported: " + mode);
|
||||
|
||||
string sysInfoMode = utils::string::split_left(sysInfo->value(), ':', settings.systemInfo.path);
|
||||
if (sysInfoMode == "none")
|
||||
settings.systemInfo.mode = ClientSettings::SystemInfo::Mode::none;
|
||||
else if (sysInfoMode == "file")
|
||||
settings.systemInfo.mode = ClientSettings::SystemInfo::Mode::file;
|
||||
else if (sysInfoMode == "script")
|
||||
settings.systemInfo.mode = ClientSettings::SystemInfo::Mode::script;
|
||||
else
|
||||
throw SnapException("System info mode not supported: " + sysInfoMode);
|
||||
|
||||
if (settings.systemInfo.interval_secs < 1)
|
||||
{
|
||||
LOG(ERROR, LOG_TAG) << "System info interval too low, setting to default value (10 seconds)\n";
|
||||
settings.systemInfo.interval_secs = 10;
|
||||
}
|
||||
|
||||
boost::asio::io_context io_context;
|
||||
// Construct a signal set registered for process termination.
|
||||
boost::asio::signal_set signals(io_context, SIGHUP, SIGINT, SIGTERM);
|
||||
|
|
45
common/message/client_system_info.hpp
Normal file
45
common/message/client_system_info.hpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
/***
|
||||
This file is part of snapcast
|
||||
Copyright (C) 2014-2022 Johannes Pohl
|
||||
Copyright (C) 2024 Marcus Weseloh
|
||||
|
||||
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/>.
|
||||
***/
|
||||
|
||||
#ifndef MESSAGE_CLIENT_SYSTEM_INFO_HPP
|
||||
#define MESSAGE_CLIENT_SYSTEM_INFO_HPP
|
||||
|
||||
// local headers
|
||||
#include "json_message.hpp"
|
||||
|
||||
|
||||
namespace msg
|
||||
{
|
||||
|
||||
// Client system information sent from client to server.
|
||||
// This message can contrain arbitrary JSON data.
|
||||
class ClientSystemInfo : public JsonMessage
|
||||
{
|
||||
public:
|
||||
ClientSystemInfo() : JsonMessage(message_type::kClientSystemInfo)
|
||||
{
|
||||
}
|
||||
|
||||
~ClientSystemInfo() override = default;
|
||||
};
|
||||
} // namespace msg
|
||||
|
||||
|
||||
#endif
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
#pragma once
|
||||
|
||||
// local headers
|
||||
#include "client_system_info.hpp"
|
||||
#include "client_info.hpp"
|
||||
#include "codec_header.hpp"
|
||||
#include "hello.hpp"
|
||||
|
@ -76,6 +77,8 @@ static std::unique_ptr<BaseMessage> createMessage(const BaseMessage& base_messag
|
|||
return createMessage<PcmChunk>(base_message, buffer);
|
||||
case message_type::kClientInfo:
|
||||
return createMessage<ClientInfo>(base_message, buffer);
|
||||
case message_type::kClientSystemInfo:
|
||||
return createMessage<ClientSystemInfo>(base_message, buffer);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -63,9 +63,10 @@ enum class message_type : uint16_t
|
|||
kHello = 5,
|
||||
// kStreamTags = 6,
|
||||
kClientInfo = 7,
|
||||
kClientSystemInfo = 8,
|
||||
|
||||
kFirst = kBase,
|
||||
kLast = kClientInfo
|
||||
kLast = kClientSystemInfo
|
||||
};
|
||||
|
||||
static std::ostream& operator<<(std::ostream& os, const message_type& msg_type)
|
||||
|
@ -93,6 +94,9 @@ static std::ostream& operator<<(std::ostream& os, const message_type& msg_type)
|
|||
case message_type::kClientInfo:
|
||||
os << "ClientInfo";
|
||||
break;
|
||||
case message_type::kClientSystemInfo:
|
||||
os << "ClientSystemInfo";
|
||||
break;
|
||||
default:
|
||||
os << "Unknown";
|
||||
}
|
||||
|
|
|
@ -235,6 +235,10 @@ struct ClientInfo
|
|||
lastSeen.tv_sec = jGet<int32_t>(j["lastSeen"], "sec", 0);
|
||||
lastSeen.tv_usec = jGet<int32_t>(j["lastSeen"], "usec", 0);
|
||||
connected = jGet<bool>(j, "connected", true);
|
||||
if (j.contains("systemInfo"))
|
||||
{
|
||||
systemInfo = j["systemInfo"].template get<json>();
|
||||
}
|
||||
}
|
||||
|
||||
json toJson()
|
||||
|
@ -247,6 +251,7 @@ struct ClientInfo
|
|||
j["lastSeen"]["sec"] = lastSeen.tv_sec;
|
||||
j["lastSeen"]["usec"] = lastSeen.tv_usec;
|
||||
j["connected"] = connected;
|
||||
j["systemInfo"] = systemInfo;
|
||||
return j;
|
||||
}
|
||||
|
||||
|
@ -256,6 +261,7 @@ struct ClientInfo
|
|||
ClientConfig config;
|
||||
timeval lastSeen;
|
||||
bool connected;
|
||||
json systemInfo;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
// local headers
|
||||
#include "common/aixlog.hpp"
|
||||
#include "common/message/client_info.hpp"
|
||||
#include "common/message/client_system_info.hpp"
|
||||
#include "common/message/hello.hpp"
|
||||
#include "common/message/server_settings.hpp"
|
||||
#include "common/message/time.hpp"
|
||||
|
@ -763,6 +764,22 @@ void Server::onMessageReceived(StreamSession* streamSession, const msg::BaseMess
|
|||
"Client.OnVolumeChanged", jsonrpcpp::Parameter("id", streamSession->clientId, "volume", clientInfo->config.volume.toJson()));
|
||||
controlServer_->send(notification->to_json().dump());
|
||||
}
|
||||
else if (baseMessage.type == message_type::kClientSystemInfo)
|
||||
{
|
||||
ClientInfoPtr clientInfo = Config::instance().getClientInfo(streamSession->clientId);
|
||||
if (clientInfo == nullptr)
|
||||
{
|
||||
LOG(ERROR, LOG_TAG) << "client not found: " << streamSession->clientId << "\n";
|
||||
return;
|
||||
}
|
||||
msg::ClientSystemInfo sysInfoMsg;
|
||||
sysInfoMsg.deserialize(baseMessage, buffer);
|
||||
|
||||
clientInfo->systemInfo = sysInfoMsg.msg;
|
||||
jsonrpcpp::notification_ptr notification = make_shared<jsonrpcpp::Notification>(
|
||||
"Client.OnSystemInfoChanged", jsonrpcpp::Parameter("id", streamSession->clientId, "systemInfo", clientInfo->systemInfo));
|
||||
controlServer_->send(notification->to_json().dump());
|
||||
}
|
||||
else if (baseMessage.type == message_type::kHello)
|
||||
{
|
||||
msg::Hello helloMsg;
|
||||
|
|
Loading…
Add table
Reference in a new issue