/** @file src/steam_auto_sync.cpp */ #include "steam_auto_sync.h" #include "confighttp.h" #include "file_handler.h" #include "logging.h" #include "platform/common.h" #include "steam_artwork.h" #include "steam_auto_sync_policy.h" #include "steam_integration.h" #include "steam_sync_policy.h" #include #include namespace platf::steam::autosync { namespace { constexpr auto poll_interval = std::chrono::seconds {30}; class manager_t { public: void configure(settings_t settings) { { std::lock_guard lock {mutex_}; settings_ = std::move(settings); ++epoch_; } condition_.notify_all(); } void start() { std::lock_guard lock {mutex_}; if (worker_.joinable()) { return; } stopping_ = false; worker_ = std::thread {[this] { run(); }}; } void stop() { { std::lock_guard lock {mutex_}; stopping_ = true; } condition_.notify_all(); if (worker_.joinable()) { worker_.join(); } } private: void run() { std::uint64_t previous_fingerprint = 0; std::uint64_t observed_epoch = 0; bool have_fingerprint = false; auto next_artwork_retry = std::chrono::steady_clock::time_point {}; while (true) { settings_t settings; std::uint64_t epoch; { std::lock_guard lock {mutex_}; if (stopping_) { return; } settings = settings_; epoch = epoch_; } if (epoch != observed_epoch) { observed_epoch = epoch; // A provider-policy change (exclusions/include-tools/removal) // must reconcile even when Steam's files did not change. have_fingerprint = false; } if (settings.enabled && settings.auto_sync) { try { if (platf::steam::available()) { auto catalog = platf::steam::discover_catalog(); const auto fingerprint = source_fingerprint(catalog); if (!have_fingerprint || fingerprint != previous_fingerprint || std::chrono::steady_clock::now() >= next_artwork_retry) { auto games = platf::steam::sync::policy::select_games( catalog, settings.sync_all_installed, settings.recent_games, settings.recent_max_age_days, settings.exclusions, settings.include_tools ); platf::steam::artwork::prepare(games, platf::appdata()); std::lock_guard apps_lock {confighttp::apps_file_mutex()}; const auto text = file_handler::read_file(config::stream.file_apps.c_str()); if (!text.empty()) { auto root = nlohmann::json::parse(text); const auto changed = platf::steam::sync::policy::reconcile( root, games, !settings.sync_all_installed || settings.remove_uninstalled, settings.exclusions, settings.include_tools, settings.sync_all_installed ? "installed" : "recent" ); if (changed && !confighttp::refresh_client_apps_cache(root)) { BOOST_LOG(warning) << "Steam auto-sync could not save applications"; } else { previous_fingerprint = fingerprint; have_fingerprint = true; next_artwork_retry = std::chrono::steady_clock::now() + std::chrono::minutes {5}; } } } } } catch (const std::exception &error) { BOOST_LOG(warning) << "Steam auto-sync failed: " << error.what(); } catch (...) { BOOST_LOG(warning) << "Steam auto-sync failed with an unknown error"; } } std::unique_lock lock {mutex_}; const auto wake_epoch = epoch_; condition_.wait_for(lock, poll_interval, [this, wake_epoch] { return stopping_ || epoch_ != wake_epoch; }); if (stopping_) { return; } } } std::mutex mutex_; std::condition_variable condition_; settings_t settings_; std::uint64_t epoch_ = 0; bool stopping_ = false; std::thread worker_; }; manager_t &manager() { static manager_t instance; return instance; } } // namespace void configure(settings_t settings) { manager().configure(std::move(settings)); } void start() { manager().start(); } void stop() { manager().stop(); } } // namespace platf::steam::autosync