Ctrl K
ComunidadGuías…

server/src/session_history.cpp

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * @file src/session_history.cpp
 * @brief Public facade for SQLite-backed session history persistence.
 */

// standard includes
#include <algorithm>
#include <chrono>
#include <mutex>

// local includes
#include "session_history.h"
#include "config.h"
#include "host_stats.h"
#include "logging.h"
#include "rtsp.h"
#include "stream.h"
#include "session_history_sampler.h"
#include "session_history_storage.h"
#include "session_history_storage_diagnostics.h"
#include "session_history_writer.h"

#ifdef _WIN32
  #include "platform/windows/display.h"
#endif

namespace session_history {

  namespace {

    std::mutex g_lifecycle_mutex;
    std::string g_history_db_path;

    writer::settings_t current_writer_settings() {
      writer::settings_t settings;
      settings.enabled = config::sunshine.session_history_enabled;
      settings.ttl_days = std::max(config::sunshine.session_history_ttl_days, 0);
      settings.max_db_size_bytes =
        config::sunshine.session_history_db_size_limit_mb > 0 ?
          static_cast<std::uint64_t>(config::sunshine.session_history_db_size_limit_mb) * 1024ull * 1024ull :
          0ull;
      return settings;
    }

    bool history_available() {
      return writer::is_enabled() && writer::is_available();
    }

    void log_storage_diagnostic(storage::diagnostics::level_e level, const std::string &message) {
      switch (level) {
        case storage::diagnostics::level_e::debug:
          BOOST_LOG(debug) << message;
          break;
        case storage::diagnostics::level_e::info:
          BOOST_LOG(info) << message;
          break;
        case storage::diagnostics::level_e::warning:
          BOOST_LOG(warning) << message;
          break;
        case storage::diagnostics::level_e::error:
          BOOST_LOG(error) << message;
          break;
      }
    }

  }  // namespace

  void init(const std::string &db_path) {
    std::scoped_lock lk {g_lifecycle_mutex};
    storage::diagnostics::set_sink(log_storage_diagnostic);
    g_history_db_path = db_path;
    writer::update_settings(current_writer_settings());
    writer::init(g_history_db_path);
    if (writer::is_available()) {
      sampler::init();
    }
  }

  void shutdown() {
    std::scoped_lock lk {g_lifecycle_mutex};
    BOOST_LOG(info) << "session_history: shutting down";
    sampler::shutdown();
    writer::shutdown();
    g_history_db_path.clear();
    BOOST_LOG(info) << "session_history: shut down";
  }

  void reload_settings() {
    std::scoped_lock lk {g_lifecycle_mutex};
    const auto settings = current_writer_settings();
    writer::update_settings(settings);

    if (!settings.enabled) {
      sampler::shutdown();
      writer::shutdown();
      return;
    }

    if (!g_history_db_path.empty()) {
      writer::init(g_history_db_path);
    }
    if (writer::is_available()) {
      sampler::init();
      (void) writer::enqueue_prune();
    }
  }

  void begin_session(const session_metadata_t &metadata) {
    if (!history_available()) {
      return;
    }

    BOOST_LOG(info) << "session_history: begin_session uuid=" << metadata.uuid
                    << " protocol=" << metadata.protocol;

    session_metadata_t enriched = metadata;
    if (enriched.host_cpu_model.empty() || enriched.host_gpu_model.empty()) {
      const auto &info = host_stats::info();
      if (enriched.host_cpu_model.empty()) {
        enriched.host_cpu_model = info.cpu_model;
      }
      if (enriched.host_gpu_model.empty()) {
        enriched.host_gpu_model = info.gpu_model;
      }
    }
#ifdef _WIN32
    if (enriched.stream_gpu_model.empty()) {
      enriched.stream_gpu_model = platf::dxgi::current_display_adapter_name();
    }
#endif
    enriched.codec = stream::canonical_codec_name(enriched.codec);

    sampler::register_session(enriched);

    if (!writer::enqueue_begin(enriched)) {
      BOOST_LOG(error) << "session_history: failed to queue begin_session for uuid=" << enriched.uuid;
    }

    session_event_t event;
    event.session_uuid = enriched.uuid;
    event.timestamp_unix = storage::now_unix();
    event.event_type = "stream_started";
    if (!writer::enqueue_event(event)) {
      BOOST_LOG(error) << "session_history: failed to queue stream_started event for uuid=" << enriched.uuid;
    }
  }

  void end_session(const std::string &uuid) {
    sampler::unregister_session(uuid);

    if (!history_available()) {
      return;
    }

    BOOST_LOG(info) << "session_history: end_session uuid=" << uuid;

    session_event_t event;
    event.session_uuid = uuid;
    event.timestamp_unix = storage::now_unix();
    event.event_type = "stream_ended";
    if (!writer::enqueue_event(event)) {
      BOOST_LOG(error) << "session_history: failed to queue stream_ended event for uuid=" << uuid;
    }

    if (!writer::enqueue_end(uuid)) {
      BOOST_LOG(error) << "session_history: failed to queue end_session for uuid=" << uuid;
    }

    (void) writer::enqueue_prune();
  }

  void record_event(const std::string &uuid, const std::string &event_type, const std::string &payload) {
    if (!history_available()) {
      return;
    }

    session_event_t event;
    event.session_uuid = uuid;
    event.timestamp_unix = storage::now_unix();
    event.event_type = event_type;
    event.payload = payload;
    if (!writer::enqueue_event(event)) {
      BOOST_LOG(error) << "session_history: failed to queue event '" << event_type << "' for uuid=" << uuid;
    }
  }

  std::vector<session_summary_t> list_sessions(int limit, int offset) {
    return writer::list_sessions(limit, offset);
  }

  std::optional<session_detail_t> get_session_detail(const std::string &uuid, bool include_all) {
    return writer::get_session_detail(uuid, include_all);
  }

  std::vector<active_session_t> get_active_sessions() {
    return sampler::get_active_sessions();
  }

  history_status_t get_history_status() {
    return writer::get_status();
  }

  delete_result_e delete_session(const std::string &uuid) {
    if (!history_available()) {
      return delete_result_e::unavailable;
    }
    if (sampler::is_active(uuid)) {
      return delete_result_e::active_session;
    }
    return writer::delete_session(uuid);
  }

#ifdef SUNSHINE_TESTS
  bool record_sample_for_tests(const session_sample_t &sample) {
    return writer::enqueue_sample(sample);
  }

  bool set_session_end_time_for_tests(const std::string &uuid, double end_time_unix) {
    return writer::set_session_end_time_for_tests(uuid, end_time_unix);
  }

  void configure_retention_for_tests(bool enabled, int ttl_days, std::uint64_t max_db_size_bytes) {
    writer::settings_t settings;
    settings.enabled = enabled;
    settings.ttl_days = ttl_days;
    settings.max_db_size_bytes = max_db_size_bytes;
    writer::update_settings(settings);
  }

  bool prune_now_for_tests() {
    return writer::prune_now_for_tests();
  }

  bool force_write_failure_for_tests() {
    return writer::force_write_failure_for_tests();
  }

  void configure_queue_limits_for_tests(
    std::size_t priority_limit,
    std::size_t regular_limit,
    std::size_t sample_limit,
    std::size_t sample_batch_size) {
    writer::configure_queue_limits_for_tests(priority_limit, regular_limit, sample_limit, sample_batch_size);
  }

  void reset_queue_limits_for_tests() {
    writer::reset_queue_limits_for_tests();
  }
#endif

}  // namespace session_history