#include "state.h" #include "spdlog/spdlog.h" #include #include #include #include #include using namespace state; using namespace nlohmann; AppState::AppState(std::string filename, config::AppConfig *cfg) : m_saveFilename(filename), m_config(cfg) { for (int i = 0; i < cfg->vkSources.size(); ++i) { vkRepostState.emplace_back(); } for (int i = 0; i < cfg->tgSources.size(); ++i) { tgRepostState.emplace_back(); } std::ifstream f(filename); if (f.fail()) { spdlog::error("no state file"); return; } json state = json::parse(f); if (state.type() != json::value_t::object) { throw InvalidSavedStateException("JSON root must be an object"); } if (state.contains("vk")) { json vkStates_ = state["vk"]; if (vkStates_.type() == json::value_t::object) { for (auto& [key, vkState_] : vkStates_.items()) { if (key.empty()) continue; int sourceIndex; RepostState &&s {}; if (key[0] >= '0' && key[0] <= '9') { long keyNum; try { keyNum = std::stol(key); } catch (std::exception &e) { throw InvalidSavedStateException("invalid number in vk source id"); } sourceIndex = std::distance( m_config->vkSources.begin(), std::find_if(m_config->vkSources.begin(), m_config->vkSources.end(), [=](auto &src){ return src.id.index() == 0 && std::get(src.id) == std::stoi(key); } ) ); } else { sourceIndex = std::distance( m_config->vkSources.begin(), std::find_if(m_config->vkSources.begin(), m_config->vkSources.end(), [=](auto &src){ return src.id.index() == 1 && std::get(src.id) == key; } ) ); } if (vkState_.contains("last_post_date")) { json lastPostDate_ = vkState_["last_post_date"]; if (lastPostDate_.type() == json::value_t::number_integer || lastPostDate_.type() == json::value_t::number_unsigned) { s.lastForwardedPostDate = vkState_["last_post_date"]; } else { throw InvalidSavedStateException("key vk.last_post_date must be an integer"); } } if (sourceIndex < m_config->vkSources.size()) vkRepostState[sourceIndex] = s; } } else { throw InvalidSavedStateException("key vk must be an object"); } } if (state.contains("tg")) { json tgStates_ = state["tg"]; if (tgStates_.type() == json::value_t::object) { for (auto& [key, tgState_] : tgStates_.items()) { if (key.empty()) continue; int sourceIndex; RepostState &&s {}; long keyNum; try { keyNum = std::stol(key); } catch (std::exception &e) { throw InvalidSavedStateException("invalid number in tg source id"); } sourceIndex = std::distance( m_config->tgSources.begin(), std::find_if(m_config->tgSources.begin(), m_config->tgSources.end(), [=](auto &src){ return src.id == keyNum; } ) ); if (tgState_.contains("last_post_date")) { json lastPostDate_ = tgState_["last_post_date"]; if (lastPostDate_.type() == json::value_t::number_integer || lastPostDate_.type() == json::value_t::number_unsigned) { s.lastForwardedPostDate = tgState_["last_post_date"]; } else { throw InvalidSavedStateException("key tg.last_post_date must be an integer"); } } if (sourceIndex < m_config->tgSources.size()) tgRepostState[sourceIndex] = s; } } else { throw InvalidSavedStateException("key tg must be an object"); } } } void AppState::save() { if (m_saveFilename.empty()) return; spdlog::info("saving state"); json state = {{"vk", json::object()}, {"tg", json::object()}}; for (int i = 0; i < m_config->vkSources.size(); ++i) { auto &s = vkRepostState[i]; json st = {{"last_post_date", s.lastForwardedPostDate}}; auto &vkId = m_config->vkSources[i].id; std::string vkIdStr; if (vkId.index() == 0) vkIdStr = std::to_string(std::get(vkId)); else vkIdStr = std::get(vkId); state["vk"].emplace(vkIdStr, st); } for (int i = 0; i < m_config->tgSources.size(); ++i) { auto &s = tgRepostState[i]; json st = {{"last_post_date", s.lastForwardedPostDate}}; state["tg"].emplace(std::to_string(m_config->tgSources[i].id), st); } std::ofstream f(m_saveFilename.c_str()); if (f.fail()) { spdlog::error("failed to open state file '{}'", m_saveFilename); return; } f << state; } std::string AppState::to_string() { std::stringstream output; output << ""; return output.str(); }