BareGit
#include "application.hpp"

#include <cerrno>
#include <csignal>
#include <ctime>
#include <cstring>
#include <pthread.h>

#include <spdlog/spdlog.h>

#include "service_error.hpp"

namespace telegrammer
{

Application::Application(ApplicationConfig config)
    : database_lock_(std::move(config.database_lock)),
      listen_info_(config.listen_info), db_path_(std::move(config.db_path)),
      keys_(db_path_),
      subscriptions_(db_path_), deliveries_(db_path_),
      telegram_(std::move(config.token)), dispatcher_(deliveries_),
      poller_(telegram_, deliveries_, dispatcher_, state_, 0),
      server_(config.listen_info, keys_, subscriptions_, deliveries_,
              telegram_, state_)
{}

void Application::requestStop()
{
    if(stopping_.exchange(true))
    {
        return;
    }
    server_.stop();
    poller_.stop();
    dispatcher_.stop();
}

void Application::waitForSignal(std::stop_token stop_token)
{
    sigset_t signals;
    sigemptyset(&signals);
    sigaddset(&signals, SIGINT);
    sigaddset(&signals, SIGTERM);

    while(!stop_token.stop_requested())
    {
        timespec timeout{1, 0};
        int signal = sigtimedwait(&signals, nullptr, &timeout);
        if(signal == SIGINT || signal == SIGTERM)
        {
            requestStop();
            return;
        }
        if(signal < 0 && errno != EAGAIN && errno != EINTR)
        {
            spdlog::error("Signal wait failed: {}", std::strerror(errno));
            requestStop();
            return;
        }
    }
}

int Application::run()
{
    sigset_t signals;
    sigemptyset(&signals);
    sigaddset(&signals, SIGINT);
    sigaddset(&signals, SIGTERM);
    if(pthread_sigmask(SIG_BLOCK, &signals, nullptr) != 0)
    {
        spdlog::error("Unable to block termination signals");
        return 1;
    }

    auto identity = telegram_.getMe();
    if(!identity.has_value())
    {
        const ServiceError* error = asServiceError(identity.error());
        if(error != nullptr)
        {
            spdlog::error("Unable to validate Telegram bot [{}]: {}",
                          error->code, error->msg);
        }
        else
        {
            spdlog::error("Unable to validate Telegram bot: {}",
                          mw::errorMsg(identity.error()));
        }
        return 1;
    }
    int64_t bot_id = (*identity)["result"]["id"].get<int64_t>();

    auto bot_result = deliveries_.ensureBot(bot_id);
    if(!bot_result.has_value())
    {
        spdlog::error("Unable to configure bot state: {}",
                      mw::errorMsg(bot_result.error()));
        return 1;
    }
    auto reset_result = deliveries_.resetInFlight();
    if(!reset_result.has_value())
    {
        spdlog::error("Unable to recover callback jobs: {}",
                      mw::errorMsg(reset_result.error()));
        return 1;
    }
    poller_.setBotId(bot_id);
    state_.polling_ready = true;
    state_.degraded = false;

    auto start_result = server_.start();
    if(!start_result.has_value())
    {
        spdlog::error("Failed to start server: {}",
                      mw::errorMsg(start_result.error()));
        return 1;
    }

    spdlog::info("Server listening on {}:{}", listen_info_.address,
                 listen_info_.port);
    dispatcher_.start();
    poller_.start();
    signal_watcher_ = std::jthread([this](std::stop_token stop_token)
                                    {
                                        waitForSignal(stop_token);
                                    });

    server_.wait();
    requestStop();
    signal_watcher_.request_stop();
    if(signal_watcher_.joinable())
    {
        signal_watcher_.join();
    }
    return 0;
}

} // namespace telegrammer