mirror of
https://github.com/badaix/snapcast.git
synced 2025-04-29 10:17:16 +02:00
458 lines
12 KiB
C++
458 lines
12 KiB
C++
/***
|
|
This file is part of snapcast
|
|
Copyright (C) 2014-2025 Johannes Pohl
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
***/
|
|
|
|
#pragma once
|
|
|
|
|
|
// local headers
|
|
#include "common/json.hpp"
|
|
#include "common/utils.hpp"
|
|
#include "common/utils/string_utils.hpp"
|
|
|
|
// standard headers
|
|
#include <memory>
|
|
#include <mutex>
|
|
#include <string>
|
|
#include <sys/time.h>
|
|
#include <vector>
|
|
|
|
|
|
namespace strutils = utils::string;
|
|
using json = nlohmann::json;
|
|
|
|
struct ClientInfo;
|
|
struct Group;
|
|
|
|
using ClientInfoPtr = std::shared_ptr<ClientInfo>;
|
|
using GroupPtr = std::shared_ptr<Group>;
|
|
|
|
|
|
/// Config item base class
|
|
struct JsonConfigItem
|
|
{
|
|
/// Read config item from json object @p j
|
|
virtual void fromJson(const json& j) = 0;
|
|
/// @return config item serialized to json
|
|
virtual json toJson() = 0;
|
|
|
|
/// d'tor
|
|
virtual ~JsonConfigItem() = default;
|
|
|
|
protected:
|
|
/// @return value for key @p what or @p def, if not found. Result is casted to T.
|
|
template <typename T>
|
|
T jGet(const json& j, const std::string& what, const T& def)
|
|
{
|
|
try
|
|
{
|
|
if (!j.count(what))
|
|
return def;
|
|
return j[what].get<T>();
|
|
}
|
|
catch (...)
|
|
{
|
|
return def;
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Volume config
|
|
struct Volume : public JsonConfigItem
|
|
{
|
|
/// c'tor
|
|
explicit Volume(uint16_t _percent = 100, bool _muted = false) : percent(_percent), muted(_muted)
|
|
{
|
|
}
|
|
|
|
void fromJson(const json& j) override
|
|
{
|
|
percent = jGet<uint16_t>(j, "percent", percent);
|
|
muted = jGet<bool>(j, "muted", muted);
|
|
}
|
|
|
|
json toJson() override
|
|
{
|
|
json j;
|
|
j["percent"] = percent;
|
|
j["muted"] = muted;
|
|
return j;
|
|
}
|
|
|
|
uint16_t percent; ///< volume in percent
|
|
bool muted; ///< muted
|
|
};
|
|
|
|
|
|
/// Host config
|
|
struct Host : public JsonConfigItem
|
|
{
|
|
/// c'tor
|
|
Host() = default;
|
|
|
|
/// Update name, os and arch to actual values
|
|
void update()
|
|
{
|
|
name = getHostName();
|
|
os = getOS();
|
|
arch = getArch();
|
|
}
|
|
|
|
void fromJson(const json& j) override
|
|
{
|
|
name = strutils::trim_copy(jGet<std::string>(j, "name", ""));
|
|
mac = strutils::trim_copy(jGet<std::string>(j, "mac", ""));
|
|
os = strutils::trim_copy(jGet<std::string>(j, "os", ""));
|
|
arch = strutils::trim_copy(jGet<std::string>(j, "arch", ""));
|
|
ip = strutils::trim_copy(jGet<std::string>(j, "ip", ""));
|
|
}
|
|
|
|
json toJson() override
|
|
{
|
|
json j;
|
|
j["name"] = name;
|
|
j["mac"] = mac;
|
|
j["os"] = os;
|
|
j["arch"] = arch;
|
|
j["ip"] = ip;
|
|
return j;
|
|
}
|
|
|
|
std::string name; ///< host name
|
|
std::string mac; ///< mac address
|
|
std::string os; ///< OS
|
|
std::string arch; ///< CPU architecture
|
|
std::string ip; ///< IP address
|
|
};
|
|
|
|
|
|
/// Client config
|
|
struct ClientConfig : public JsonConfigItem
|
|
{
|
|
ClientConfig() : volume(100)
|
|
{
|
|
}
|
|
|
|
void fromJson(const json& j) override
|
|
{
|
|
name = strutils::trim_copy(jGet<std::string>(j, "name", ""));
|
|
volume.fromJson(j["volume"]);
|
|
latency = jGet<int32_t>(j, "latency", 0);
|
|
instance = jGet<size_t>(j, "instance", 1);
|
|
}
|
|
|
|
json toJson() override
|
|
{
|
|
json j;
|
|
j["name"] = strutils::trim_copy(name);
|
|
j["volume"] = volume.toJson();
|
|
j["latency"] = latency;
|
|
j["instance"] = instance;
|
|
return j;
|
|
}
|
|
|
|
std::string name; ///< client name
|
|
Volume volume; ///< client volume
|
|
int32_t latency{0}; ///< additional latency
|
|
size_t instance{1}; ///< instance id
|
|
};
|
|
|
|
|
|
/// Snapcast base config
|
|
struct Snapcast : public JsonConfigItem
|
|
{
|
|
/// c'tor
|
|
explicit Snapcast(std::string _name = "", std::string _version = "") : name(std::move(_name)), version(std::move(_version))
|
|
{
|
|
}
|
|
|
|
virtual ~Snapcast() = default;
|
|
|
|
void fromJson(const json& j) override
|
|
{
|
|
name = strutils::trim_copy(jGet<std::string>(j, "name", ""));
|
|
version = strutils::trim_copy(jGet<std::string>(j, "version", ""));
|
|
protocolVersion = jGet<int>(j, "protocolVersion", 1);
|
|
}
|
|
|
|
json toJson() override
|
|
{
|
|
json j;
|
|
j["name"] = strutils::trim_copy(name);
|
|
j["version"] = strutils::trim_copy(version);
|
|
j["protocolVersion"] = protocolVersion;
|
|
return j;
|
|
}
|
|
|
|
std::string name; ///< name of the client or server
|
|
std::string version; ///< software version
|
|
int protocolVersion{1}; ///< binary protocol version
|
|
};
|
|
|
|
|
|
/// Snapclient config
|
|
struct Snapclient : public Snapcast
|
|
{
|
|
explicit Snapclient(std::string _name = "", std::string _version = "") : Snapcast(std::move(_name), std::move(_version))
|
|
{
|
|
}
|
|
};
|
|
|
|
|
|
/// Snapserver config
|
|
struct Snapserver : public Snapcast
|
|
{
|
|
/// c'tor
|
|
explicit Snapserver(std::string _name = "", std::string _version = "") : Snapcast(std::move(_name), std::move(_version))
|
|
{
|
|
}
|
|
|
|
void fromJson(const json& j) override
|
|
{
|
|
Snapcast::fromJson(j);
|
|
controlProtocolVersion = jGet<int>(j, "controlProtocolVersion", 1);
|
|
}
|
|
|
|
json toJson() override
|
|
{
|
|
json j = Snapcast::toJson();
|
|
j["controlProtocolVersion"] = controlProtocolVersion;
|
|
return j;
|
|
}
|
|
|
|
int controlProtocolVersion{1}; ///< control protocol version
|
|
};
|
|
|
|
|
|
/// Client config
|
|
struct ClientInfo : public JsonConfigItem
|
|
{
|
|
/// c'tor
|
|
explicit ClientInfo(std::string _clientId = "") : id(std::move(_clientId))
|
|
{
|
|
lastSeen.tv_sec = 0;
|
|
lastSeen.tv_usec = 0;
|
|
}
|
|
|
|
void fromJson(const json& j) override
|
|
{
|
|
host.fromJson(j["host"]);
|
|
id = jGet<std::string>(j, "id", host.mac);
|
|
snapclient.fromJson(j["snapclient"]);
|
|
config.fromJson(j["config"]);
|
|
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);
|
|
}
|
|
|
|
json toJson() override
|
|
{
|
|
json j;
|
|
j["id"] = id;
|
|
j["host"] = host.toJson();
|
|
j["snapclient"] = snapclient.toJson();
|
|
j["config"] = config.toJson();
|
|
j["lastSeen"]["sec"] = lastSeen.tv_sec;
|
|
j["lastSeen"]["usec"] = lastSeen.tv_usec;
|
|
j["connected"] = connected;
|
|
return j;
|
|
}
|
|
|
|
std::string id; ///< unique client id
|
|
Host host; ///< host
|
|
Snapclient snapclient; ///< Snapclient
|
|
ClientConfig config; ///< Client config
|
|
timeval lastSeen; ///< last online
|
|
bool connected{false}; ///< connected to server?
|
|
};
|
|
|
|
|
|
/// Group config
|
|
struct Group : public JsonConfigItem
|
|
{
|
|
/// c'tor
|
|
explicit Group(const ClientInfoPtr& client = nullptr)
|
|
{
|
|
if (client)
|
|
id = client->id;
|
|
id = generateUUID();
|
|
}
|
|
|
|
void fromJson(const json& j) override
|
|
{
|
|
name = strutils::trim_copy(jGet<std::string>(j, "name", ""));
|
|
id = strutils::trim_copy(jGet<std::string>(j, "id", ""));
|
|
streamId = strutils::trim_copy(jGet<std::string>(j, "stream_id", ""));
|
|
muted = jGet<bool>(j, "muted", false);
|
|
clients.clear();
|
|
if (j.count("clients"))
|
|
{
|
|
for (const auto& jClient : j["clients"])
|
|
{
|
|
ClientInfoPtr client = std::make_shared<ClientInfo>();
|
|
client->fromJson(jClient);
|
|
client->connected = false;
|
|
addClient(client);
|
|
}
|
|
}
|
|
}
|
|
|
|
json toJson() override
|
|
{
|
|
json j;
|
|
j["name"] = strutils::trim_copy(name);
|
|
j["id"] = strutils::trim_copy(id);
|
|
j["stream_id"] = strutils::trim_copy(streamId);
|
|
j["muted"] = muted;
|
|
|
|
json jClients = json::array();
|
|
for (const auto& client : clients)
|
|
jClients.push_back(client->toJson());
|
|
j["clients"] = jClients;
|
|
return j;
|
|
}
|
|
|
|
/// Remove client with id @p client_id from the group and @return the removed client
|
|
ClientInfoPtr removeClient(const std::string& client_id)
|
|
{
|
|
for (auto iter = clients.begin(); iter != clients.end(); ++iter)
|
|
{
|
|
if ((*iter)->id == client_id)
|
|
{
|
|
clients.erase(iter);
|
|
return (*iter);
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/// Remove client @p client from the group and @return the removed client
|
|
ClientInfoPtr removeClient(const ClientInfoPtr& client)
|
|
{
|
|
if (!client)
|
|
return nullptr;
|
|
|
|
return removeClient(client->id);
|
|
}
|
|
|
|
/// @return client with id @p client_id
|
|
ClientInfoPtr getClient(const std::string& client_id)
|
|
{
|
|
for (const auto& client : clients)
|
|
{
|
|
if (client->id == client_id)
|
|
return client;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
/// Add client @p client
|
|
void addClient(const ClientInfoPtr& client)
|
|
{
|
|
if (!client)
|
|
return;
|
|
|
|
for (const auto& c : clients)
|
|
{
|
|
if (c->id == client->id)
|
|
return;
|
|
}
|
|
|
|
clients.push_back(client);
|
|
/* sort(clients.begin(), clients.end(),
|
|
[](const ClientInfoPtr a, const ClientInfoPtr b) -> bool
|
|
{
|
|
return a.name > b.name;
|
|
});
|
|
*/
|
|
}
|
|
|
|
/// @return if the group is empty
|
|
bool empty() const
|
|
{
|
|
return clients.empty();
|
|
}
|
|
|
|
std::string name; ///< group name
|
|
std::string id; ///< group id
|
|
std::string streamId; ///< stream id assigned to this group
|
|
bool muted{false}; ///< is the group muted?
|
|
std::vector<ClientInfoPtr> clients; ///< list of clients in this group
|
|
};
|
|
|
|
|
|
/// Runtime configuration
|
|
class Config
|
|
{
|
|
public:
|
|
/// singleton c'tor
|
|
static Config& instance()
|
|
{
|
|
static Config instance_;
|
|
return instance_;
|
|
}
|
|
|
|
/// @return client from @p client_id
|
|
ClientInfoPtr getClientInfo(const std::string& client_id) const;
|
|
/// Get or create client with id @p client_id and return its group (create a new group for new clients)
|
|
/// @return the client's group
|
|
GroupPtr addClientInfo(const std::string& client_id);
|
|
/// Add client @p client to a group
|
|
/// @return the client's group: exising or newly created
|
|
GroupPtr addClientInfo(const ClientInfoPtr& client);
|
|
/// Remove client @p client from group
|
|
void remove(const ClientInfoPtr& client);
|
|
/// Remove group @p group, @p force removal of a non-empty group
|
|
void remove(const GroupPtr& group, bool force = false);
|
|
|
|
// GroupPtr removeFromGroup(const std::string& groupId, const std::string& clientId);
|
|
// GroupPtr setGroupForClient(const std::string& groupId, const std::string& clientId);
|
|
|
|
/// @return grouo from client with id @p client_id
|
|
GroupPtr getGroupFromClient(const std::string& client_id);
|
|
/// @return group from @p client
|
|
GroupPtr getGroupFromClient(const ClientInfoPtr& client);
|
|
/// @return group with id @p group_id
|
|
GroupPtr getGroup(const std::string& group_id) const;
|
|
|
|
/// @return groups with client as json
|
|
json getGroups() const;
|
|
/// @return complete server status, including @p streams
|
|
json getServerStatus(const json& streams) const;
|
|
|
|
/// Save config to file (json format)
|
|
void save();
|
|
|
|
/// Set directory and user/group of persistent "server.json"
|
|
void init(const std::string& root_directory = "", const std::string& user = "", const std::string& group = "");
|
|
|
|
/// List of groups
|
|
std::vector<GroupPtr> groups;
|
|
|
|
/// to protect members
|
|
std::mutex& getMutex();
|
|
|
|
private:
|
|
/// c'tor
|
|
Config() = default;
|
|
/// d'tor
|
|
~Config();
|
|
|
|
mutable std::recursive_mutex mutex_; ///< to protect members
|
|
std::mutex client_mutex_; ///< returned by "getMutex()", TODO: check this
|
|
std::string filename_; ///< filename to persist the config
|
|
};
|