/*** 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 "common/json.hpp" #include "common/utils.hpp" #include "common/utils/string_utils.hpp" // standard headers #include #include #include #include #include namespace strutils = utils::string; using json = nlohmann::json; struct ClientInfo; struct Group; using ClientInfoPtr = std::shared_ptr; using GroupPtr = std::shared_ptr; template T jGet(const json& j, const std::string& what, const T& def) { try { if (!j.count(what)) return def; return j[what].get(); } catch (...) { return def; } } struct Volume { Volume(uint16_t _percent = 100, bool _muted = false) : percent(_percent), muted(_muted) { } void fromJson(const json& j) { percent = jGet(j, "percent", percent); muted = jGet(j, "muted", muted); } json toJson() { json j; j["percent"] = percent; j["muted"] = muted; return j; } uint16_t percent; bool muted; }; struct Host { Host() : name(""), mac(""), os(""), arch(""), ip("") { } void update() { name = getHostName(); os = getOS(); arch = getArch(); } void fromJson(const json& j) { name = strutils::trim_copy(jGet(j, "name", "")); mac = strutils::trim_copy(jGet(j, "mac", "")); os = strutils::trim_copy(jGet(j, "os", "")); arch = strutils::trim_copy(jGet(j, "arch", "")); ip = strutils::trim_copy(jGet(j, "ip", "")); } json toJson() { json j; j["name"] = name; j["mac"] = mac; j["os"] = os; j["arch"] = arch; j["ip"] = ip; return j; } std::string name; std::string mac; std::string os; std::string arch; std::string ip; }; struct ClientConfig { ClientConfig() : name(""), volume(100), latency(0), instance(1) { } void fromJson(const json& j) { name = strutils::trim_copy(jGet(j, "name", "")); volume.fromJson(j["volume"]); latency = jGet(j, "latency", 0); instance = jGet(j, "instance", 1); } json toJson() { json j; j["name"] = strutils::trim_copy(name); j["volume"] = volume.toJson(); j["latency"] = latency; j["instance"] = instance; return j; } std::string name; Volume volume; int32_t latency; size_t instance; }; struct Snapcast { Snapcast(const std::string& _name = "", const std::string& _version = "") : name(_name), version(_version), protocolVersion(1) { } virtual ~Snapcast() = default; virtual void fromJson(const json& j) { name = strutils::trim_copy(jGet(j, "name", "")); version = strutils::trim_copy(jGet(j, "version", "")); protocolVersion = jGet(j, "protocolVersion", 1); } virtual json toJson() { json j; j["name"] = strutils::trim_copy(name); j["version"] = strutils::trim_copy(version); j["protocolVersion"] = protocolVersion; return j; } std::string name; std::string version; int protocolVersion; }; struct Snapclient : public Snapcast { Snapclient(const std::string& _name = "", const std::string& _version = "") : Snapcast(_name, _version) { } }; struct Snapserver : public Snapcast { Snapserver(const std::string& _name = "", const std::string& _version = "") : Snapcast(_name, _version), controlProtocolVersion(1) { } void fromJson(const json& j) override { Snapcast::fromJson(j); controlProtocolVersion = jGet(j, "controlProtocolVersion", 1); } json toJson() override { json j = Snapcast::toJson(); j["controlProtocolVersion"] = controlProtocolVersion; return j; } int controlProtocolVersion; }; struct ClientInfo { ClientInfo(const std::string& _clientId = "") : id(_clientId), connected(false) { lastSeen.tv_sec = 0; lastSeen.tv_usec = 0; } void fromJson(const json& j) { host.fromJson(j["host"]); id = jGet(j, "id", host.mac); snapclient.fromJson(j["snapclient"]); config.fromJson(j["config"]); lastSeen.tv_sec = jGet(j["lastSeen"], "sec", 0); lastSeen.tv_usec = jGet(j["lastSeen"], "usec", 0); connected = jGet(j, "connected", true); } json toJson() { 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; Host host; Snapclient snapclient; ClientConfig config; timeval lastSeen; bool connected; }; struct Group { Group(const ClientInfoPtr client = nullptr) : muted(false) { if (client) id = client->id; id = generateUUID(); } void fromJson(const json& j) { name = strutils::trim_copy(jGet(j, "name", "")); id = strutils::trim_copy(jGet(j, "id", "")); streamId = strutils::trim_copy(jGet(j, "stream_id", "")); muted = jGet(j, "muted", false); clients.clear(); if (j.count("clients")) { for (const auto& jClient : j["clients"]) { ClientInfoPtr client = std::make_shared(); client->fromJson(jClient); client->connected = false; addClient(client); } } } json toJson() { 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; } ClientInfoPtr removeClient(const std::string& clientId) { for (auto iter = clients.begin(); iter != clients.end(); ++iter) { if ((*iter)->id == clientId) { clients.erase(iter); return (*iter); } } return nullptr; } ClientInfoPtr removeClient(ClientInfoPtr client) { if (!client) return nullptr; return removeClient(client->id); } ClientInfoPtr getClient(const std::string& clientId) { for (const auto& client : clients) { if (client->id == clientId) return client; } return nullptr; } void addClient(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; }); */ } bool empty() const { return clients.empty(); } std::string name; std::string id; std::string streamId; bool muted; std::vector clients; }; class Config { public: static Config& instance() { static Config instance_; return instance_; } ClientInfoPtr getClientInfo(const std::string& clientId) const; GroupPtr addClientInfo(const std::string& clientId); GroupPtr addClientInfo(ClientInfoPtr client); void remove(ClientInfoPtr client); void remove(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); GroupPtr getGroupFromClient(const std::string& clientId); GroupPtr getGroupFromClient(ClientInfoPtr client); GroupPtr getGroup(const std::string& groupId) const; json getGroups() const; json getServerStatus(const json& streams) const; void save(); void init(const std::string& root_directory = "", const std::string& user = "", const std::string& group = ""); std::vector groups; std::mutex& getMutex(); private: Config() = default; ~Config(); mutable std::recursive_mutex mutex_; std::mutex client_mutex_; std::string filename_; };