diff --git a/monitarr/libmonitarr/include/args.hpp b/monitarr/libmonitarr/include/args.hpp new file mode 100644 index 0000000..70c5f29 --- /dev/null +++ b/monitarr/libmonitarr/include/args.hpp @@ -0,0 +1,13 @@ +#ifndef LIBMONITARR_INCLUDE_ARGS_HPP_ +#define LIBMONITARR_INCLUDE_ARGS_HPP_ + +#include "utils/config.hpp" +#include + +namespace monitarr { +[[nodiscard]] auto get_arg(std::string_view arg, int argc, char **argv) + -> std::optional; +[[nodiscard]] auto has_arg(std::string_view arg, int argc, char **argv) -> bool; +} // namespace monitarr + +#endif // LIBMONITARR_INCLUDE_ARGS_HPP_ diff --git a/monitarr/libmonitarr/src/args.cpp b/monitarr/libmonitarr/src/args.cpp new file mode 100644 index 0000000..0a03d88 --- /dev/null +++ b/monitarr/libmonitarr/src/args.cpp @@ -0,0 +1,33 @@ +#include "args.hpp" + +namespace monitarr { +auto get_arg(std::string_view arg, int argc, char **argv) + -> std::optional { + auto cmd_line = std::span(argv, static_cast(argc)) | + std::views::transform( + [](char const *val) { return std::string_view(val); }); + auto iter = + std::ranges::find_if(std::next(cmd_line.begin()), cmd_line.end(), + [&arg](auto &&item) -> bool { return arg == item; }); + if (iter == cmd_line.end()) { + return std::nullopt; + } + + iter = std::next(iter); + if (iter == cmd_line.end()) { + return std::nullopt; + } + + return std::string{*iter}; +} + +auto has_arg(std::string_view arg, int argc, char **argv) -> bool { + auto cmd_line = std::span(argv, static_cast(argc)) | + std::views::transform( + [](char const *val) { return std::string_view(val); }); + return std::ranges::find_if(std::next(cmd_line.begin()), cmd_line.end(), + [&arg](auto &&item) -> bool { + return arg == item; + }) != cmd_line.end(); +} +} // namespace monitarr diff --git a/monitarr/monitarr/main.cpp b/monitarr/monitarr/main.cpp index 3ad009e..2817ac7 100644 --- a/monitarr/monitarr/main.cpp +++ b/monitarr/monitarr/main.cpp @@ -6,6 +6,7 @@ #include "initialize.hpp" +#include "args.hpp" #include "data_db.hpp" #include "settings.hpp" #include "utils/common.hpp" @@ -29,12 +30,16 @@ namespace monitarr { static void remove_stalled(std::string_view download_id, std::string_view title, std::uint64_t episode_id, std::uint64_t movie_id, - const server_cfg &server, data_db &state_db) { + const server_cfg &server, + data_db *state_db = nullptr) { MONITARR_USES_FUNCTION_NAME(); fmt::println("remove and block {}|{}|{}|{}", server.id, server.url, title, download_id); - state_db.remove(download_id); + + if (state_db != nullptr) { + state_db->remove(download_id); + } auto cli = create_client(server); auto response = cli.Delete( @@ -54,8 +59,9 @@ static void remove_stalled(std::string_view download_id, std::string_view title, {"movieIds", {movie_id}}, }); - response = cli.Post("/api/{}/command", data.dump(), "application/json"); - if (response->status != httplib::StatusCode::OK_200) { + response = cli.Post(fmt::format("/api/{}/command", server.api_version), + data.dump(), "application/json"); + if (response->status != httplib::StatusCode::Created_201) { utils::error::handle_error( function_name, fmt::format("failed to search radarr|{}|{}|{}|{}|{}", server.id, @@ -73,8 +79,9 @@ static void remove_stalled(std::string_view download_id, std::string_view title, {"episodeIds", {episode_id}}, }); - response = cli.Post("/api/{}/command", data.dump(), "application/json"); - if (response->status != httplib::StatusCode::OK_200) { + response = cli.Post(fmt::format("/api/{}/command", server.api_version), + data.dump(), "application/json"); + if (response->status != httplib::StatusCode::Created_201) { utils::error::handle_error( function_name, fmt::format("failed to search sonarr|{}|{}|{}|{}|{}", server.id, @@ -82,6 +89,79 @@ static void remove_stalled(std::string_view download_id, std::string_view title, } } +static void display_queue(const server_cfg &server) { + MONITARR_USES_FUNCTION_NAME(); + + auto cli = create_client(server); + + std::uint16_t page{0U}; + while (++page != 0U) { + httplib::Params params; + params.emplace("page", std::to_string(page)); + params.emplace("pageSize", "100"); + + auto response = + cli.Get(fmt::format("/api/{}/queue", server.api_version), params, {}); + if (response->status != httplib::StatusCode::OK_200) { + utils::error::handle_error( + function_name, fmt::format("check server request failed|{}|{}|{}", + server.id, server.url, response->status)); + break; + } + + auto json_data = nlohmann::json::parse(response->body); + if (json_data.at("page").get() != page) { + return; + } + + for (const auto &record : json_data.at("records")) { + fmt::println("{}", record.dump(2)); + } + } +} + +[[nodiscard]] static auto get_download(std::uint64_t record_id, + const server_cfg &server) + -> std::optional { + MONITARR_USES_FUNCTION_NAME(); + + auto cli = create_client(server); + + std::uint16_t page{0U}; + while (++page != 0U) { + httplib::Params params; + params.emplace("page", std::to_string(page)); + params.emplace("pageSize", "100"); + + auto response = + cli.Get(fmt::format("/api/{}/queue", server.api_version), params, {}); + if (response->status != httplib::StatusCode::OK_200) { + utils::error::handle_error( + function_name, fmt::format("check server request failed|{}|{}|{}", + server.id, server.url, response->status)); + return std::nullopt; + } + + auto json_data = nlohmann::json::parse(response->body); + if (json_data.at("page").get() != page) { + return std::nullopt; + } + + auto iter = std::ranges::find_if( + json_data.at("records"), + [&record_id](const nlohmann::json &record) -> bool { + return record_id == record.at("id").get(); + }); + if (iter == json_data.at("records").end()) { + continue; + } + + return *iter; + } + + return std::nullopt; +} + static void check_server(const server_cfg &server, data_db &state_db) { MONITARR_USES_FUNCTION_NAME(); @@ -112,8 +192,8 @@ static void check_server(const server_cfg &server, data_db &state_db) { auto now = utils::time::get_time_now(); for (const auto &record : json_data.at("records")) { - auto download_id = fmt::format( - "{}/{}", server.id, record.at("downloadId").get()); + auto download_id = + fmt::format("{}/{}", server.id, record.at("id").get()); auto episode_id = record.contains("episodeId") ? record["episodeId"].get() : std::uint64_t{0U}; @@ -147,7 +227,7 @@ static void check_server(const server_cfg &server, data_db &state_db) { server.timeout) { if (size_left == data->size_left) { remove_stalled(download_id, title, episode_id, movie_id, server, - state_db); + &state_db); } else if (size_left == 0U || not is_downloading) { state_db.remove(download_id); } else { @@ -203,7 +283,7 @@ static void check_server(const server_cfg &server, data_db &state_db) { using namespace monitarr; -auto main(int /* argc */, char ** /* argv */) -> int { +auto main(int argc, char **argv) -> int { MONITARR_USES_FUNCTION_NAME(); #if defined(PROJECT_ENABLE_BACKWARD_CPP) @@ -214,71 +294,109 @@ auto main(int /* argc */, char ** /* argv */) -> int { return -1; } - static std::mutex mtx; - static std::condition_variable notify; - static stop_type stop_requested{false}; - - static const auto quit_handler = [](int sig) { - fmt::println("stop requested|{}", sig); - stop_requested = true; - - mutex_lock lock(mtx); - notify.notify_all(); - }; - - std::signal(SIGINT, quit_handler); - std::signal(SIGQUIT, quit_handler); - std::signal(SIGTERM, quit_handler); - auto ret{0}; - - try { - std::string cfg_file; - auto cfg{load_config(cfg_file)}; - auto state_db{load_db()}; - - if (cfg.server_list.empty()) { - utils::error::handle_error(function_name, - "no servers have been configured"); - ret = 3; - } else { - while (not stop_requested) { - std::for_each(std::execution::par, cfg.server_list.begin(), - cfg.server_list.end(), - [&state_db](const server_cfg &server) { - if (stop_requested) { - return; - } - - try { - check_server(server, state_db); - } catch (const std::exception &ex) { - utils::error::handle_exception(function_name, ex); - } catch (...) { - utils::error::handle_exception(function_name); - } - }); - unique_mutex_lock lock(mtx); - if (stop_requested) { - continue; + if (argc > 1) { + try { + std::string cfg_file; + auto cfg{load_config(cfg_file)}; + if (has_arg("-d", argc, argv)) { + fmt::println("{}", nlohmann::json(cfg).dump(2)); + } else if (has_arg("-l", argc, argv)) { + auto idx = get_arg("-i", argc, argv); + if (idx.has_value()) { + auto &server = cfg.server_list.at(utils::string::to_uint64(*idx)); + fmt::println("queue|{}|{}", server.id, server.url); + display_queue(server); + } + } else if (has_arg("-b", argc, argv)) { + auto idx = get_arg("-i", argc, argv); + if (idx.has_value()) { + auto &server = cfg.server_list.at(utils::string::to_uint64(*idx)); + auto record_id = get_arg("-id", argc, argv); + if (record_id.has_value()) { + auto entry = + get_download(utils::string::to_uint64(*record_id), server); + if (entry.has_value()) { + remove_stalled(fmt::format("{}/{}", server.id, *record_id), + entry->at("title").get(), 0U, + entry->at("movieId").get(), server); + } + } } - - fmt::println("waiting for next check|{}", cfg.check_interval); - notify.wait_for(lock, cfg.check_interval); } + } catch (const std::exception &ex) { + utils::error::handle_exception(function_name, ex); + ret = 2; + } catch (...) { + utils::error::handle_exception(function_name); + ret = 2; + } + } else { + static std::mutex mtx; + static std::condition_variable notify; + static stop_type stop_requested{false}; + + static const auto quit_handler = [](int sig) { + fmt::println("stop requested|{}", sig); + stop_requested = true; + + mutex_lock lock(mtx); + notify.notify_all(); + }; + + std::signal(SIGINT, quit_handler); + std::signal(SIGQUIT, quit_handler); + std::signal(SIGTERM, quit_handler); + + try { + std::string cfg_file; + auto cfg{load_config(cfg_file)}; + auto state_db{load_db()}; + + if (cfg.server_list.empty()) { + utils::error::handle_error(function_name, + "no servers have been configured"); + ret = 3; + } else { + while (not stop_requested) { + std::for_each(std::execution::par, cfg.server_list.begin(), + cfg.server_list.end(), + [&state_db](const server_cfg &server) { + if (stop_requested) { + return; + } + + try { + check_server(server, state_db); + } catch (const std::exception &ex) { + utils::error::handle_exception(function_name, ex); + } catch (...) { + utils::error::handle_exception(function_name); + } + }); + unique_mutex_lock lock(mtx); + if (stop_requested) { + continue; + } + + fmt::println("waiting for next check|{}", cfg.check_interval); + notify.wait_for(lock, cfg.check_interval); + } + } + + cfg.save(cfg_file); + state_db.close(); + } catch (const std::exception &ex) { + utils::error::handle_exception(function_name, ex); + ret = 2; + } catch (...) { + utils::error::handle_exception(function_name); + ret = 2; } - cfg.save(cfg_file); - state_db.close(); - } catch (const std::exception &ex) { - utils::error::handle_exception(function_name, ex); - ret = 2; - } catch (...) { - utils::error::handle_exception(function_name); - ret = 2; + fmt::println("terminating application|{}", ret); } - fmt::println("terminating application|{}", ret); monitarr::project_cleanup(); return ret; }