/*** 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 "properties.hpp" // local headers #include "common/aixlog.hpp" // standard headers #include static constexpr auto LOG_TAG = "Properties"; namespace { template void readTag(const json& j, const std::string& tag, std::optional& dest) { try { if (!j.contains(tag)) dest = std::nullopt; else dest = j[tag].get(); } catch (const std::exception& e) { LOG(ERROR, LOG_TAG) << "failed to read tag: '" << tag << "': " << e.what() << '\n'; } } template void readTag(const json& j, const std::string& tag, T& dest, const T& def) { std::optional val; readTag(j, tag, val); if (val.has_value()) dest = val.value(); else dest = def; } template void addTag(json& j, const std::string& tag, const T& source) { try { j[tag] = source; } catch (const std::exception& e) { LOG(ERROR, LOG_TAG) << "failed to add tag: '" << tag << "': " << e.what() << '\n'; } } template void addTag(json& j, const std::string& tag, const std::optional& source) { if (!source.has_value()) { if (j.contains(tag)) j.erase(tag); } else addTag(j, tag, source.value()); } } // namespace Properties::Properties(const json& j) { fromJson(j); } json Properties::toJson() const { json j; if (playback_status.has_value()) addTag(j, "playbackStatus", std::optional(to_string(playback_status.value()))); if (loop_status.has_value()) addTag(j, "loopStatus", std::optional(to_string(loop_status.value()))); addTag(j, "rate", rate); addTag(j, "shuffle", shuffle); addTag(j, "volume", volume); addTag(j, "mute", mute); addTag(j, "position", position); addTag(j, "minimumRate", minimum_rate); addTag(j, "maximumRate", maximum_rate); addTag(j, "canGoNext", can_go_next); addTag(j, "canGoPrevious", can_go_previous); addTag(j, "canPlay", can_play); addTag(j, "canPause", can_pause); addTag(j, "canSeek", can_seek); addTag(j, "canControl", can_control); if (metadata.has_value()) addTag(j, "metadata", metadata->toJson()); return j; } void Properties::fromJson(const json& j) { static std::set rw_props = {"loopStatus", "shuffle", "volume", "mute", "rate"}; static std::set ro_props = {"playbackStatus", "loopStatus", "shuffle", "volume", "mute", "position", "minimumRate", "maximumRate", "canGoNext", "canGoPrevious", "canPlay", "canPause", "canSeek", "canControl", "metadata"}; for (const auto& element : j.items()) { bool is_rw = (rw_props.find(element.key()) != rw_props.end()); bool is_ro = (ro_props.find(element.key()) != ro_props.end()); if (!is_rw && !is_ro) LOG(WARNING, LOG_TAG) << "Property not supoorted: " << element.key() << "\n"; } std::optional opt; readTag(j, "playbackStatus", opt); if (opt.has_value()) playback_status = playback_status_from_string(opt.value()); else playback_status = std::nullopt; readTag(j, "loopStatus", opt); if (opt.has_value()) loop_status = loop_status_from_string(opt.value()); else loop_status = std::nullopt; readTag(j, "rate", rate); readTag(j, "shuffle", shuffle); readTag(j, "volume", volume); readTag(j, "mute", mute); readTag(j, "position", position); readTag(j, "minimumRate", minimum_rate); readTag(j, "maximumRate", maximum_rate); readTag(j, "canGoNext", can_go_next, false); readTag(j, "canGoPrevious", can_go_previous, false); readTag(j, "canPlay", can_play, false); readTag(j, "canPause", can_pause, false); readTag(j, "canSeek", can_seek, false); readTag(j, "canControl", can_control, false); if (j.contains("metadata")) { Metadata m; m.fromJson(j["metadata"]); metadata = m; } else metadata = std::nullopt; } bool Properties::operator==(const Properties& other) const { // expensive, but not called ofetn and less typing return (toJson() == other.toJson()); // clang-format off // return (playback_status == other.playback_status && // loop_status == other.loop_status && // rate == other.rate && // shuffle == other.shuffle && // volume == other.volume && // position == other.position && // minimum_rate == other.minimum_rate && // maximum_rate == other.maximum_rate && // can_go_next == other.can_go_next && // can_go_previous == other.can_go_previous && // can_play == other.can_play && // can_pause == other.can_pause && // can_seek == other.can_seek && // can_control == other.can_control); // clang-format on }