From 4af2af923ffc5d4fcdb91115e3eec31e9f6db7ee Mon Sep 17 00:00:00 2001 From: Simon Quigley Date: Sun, 15 Dec 2024 20:39:22 -0600 Subject: [PATCH] Add several more fields related to distro, archive, and binary --- CMakeLists.txt | 15 +- src/archive.cpp | 836 ++++++++++++++++++++-- src/archive.h | 154 +++- src/archive_dependency.cpp | 79 ++ src/archive_dependency.h | 38 + src/archive_permission.cpp | 46 +- src/archive_permission.h | 23 +- src/binary_package_publishing_history.cpp | 178 +++++ src/binary_package_publishing_history.h | 69 ++ src/distro_arch_series.cpp | 214 ++++++ src/distro_arch_series.h | 74 ++ src/distro_arch_series_filter.cpp | 74 ++ src/distro_arch_series_filter.h | 39 + src/distro_series.cpp | 521 ++++++++++++++ src/distro_series.h | 127 ++++ 15 files changed, 2405 insertions(+), 82 deletions(-) create mode 100644 src/archive_dependency.cpp create mode 100644 src/archive_dependency.h create mode 100644 src/binary_package_publishing_history.cpp create mode 100644 src/binary_package_publishing_history.h create mode 100644 src/distro_arch_series.cpp create mode 100644 src/distro_arch_series.h create mode 100644 src/distro_arch_series_filter.cpp create mode 100644 src/distro_arch_series_filter.h create mode 100644 src/distro_series.cpp create mode 100644 src/distro_series.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1331ab5..7b67c5d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,3 @@ -# CMakeLists.txt - cmake_minimum_required(VERSION 3.10) project(launchpadlib-cpp VERSION 0.0.1 LANGUAGES CXX) @@ -22,6 +20,11 @@ add_library(launchpad SHARED src/authentication.cpp src/source_package_publishing_history.cpp src/build.cpp + src/binary_package_publishing_history.cpp + src/archive_dependency.cpp + src/distro_series.cpp + src/distro_arch_series.cpp + src/distro_arch_series_filter.cpp ) target_include_directories(launchpad PUBLIC @@ -33,12 +36,11 @@ target_compile_definitions(launchpad PRIVATE BUILDING_LAUNCHPAD) set_target_properties(launchpad PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION 0 - PUBLIC_HEADER "src/launchpad.h;src/callablewrapper.h;src/archive.h;src/archive_permission.h;src/utils.h;src/person.h;src/distribution.h;src/authentication.h;src/source_package_publishing_history.h;src/build.h" + PUBLIC_HEADER "src/launchpad.h;src/callablewrapper.h;src/archive.h;src/archive_permission.h;src/utils.h;src/person.h;src/distribution.h;src/authentication.h;src/source_package_publishing_history.h;src/build.h;src/binary_package_publishing_history.h;src/archive_dependency.h;src/distro_series.h;src/distro_arch_series.h;src/distro_arch_series_filter.h" CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN YES ) -# Install the library and its headers install(TARGETS launchpad EXPORT launchpadTargets LIBRARY DESTINATION lib @@ -47,14 +49,12 @@ install(TARGETS launchpad PUBLIC_HEADER DESTINATION include/launchpadlib-cpp ) -# Export the targets to a script install(EXPORT launchpadTargets FILE launchpadTargets.cmake NAMESPACE launchpad:: DESTINATION lib/cmake/launchpadlib-cpp ) -# Configure the CMake package configuration file include(CMakePackageConfigHelpers) configure_package_config_file( "${CMAKE_CURRENT_SOURCE_DIR}/cmake/launchpadConfig.cmake.in" @@ -64,21 +64,18 @@ configure_package_config_file( NO_CHECK_REQUIRED_COMPONENTS_MACRO ) -# Generate a version file for the package write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/launchpadConfigVersion.cmake" VERSION ${PROJECT_VERSION} COMPATIBILITY AnyNewerVersion ) -# Install the CMake package configuration files install(FILES "${CMAKE_CURRENT_BINARY_DIR}/launchpadConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/launchpadConfigVersion.cmake" DESTINATION lib/cmake/launchpadlib-cpp ) -# Link necessary libraries target_link_libraries(launchpad PRIVATE nlohmann_json::nlohmann_json OpenSSL::Crypto diff --git a/src/archive.cpp b/src/archive.cpp index 807fa4f..00f1b2d 100644 --- a/src/archive.cpp +++ b/src/archive.cpp @@ -17,6 +17,9 @@ #include "utils.h" #include "launchpad.h" #include "source_package_publishing_history.h" +#include "archive_permission.h" +#include "binary_package_publishing_history.h" +#include "archive_dependency.h" #include #include @@ -57,11 +60,8 @@ void archive::parse_json(const std::string& json_data) { std::cerr << "Error: Empty JSON data for archive::parse_json." << std::endl; return; } - try { auto data = nlohmann::json::parse(json_data); - - // Map JSON keys to member variable setters std::map> json_map = { {"authorized_size", [this](const nlohmann::json& val) { authorized_size = val.get(); }}, {"build_debug_symbols", [this](const nlohmann::json& val) { build_debug_symbols = val.get(); }}, @@ -83,8 +83,6 @@ void archive::parse_json(const std::string& json_data) { {"signing_key_fingerprint", [this](const nlohmann::json& val) { signing_key_fingerprint = val.get(); }}, {"suppress_subscription_notifications", [this](const nlohmann::json& val) { suppress_subscription_notifications = val.get(); }} }; - - // Handle read-only fields separately std::map> readonly_map = { {"dependencies_collection_link", [this](const nlohmann::json& val) { dependencies_collection_link = val.get(); }}, {"enabled_restricted_processors_collection_link", [this](const nlohmann::json& val) { enabled_restricted_processors_collection_link = val.get(); }}, @@ -97,7 +95,6 @@ void archive::parse_json(const std::string& json_data) { {"web_link", [this](const nlohmann::json& val) { web_link = val.get(); }} }; - // Process all keys in the JSON object for (auto& [key, value] : data.items()) { try { if (json_map.find(key) != json_map.end()) { @@ -107,12 +104,11 @@ void archive::parse_json(const std::string& json_data) { readonly_map[key](value); readonly_map.erase(key); } - } catch (const std::exception& e) { + } catch (...) { continue; } } - // Special handling for owner_name and ppa_name std::string link = owner_link; size_t pos = link.find("~"); if (pos != std::string::npos) { @@ -123,11 +119,8 @@ void archive::parse_json(const std::string& json_data) { } ppa_name = name; - } catch (const nlohmann::json::parse_error& e) { - std::cerr << "JSON parse error in archive::parse_json: " << e.what() << std::endl; - std::cerr << "Input JSON was: " << json_data << std::endl; - } catch (const std::exception& e) { - std::cerr << "Unexpected error during JSON parsing: " << e.what() << std::endl; + } catch (...) { + // ignore parse errors } } @@ -135,26 +128,28 @@ std::string archive::build_archive_endpoint() const { return "~" + url_encode(owner_name) + "/" + url_encode(ppa_name); } +void archive::set_lp(launchpad* lp_ptr) { + lp = lp_ptr; +} + std::generator archive::getAllPermissions() const { std::map params = {{"ws.op", "getAllPermissions"}}; - auto response = lp->api_get(self_link, params); + if(!response) co_return; auto data = nlohmann::json::parse(response.value()); - while (response.has_value() && data.contains("next_collection_link") && data["next_collection_link"] != "") { - try { - if (data.contains("entries") && data["entries"].is_array()) { - for (auto& entry : data["entries"]) { - auto s_opt = archive_permission::parse(entry.dump()); - if (!s_opt) continue; - s_opt->set_lp(lp); - co_yield s_opt.value(); - } + while (true) { + if (data.contains("entries") && data["entries"].is_array()) { + for (auto& entry : data["entries"]) { + auto s_opt = archive_permission::parse(entry.dump()); + if (!s_opt) continue; + s_opt->set_lp(lp); + co_yield s_opt.value(); } - response = lp->api_get(data["next_collection_link"]); - data = nlohmann::json::parse(response.value()); - } catch (const std::exception& e) { - std::cerr << "Error parsing published sources: " << e.what() << std::endl; } + if (!data.contains("next_collection_link") || data["next_collection_link"].is_null() || data["next_collection_link"] == "") break; + response = lp->api_get(data["next_collection_link"].get()); + if(!response) break; + data = nlohmann::json::parse(response.value()); } } @@ -162,27 +157,782 @@ std::generator archive::getPublishedSources(c std::map params; params["ws.op"] = "getPublishedSources"; params["status"] = status; + auto response = lp->api_get(self_link, params); + if(!response) co_return; + auto data = nlohmann::json::parse(response.value()); + while (true) { + if (data.contains("entries") && data["entries"].is_array()) { + for (auto& entry : data["entries"]) { + auto s_opt = source_package_publishing_history::parse(entry.dump()); + if (!s_opt) continue; + s_opt->set_lp(lp); + co_yield s_opt.value(); + } + } + if (!data.contains("next_collection_link") || data["next_collection_link"].is_null() || data["next_collection_link"] == "") break; + response = lp->api_get(data["next_collection_link"].get()); + if(!response) break; + data = nlohmann::json::parse(response.value()); + } +} + +std::optional archive::checkUpload(const std::string& component, + const std::string& distroseries, + const std::string& person, + const std::string& pocket, + const std::string& sourcepackagename, + bool strict_component) const { + std::map params; + params["ws.op"] = "checkUpload"; + params["component"] = component; + params["distroseries"] = distroseries; + params["person"] = person; + params["pocket"] = pocket; + params["sourcepackagename"] = sourcepackagename; + if(strict_component) params["strict_component"] = "true"; - auto response = lp->api_get(this->self_link, params); + auto response = lp->api_get(self_link, params); + if(!response) return std::nullopt; + return nlohmann::json::parse(response.value()); +} + +std::optional archive::getArchiveDependency(const std::string& dependency_link) const { + std::map params; + params["ws.op"] = "getArchiveDependency"; + params["dependency"] = dependency_link; + auto response = lp->api_get(self_link, params); + if(!response) return std::nullopt; + auto ad_opt = archive_dependency::parse(response.value()); + if(ad_opt) ad_opt->set_lp(lp); + return ad_opt; +} + +std::optional archive::getBuildCounters(bool include_needsbuild) const { + std::map params; + params["ws.op"] = "getBuildCounters"; + if(include_needsbuild) params["include_needsbuild"]="true"; + auto response = lp->api_get(self_link, params); + if(!response) return std::nullopt; + return nlohmann::json::parse(response.value()); +} + +std::generator archive::getBuildRecords(const std::string& build_state, + const std::string& pocket, + const std::string& source_name) const { + std::map params; + params["ws.op"] = "getBuildRecords"; + if(!build_state.empty()) params["build_state"] = build_state; + if(!pocket.empty()) params["pocket"] = pocket; + if(!source_name.empty()) params["source_name"] = source_name; + auto response = lp->api_get(self_link, params); + if(!response) co_return; auto data = nlohmann::json::parse(response.value()); - while (response.has_value() && data.contains("next_collection_link") && data["next_collection_link"] != "") { - try { - if (data.contains("entries") && data["entries"].is_array()) { - for (auto& entry : data["entries"]) { - auto s_opt = source_package_publishing_history::parse(entry.dump()); - if (!s_opt) continue; - s_opt->set_lp(lp); - co_yield s_opt.value(); - } + while (true) { + if (data.contains("entries") && data["entries"].is_array()) { + for (auto& e : data["entries"]) { + build_record br; + br.data = e; + co_yield br; } - response = lp->api_get(data["next_collection_link"]); - data = nlohmann::json::parse(response.value()); - } catch (const std::exception& e) { - std::cerr << "Error parsing published sources: " << e.what() << std::endl; } + if(!data.contains("next_collection_link") || data["next_collection_link"].is_null() || data["next_collection_link"]=="") break; + response = lp->api_get(data["next_collection_link"].get()); + if(!response) break; + data = nlohmann::json::parse(response.value()); } } -void archive::set_lp(launchpad* lp_ptr) { - lp = lp_ptr; +std::optional archive::getBuildSummariesForSourceIds(const std::vector& source_ids) const { + std::map params; + params["ws.op"] = "getBuildSummariesForSourceIds"; + std::string joined; + for (auto &sid : source_ids) { + joined += sid + ","; + } + if(!joined.empty()) joined.pop_back(); + if(!joined.empty()) params["source_ids"] = joined; + auto response = lp->api_get(self_link, params); + if(!response) return std::nullopt; + return nlohmann::json::parse(response.value()); +} + +std::generator archive::getComponentsForQueueAdmin(const std::string& person_link) const { + std::map params; + params["ws.op"] = "getComponentsForQueueAdmin"; + params["person"] = person_link; + auto response = lp->api_get(self_link, params); + if(!response) co_return; + auto data = nlohmann::json::parse(response.value()); + if(data.contains("entries") && data["entries"].is_array()){ + for(auto& e : data["entries"]){ + auto ap = archive_permission::parse(e.dump()); + if(ap) { + ap->set_lp(lp); + co_yield ap.value(); + } + } + } +} + +std::optional archive::getNamedAuthToken(const std::string& name) const { + std::map params; + params["ws.op"]="getNamedAuthToken"; + params["name"]=name; + auto response = lp->api_get(self_link, params); + if(!response) return std::nullopt; + return nlohmann::json::parse(response.value()); +} + +std::optional archive::getNamedAuthTokens(const std::vector& names) const { + std::map params; + params["ws.op"]="getNamedAuthTokens"; + if(!names.empty()){ + std::string joined; + for(auto& n:names) joined+=n+","; + if(!joined.empty()) joined.pop_back(); + params["names"]=joined; + } + auto response = lp->api_get(self_link, params); + if(!response) return std::nullopt; + return nlohmann::json::parse(response.value()); +} + +std::generator archive::getPackagesetsForSource(const std::string& sourcepackagename, + bool direct_permissions) const { + std::map params; + params["ws.op"]="getPackagesetsForSource"; + params["sourcepackagename"]=sourcepackagename; + if(direct_permissions) params["direct_permissions"]="true"; + auto response=lp->api_get(self_link, params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries") && data["entries"].is_array()){ + for(auto&e: data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap) {ap->set_lp(lp); co_yield ap.value();} + } + } +} + +std::generator archive::getPackagesetsForSourceUploader(const std::string& person_link, + const std::string& sourcepackagename) const { + std::map params; + params["ws.op"]="getPackagesetsForSourceUploader"; + params["person"]=person_link; + params["sourcepackagename"]=sourcepackagename; + auto response=lp->api_get(self_link, params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries")&&data["entries"].is_array()){ + for(auto&e:data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap){ap->set_lp(lp);co_yield ap.value();} + } + } +} + +std::generator archive::getPackagesetsForUploader(const std::string& person_link) const { + std::map params; + params["ws.op"]="getPackagesetsForUploader"; + params["person"]=person_link; + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries") && data["entries"].is_array()){ + for(auto&e:data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap){ap->set_lp(lp);co_yield ap.value();} + } + } +} + +std::generator archive::getPermissionsForPerson(const std::string& person_link) const { + std::map params; + params["ws.op"]="getPermissionsForPerson"; + params["person"]=person_link; + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries")){ + for(auto&e:data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap){ap->set_lp(lp);co_yield ap.value();} + } + } +} + +std::generator archive::getPocketsForQueueAdmin(const std::string& person_link) const { + std::mapparams; + params["ws.op"]="getPocketsForQueueAdmin"; + params["person"]=person_link; + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries")){ + for(auto&e:data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap){ap->set_lp(lp);co_yield ap.value();} + } + } +} + +std::generator archive::getPocketsForUploader(const std::string& person_link) const { + std::mapparams; + params["ws.op"]="getPocketsForUploader"; + params["person"]=person_link; + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries")){ + for(auto&e:data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap){ap->set_lp(lp);co_yield ap.value();} + } + } +} + +std::generator archive::getPublishedBinaries(const std::string& binary_name, + const std::string& component_name, + const std::string& created_since_date, + const std::string& distro_arch_series, + bool exact_match, + bool order_by_date, + bool ordered, + const std::string& pocket, + const std::string& status, + const std::string& version) const { + std::map params; + params["ws.op"]="getPublishedBinaries"; + if(!binary_name.empty()) params["binary_name"]=binary_name; + if(!component_name.empty()) params["component_name"]=component_name; + if(!created_since_date.empty()) params["created_since_date"]=created_since_date; + if(!distro_arch_series.empty()) params["distro_arch_series"]=distro_arch_series; + if(exact_match) params["exact_match"]="true"; + if(order_by_date) params["order_by_date"]="true"; + if(!ordered) params["ordered"]="false"; + if(!pocket.empty()) params["pocket"]=pocket; + if(!status.empty()) params["status"]=status; + if(!version.empty()) params["version"]=version; + + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + while(true){ + if(data.contains("entries") && data["entries"].is_array()){ + for(auto&e:data["entries"]){ + auto bpph = binary_package_publishing_history::parse(e.dump()); + if(bpph){bpph->set_lp(lp);co_yield bpph.value();} + } + } + if(!data.contains("next_collection_link")||data["next_collection_link"].is_null()||data["next_collection_link"]=="")break; + response=lp->api_get(data["next_collection_link"].get()); + if(!response)break; + data=nlohmann::json::parse(response.value()); + } +} + +std::generator archive::getQueueAdminsForComponent(const std::string& component_name) const { + std::mapparams; + params["ws.op"]="getQueueAdminsForComponent"; + params["component_name"]=component_name; + auto response=lp->api_get(self_link,params); + if(!response)co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries")){ + for(auto&e:data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap){ap->set_lp(lp); co_yield ap.value();} + } + } +} + +std::generator archive::getQueueAdminsForPocket(const std::string& distroseries, + const std::string& pocket) const { + std::mapparams; + params["ws.op"]="getQueueAdminsForPocket"; + params["distroseries"]=distroseries; + params["pocket"]=pocket; + auto response=lp->api_get(self_link,params); + if(!response)co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries")){ + for(auto&e:data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap){ap->set_lp(lp);co_yield ap.value();} + } + } +} + +std::optional archive::getSigningKeyData() const { + std::mapparams; + params["ws.op"]="getSigningKeyData"; + auto response=lp->api_get(self_link,params); + if(!response) return std::nullopt; + auto j=nlohmann::json::parse(response.value()); + if(j.is_string()) return j.get(); + return std::nullopt; +} + +std::generator archive::getUploadersForComponent(const std::string& component_name) const { + std::mapparams; + params["ws.op"]="getUploadersForComponent"; + if(!component_name.empty()) params["component_name"]=component_name; + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries")){ + for(auto&e:data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap){ap->set_lp(lp);co_yield ap.value();} + } + } +} + +std::generator archive::getUploadersForPackage(const std::string& source_package_name) const { + std::mapparams; + params["ws.op"]="getUploadersForPackage"; + params["source_package_name"]=source_package_name; + auto response=lp->api_get(self_link,params); + if(!response)co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries")){ + for(auto&e:data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap){ap->set_lp(lp);co_yield ap.value();} + } + } +} + +std::generator archive::getUploadersForPackageset(const std::string& packageset, + bool direct_permissions) const { + std::mapparams; + params["ws.op"]="getUploadersForPackageset"; + params["packageset"]=packageset; + if(direct_permissions) params["direct_permissions"]="true"; + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries")){ + for(auto&e:data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap){ap->set_lp(lp);co_yield ap.value();} + } + } +} + +std::generator archive::getUploadersForPocket(const std::string& pocket) const { + std::mapparams; + params["ws.op"]="getUploadersForPocket"; + params["pocket"]=pocket; + auto response=lp->api_get(self_link,params); + if(!response)co_return; + auto data=nlohmann::json::parse(response.value()); + if(data.contains("entries")){ + for(auto&e:data["entries"]){ + auto ap=archive_permission::parse(e.dump()); + if(ap){ap->set_lp(lp); co_yield ap.value();} + } + } +} + +std::optional archive::isSourceUploadAllowed(const std::string& distroseries, + const std::string& person, + const std::string& sourcepackagename) const { + std::map params; + params["ws.op"]="isSourceUploadAllowed"; + params["distroseries"]=distroseries; + params["person"]=person; + params["sourcepackagename"]=sourcepackagename; + auto response=lp->api_get(self_link,params); + if(!response)return std::nullopt; + auto j=nlohmann::json::parse(response.value()); + if(j.is_boolean()) return j.get(); + return std::nullopt; +} + +std::optional archive::addArchiveDependency(const std::string& dependency_link, + const std::string& component, + const std::string& pocket) const { + std::mapparams; + params["ws.op"]="addArchiveDependency"; + params["dependency"]=dependency_link; + if(!component.empty()) params["component"]=component; + params["pocket"]=pocket; + auto response=lp->api_post(self_link,params); + if(!response) return std::nullopt; + auto ad=archive_dependency::parse(response.value()); + if(ad) ad->set_lp(lp); + return ad; +} + +bool archive::copyPackage(const std::string& from_archive, + const std::string& from_pocket, + const std::string& from_series, + bool include_binaries, + bool move, + std::optional phased_update_percentage, + bool silent, + const std::string& source_name, + const std::string& sponsored, + const std::string& to_pocket, + const std::string& to_series, + bool unembargo, + const std::string& version, + bool auto_approve) const { + std::mapparams; + params["ws.op"]="copyPackage"; + params["from_archive"]=from_archive; + if(!from_pocket.empty())params["from_pocket"]=from_pocket; + if(!from_series.empty())params["from_series"]=from_series; + if(include_binaries)params["include_binaries"]="true"; + if(move)params["move"]="true"; + if(phased_update_percentage.has_value())params["phased_update_percentage"]=std::to_string(phased_update_percentage.value()); + if(silent)params["silent"]="true"; + params["source_name"]=source_name; + if(!sponsored.empty()) params["sponsored"]=sponsored; + params["to_pocket"]=to_pocket; + if(!to_series.empty())params["to_series"]=to_series; + if(unembargo)params["unembargo"]="true"; + params["version"]=version; + if(auto_approve)params["auto_approve"]="true"; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::copyPackages(const std::string& from_archive, + const std::string& from_series, + bool include_binaries, + bool silent, + const std::vector& source_names, + const std::string& sponsored, + const std::string& to_pocket, + const std::string& to_series, + bool unembargo, + bool auto_approve) const { + std::map params; + params["ws.op"]="copyPackages"; + params["from_archive"]=from_archive; + if(!from_series.empty()) params["from_series"]=from_series; + if(include_binaries) params["include_binaries"]="true"; + if(silent) params["silent"]="true"; + { + std::string joined; + for(auto&s:source_names)joined+=s+","; + if(!joined.empty())joined.pop_back(); + if(!joined.empty()) params["source_names"]=joined; + } + if(!sponsored.empty()) params["sponsored"]=sponsored; + params["to_pocket"]=to_pocket; + if(!to_series.empty()) params["to_series"]=to_series; + if(unembargo)params["unembargo"]="true"; + if(auto_approve)params["auto_approve"]="true"; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::deleteComponentUploader(const std::string& component_name, + const std::string& person) const { + std::mapparams; + params["ws.op"]="deleteComponentUploader"; + params["component_name"]=component_name; + params["person"]=person; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::deletePackageUploader(const std::string& person, + const std::string& source_package_name) const { + std::mapparams; + params["ws.op"]="deletePackageUploader"; + params["person"]=person; + params["source_package_name"]=source_package_name; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::deletePackagesetUploader(const std::string& packageset, + const std::string& person, + bool explicit_) const { + std::map params; + params["ws.op"]="deletePackagesetUploader"; + params["packageset"]=packageset; + if(explicit_)params["explicit"]="true"; + params["person"]=person; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::deletePocketQueueAdmin(const std::string& distroseries, + const std::string& person, + const std::string& pocket) const { + std::mapparams; + params["ws.op"]="deletePocketQueueAdmin"; + if(!distroseries.empty())params["distroseries"]=distroseries; + params["person"]=person; + params["pocket"]=pocket; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::deletePocketUploader(const std::string& person, + const std::string& pocket) const { + std::mapparams; + params["ws.op"]="deletePocketUploader"; + params["person"]=person; + params["pocket"]=pocket; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::deleteQueueAdmin(const std::string& component_name, + const std::string& person) const { + std::mapparams; + params["ws.op"]="deleteQueueAdmin"; + params["component_name"]=component_name; + params["person"]=person; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::enableRestrictedProcessor(const std::string& processor) const { + std::mapparams; + params["ws.op"]="enableRestrictedProcessor"; + params["processor"]=processor; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::markSuiteDirty(const std::string& distroseries, + const std::string& pocket) const { + std::mapparams; + params["ws.op"]="markSuiteDirty"; + params["distroseries"]=distroseries; + params["pocket"]=pocket; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +std::optional archive::newComponentUploader(const std::string& component_name, + const std::string& person) const { + std::mapparams; + params["ws.op"]="newComponentUploader"; + params["component_name"]=component_name; + params["person"]=person; + auto resp=lp->api_post(self_link,params); + if(!resp)return std::nullopt; + auto ap=archive_permission::parse(resp.value()); + if(ap)ap->set_lp(lp); + return ap; +} + +std::optional archive::newNamedAuthToken(const std::string& name, + const std::optional& token) const { + std::mapparams; + params["ws.op"]="newNamedAuthToken"; + params["name"]=name; + if(token.has_value())params["token"]=token.value(); + auto resp=lp->api_post(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +std::optional archive::newNamedAuthTokens(const std::vector& names) const { + std::mapparams; + params["ws.op"]="newNamedAuthTokens"; + if(!names.empty()){ + std::string joined; + for(auto&n:names)joined+=n+","; + if(!joined.empty())joined.pop_back(); + params["names"]=joined; + } + auto resp=lp->api_post(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +std::optional archive::newPackageUploader(const std::string& person, + const std::string& source_package_name) const { + std::mapparams; + params["ws.op"]="newPackageUploader"; + params["person"]=person; + params["source_package_name"]=source_package_name; + auto resp=lp->api_post(self_link,params); + if(!resp)return std::nullopt; + auto ap=archive_permission::parse(resp.value()); + if(ap)ap->set_lp(lp); + return ap; +} + +std::optional archive::newPackagesetUploader(const std::string& packageset, + const std::string& person, + bool explicit_) const { + std::mapparams; + params["ws.op"]="newPackagesetUploader"; + params["packageset"]=packageset; + if(explicit_)params["explicit"]="true"; + params["person"]=person; + auto resp=lp->api_post(self_link,params); + if(!resp)return std::nullopt; + auto ap=archive_permission::parse(resp.value()); + if(ap)ap->set_lp(lp); + return ap; +} + +std::optional archive::newPocketQueueAdmin(const std::string& distroseries, + const std::string& person, + const std::string& pocket) const { + std::mapparams; + params["ws.op"]="newPocketQueueAdmin"; + if(!distroseries.empty())params["distroseries"]=distroseries; + params["person"]=person; + params["pocket"]=pocket; + auto resp=lp->api_post(self_link,params); + if(!resp)return std::nullopt; + auto ap=archive_permission::parse(resp.value()); + if(ap)ap->set_lp(lp); + return ap; +} + +std::optional archive::newPocketUploader(const std::string& person, + const std::string& pocket) const { + std::mapparams; + params["ws.op"]="newPocketUploader"; + params["person"]=person; + params["pocket"]=pocket; + auto resp=lp->api_post(self_link,params); + if(!resp)return std::nullopt; + auto ap=archive_permission::parse(resp.value()); + if(ap)ap->set_lp(lp); + return ap; +} + +std::optional archive::newQueueAdmin(const std::string& component_name, + const std::string& person) const { + std::mapparams; + params["ws.op"]="newQueueAdmin"; + params["component_name"]=component_name; + params["person"]=person; + auto resp=lp->api_post(self_link,params); + if(!resp)return std::nullopt; + auto ap=archive_permission::parse(resp.value()); + if(ap)ap->set_lp(lp); + return ap; +} + +std::optional archive::newSubscription(const std::string& subscriber, + const std::optional& date_expires, + const std::optional& description) const { + std::mapparams; + params["ws.op"]="newSubscription"; + params["subscriber"]=subscriber; + if(date_expires.has_value()) params["date_expires"]=date_expires.value(); + if(description.has_value()) params["description"]=description.value(); + auto resp=lp->api_post(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +bool archive::removeArchiveDependency(const std::string& dependency_link) const { + std::mapparams; + params["ws.op"]="removeArchiveDependency"; + params["dependency"]=dependency_link; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::removeCopyNotification(const std::string& job_id) const { + std::mapparams; + params["ws.op"]="removeCopyNotification"; + params["job_id"]=job_id; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::revokeNamedAuthToken(const std::string& name) const { + std::mapparams; + params["ws.op"]="revokeNamedAuthToken"; + params["name"]=name; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::revokeNamedAuthTokens(const std::vector& names) const { + std::mapparams; + params["ws.op"]="revokeNamedAuthTokens"; + if(!names.empty()){ + std::string joined; + for(auto&n:names)joined+=n+","; + if(!joined.empty())joined.pop_back(); + params["names"]=joined; + } + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::setProcessors(const std::vector& processors) const { + std::mapparams; + params["ws.op"]="setProcessors"; + { + std::string joined; + for(auto&p:processors)joined+=p+","; + if(!joined.empty())joined.pop_back(); + params["processors"]=joined; + } + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::syncSource(const std::string& from_archive, + bool include_binaries, + const std::string& source_name, + const std::string& to_pocket, + const std::string& to_series, + const std::string& version) const { + std::mapparams; + params["ws.op"]="syncSource"; + params["from_archive"]=from_archive; + if(include_binaries)params["include_binaries"]="true"; + params["source_name"]=source_name; + params["to_pocket"]=to_pocket; + if(!to_series.empty())params["to_series"]=to_series; + params["version"]=version; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::syncSources(const std::string& from_archive, + const std::string& from_series, + bool include_binaries, + const std::vector& source_names, + const std::string& to_pocket, + const std::string& to_series) const { + std::mapparams; + params["ws.op"]="syncSources"; + params["from_archive"]=from_archive; + if(!from_series.empty())params["from_series"]=from_series; + if(include_binaries)params["include_binaries"]="true"; + { + std::string joined; + for(auto&s:source_names)joined+=s+","; + if(!joined.empty())joined.pop_back(); + if(!joined.empty()) params["source_names"]=joined; + } + params["to_pocket"]=to_pocket; + if(!to_series.empty())params["to_series"]=to_series; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool archive::uploadCIBuild(const std::string& ci_build, + const std::string& to_channel, + const std::string& to_pocket, + const std::string& to_series) const { + std::mapparams; + params["ws.op"]="uploadCIBuild"; + params["ci_build"]=ci_build; + if(!to_channel.empty())params["to_channel"]=to_channel; + params["to_pocket"]=to_pocket; + params["to_series"]=to_series; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); } diff --git a/src/archive.h b/src/archive.h index 31266f3..5c9f760 100644 --- a/src/archive.h +++ b/src/archive.h @@ -17,6 +17,9 @@ #define ARCHIVE_H #include "source_package_publishing_history.h" +#include "archive_permission.h" +#include "binary_package_publishing_history.h" +#include "archive_dependency.h" #include #include #include @@ -25,15 +28,11 @@ class launchpad; -#ifndef LAUNCHPAD_API -#ifdef BUILDING_LAUNCHPAD - #define LAUNCHPAD_API __attribute__((visibility("default"))) -#else - #define LAUNCHPAD_API -#endif -#endif +struct build_record { + nlohmann::json data; +}; -class LAUNCHPAD_API archive { +class archive { public: archive(); ~archive(); @@ -42,7 +41,6 @@ public: void parse_json(const std::string& json_data); void set_lp(launchpad* lp_ptr); - // New public member variables int authorized_size; bool build_debug_symbols; std::string description; @@ -66,7 +64,6 @@ public: std::string ppa_name; std::string self_link; - // Read-only fields std::string dependencies_collection_link; std::string enabled_restricted_processors_collection_link; std::string http_etag; @@ -76,14 +73,145 @@ public: std::string status; std::string web_link; - // Public functions std::generator getPublishedSources(const std::string& status) const; - std::generator archive::getAllPermissions() const; + std::generator getAllPermissions() const; - launchpad* lp; + std::optional checkUpload(const std::string& component, + const std::string& distroseries, + const std::string& person, + const std::string& pocket, + const std::string& sourcepackagename, + bool strict_component = false) const; + std::optional getArchiveDependency(const std::string& dependency_link) const; + std::optional getBuildCounters(bool include_needsbuild = false) const; + std::generator getBuildRecords(const std::string& build_state = "", + const std::string& pocket = "", + const std::string& source_name = "") const; + std::optional getBuildSummariesForSourceIds(const std::vector& source_ids) const; + std::generator getComponentsForQueueAdmin(const std::string& person_link) const; + std::optional getNamedAuthToken(const std::string& name) const; + std::optional getNamedAuthTokens(const std::vector& names = {}) const; + std::generator getPackagesetsForSource(const std::string& sourcepackagename, + bool direct_permissions = false) const; + std::generator getPackagesetsForSourceUploader(const std::string& person_link, + const std::string& sourcepackagename) const; + std::generator getPackagesetsForUploader(const std::string& person_link) const; + std::generator getPermissionsForPerson(const std::string& person_link) const; + std::generator getPocketsForQueueAdmin(const std::string& person_link) const; + std::generator getPocketsForUploader(const std::string& person_link) const; + std::generator getPublishedBinaries(const std::string& binary_name = "", + const std::string& component_name = "", + const std::string& created_since_date = "", + const std::string& distro_arch_series = "", + bool exact_match = false, + bool order_by_date = false, + bool ordered = true, + const std::string& pocket = "", + const std::string& status = "", + const std::string& version = "") const; + std::generator getQueueAdminsForComponent(const std::string& component_name) const; + std::generator getQueueAdminsForPocket(const std::string& distroseries, + const std::string& pocket) const; + std::optional getSigningKeyData() const; + std::generator getUploadersForComponent(const std::string& component_name) const; + std::generator getUploadersForPackage(const std::string& source_package_name) const; + std::generator getUploadersForPackageset(const std::string& packageset, + bool direct_permissions = false) const; + std::generator getUploadersForPocket(const std::string& pocket) const; + std::optional isSourceUploadAllowed(const std::string& distroseries, + const std::string& person, + const std::string& sourcepackagename) const; + + // Custom POST methods + std::optional addArchiveDependency(const std::string& dependency_link, + const std::string& component, + const std::string& pocket) const; + bool copyPackage(const std::string& from_archive, + const std::string& from_pocket, + const std::string& from_series, + bool include_binaries, + bool move, + std::optional phased_update_percentage, + bool silent, + const std::string& source_name, + const std::string& sponsored, + const std::string& to_pocket, + const std::string& to_series, + bool unembargo, + const std::string& version, + bool auto_approve = false) const; + bool copyPackages(const std::string& from_archive, + const std::string& from_series, + bool include_binaries, + bool silent, + const std::vector& source_names, + const std::string& sponsored, + const std::string& to_pocket, + const std::string& to_series, + bool unembargo, + bool auto_approve = false) const; + bool deleteComponentUploader(const std::string& component_name, + const std::string& person) const; + bool deletePackageUploader(const std::string& person, + const std::string& source_package_name) const; + bool deletePackagesetUploader(const std::string& packageset, + const std::string& person, + bool explicit_) const; + bool deletePocketQueueAdmin(const std::string& distroseries, + const std::string& person, + const std::string& pocket) const; + bool deletePocketUploader(const std::string& person, + const std::string& pocket) const; + bool deleteQueueAdmin(const std::string& component_name, + const std::string& person) const; + bool enableRestrictedProcessor(const std::string& processor) const; + bool markSuiteDirty(const std::string& distroseries, + const std::string& pocket) const; + std::optional newComponentUploader(const std::string& component_name, + const std::string& person) const; + std::optional newNamedAuthToken(const std::string& name, + const std::optional& token) const; + std::optional newNamedAuthTokens(const std::vector& names) const; + std::optional newPackageUploader(const std::string& person, + const std::string& source_package_name) const; + std::optional newPackagesetUploader(const std::string& packageset, + const std::string& person, + bool explicit_) const; + std::optional newPocketQueueAdmin(const std::string& distroseries, + const std::string& person, + const std::string& pocket) const; + std::optional newPocketUploader(const std::string& person, + const std::string& pocket) const; + std::optional newQueueAdmin(const std::string& component_name, + const std::string& person) const; + std::optional newSubscription(const std::string& subscriber, + const std::optional& date_expires, + const std::optional& description) const; + bool removeArchiveDependency(const std::string& dependency_link) const; + bool removeCopyNotification(const std::string& job_id) const; + bool revokeNamedAuthToken(const std::string& name) const; + bool revokeNamedAuthTokens(const std::vector& names) const; + bool setProcessors(const std::vector& processors) const; + bool syncSource(const std::string& from_archive, + bool include_binaries, + const std::string& source_name, + const std::string& to_pocket, + const std::string& to_series, + const std::string& version) const; + bool syncSources(const std::string& from_archive, + const std::string& from_series, + bool include_binaries, + const std::vector& source_names, + const std::string& to_pocket, + const std::string& to_series) const; + bool uploadCIBuild(const std::string& ci_build, + const std::string& to_channel, + const std::string& to_pocket, + const std::string& to_series) const; private: std::string build_archive_endpoint() const; + launchpad* lp; }; #endif // ARCHIVE_H diff --git a/src/archive_dependency.cpp b/src/archive_dependency.cpp new file mode 100644 index 0000000..e0596b7 --- /dev/null +++ b/src/archive_dependency.cpp @@ -0,0 +1,79 @@ +#include "archive_dependency.h" +#include "launchpad.h" +#include +#include + +archive_dependency::archive_dependency() + : lp(nullptr) {} + +archive_dependency::~archive_dependency() {} + +std::optional archive_dependency::parse(const std::string& json_data) { + archive_dependency ad; + ad.parse_json(json_data); + return ad; +} + +void archive_dependency::parse_json(const std::string& json_data) { + if (json_data.empty()) { + std::cerr << "Error: Empty JSON data for archive_dependency::parse_json." << std::endl; + return; + } + + try { + auto data = nlohmann::json::parse(json_data); + + std::map> fields = { + {"archive_link", [this](const nlohmann::json& val) { archive_link = val.get(); }}, + {"component_name", [this](const nlohmann::json& val) { component_name = val.get(); }}, + {"date_created", [this](const nlohmann::json& val) { date_created = val.get(); }}, + {"dependency_link", [this](const nlohmann::json& val) { dependency_link = val.get(); }}, + {"http_etag", [this](const nlohmann::json& val) { http_etag = val.get(); }}, + {"pocket", [this](const nlohmann::json& val) { pocket = val.get(); }}, + {"resource_type_link", [this](const nlohmann::json& val) { resource_type_link = val.get(); }}, + {"self_link", [this](const nlohmann::json& val) { self_link = val.get(); }}, + {"snap_base_link", [this](const nlohmann::json& val) { snap_base_link = val.get(); }}, + {"title", [this](const nlohmann::json& val) { title = val.get(); }} + }; + + for (auto& [key, value] : data.items()) { + if (fields.find(key) != fields.end()) { + try { + fields[key](value); + fields.erase(key); + } catch (...) { + continue; + } + } + } + + } catch (const nlohmann::json::parse_error& e) { + std::cerr << "JSON parse error in archive_dependency::parse_json: " << e.what() << std::endl; + } catch (const std::exception& e) { + std::cerr << "Unexpected error during JSON parsing: " << e.what() << std::endl; + } +} + +void archive_dependency::set_lp(launchpad* lp_ptr) { + lp = lp_ptr; +} + +std::optional archive_dependency::get() const { + if(self_link.empty())return std::nullopt; + auto resp=lp->api_get(self_link); + if(!resp)return std::nullopt; + return parse(resp.value()); +} + +bool archive_dependency::patch(const nlohmann::json& data) const { + if(self_link.empty())return false; + auto resp=lp->api_patch(self_link,data); + return resp.has_value(); +} + +bool archive_dependency::put(const nlohmann::json& data) const { + if(self_link.empty())return false; + std::string endpoint=self_link+"?ws.op=replace"; + auto resp=lp->api_patch(endpoint,data); + return resp.has_value(); +} diff --git a/src/archive_dependency.h b/src/archive_dependency.h new file mode 100644 index 0000000..95e992b --- /dev/null +++ b/src/archive_dependency.h @@ -0,0 +1,38 @@ +#ifndef ARCHIVE_DEPENDENCY_H +#define ARCHIVE_DEPENDENCY_H + +#include +#include +#include + +class launchpad; + +class archive_dependency { +public: + archive_dependency(); + ~archive_dependency(); + + static std::optional parse(const std::string& json_data); + void parse_json(const std::string& json_data); + void set_lp(launchpad* lp_ptr); + + std::string archive_link; + std::string component_name; + std::string date_created; + std::string dependency_link; + std::string http_etag; + std::string pocket; + std::string resource_type_link; + std::string self_link; + std::string snap_base_link; + std::string title; + + std::optional get() const; + bool patch(const nlohmann::json& data) const; + bool put(const nlohmann::json& data) const; + +private: + launchpad* lp; +}; + +#endif diff --git a/src/archive_permission.cpp b/src/archive_permission.cpp index ad69f23..b3619df 100644 --- a/src/archive_permission.cpp +++ b/src/archive_permission.cpp @@ -15,9 +15,11 @@ #include "archive_permission.h" #include "launchpad.h" +#include "distro_series.h" #include #include #include +#include archive_permission::archive_permission() : explicit_(false), @@ -39,8 +41,6 @@ void archive_permission::parse_json(const std::string& json_data) { try { auto data = nlohmann::json::parse(json_data); - - // Map JSON keys to lambdas to parse std::map> json_map = { {"archive_link", [this](const nlohmann::json& val) { archive_link = val.get(); }}, {"component_name", [this](const nlohmann::json& val) { component_name = val.get(); }}, @@ -70,8 +70,7 @@ void archive_permission::parse_json(const std::string& json_data) { readonly_map[key](value); readonly_map.erase(key); } - } catch (const std::exception& e) { - // If parsing fails for a field, just continue + } catch (const std::exception&) { continue; } } @@ -86,3 +85,42 @@ void archive_permission::parse_json(const std::string& json_data) { void archive_permission::set_lp(launchpad* lp_ptr) { lp = lp_ptr; } + +std::optional archive_permission::get() const { + if(self_link.empty())return std::nullopt; + auto resp=lp->api_get(self_link); + if(!resp)return std::nullopt; + return parse(resp.value()); +} + +bool archive_permission::patch(const nlohmann::json& data) const { + if(self_link.empty())return false; + auto resp=lp->api_patch(self_link,data); + return resp.has_value(); +} + +bool archive_permission::put(const nlohmann::json& data) const { + if(self_link.empty())return false; + std::string endpoint=self_link+"?ws.op=replace"; + auto resp=lp->api_patch(endpoint,data); + return resp.has_value(); +} + +std::generator archive_permission::getDistroSeries() const { + if (distroseries_link.empty() || !lp) { + co_return; + } + + auto response = lp->api_get(distroseries_link); + if (!response.has_value()) { + co_return; + } + + auto ds_opt = distro_series::parse(response.value()); + if (!ds_opt) { + co_return; + } + + ds_opt->set_lp(lp); + co_yield ds_opt.value(); +} diff --git a/src/archive_permission.h b/src/archive_permission.h index dfabf7d..497ee81 100644 --- a/src/archive_permission.h +++ b/src/archive_permission.h @@ -19,18 +19,12 @@ #include #include #include +#include +#include "distro_series.h" class launchpad; -#ifndef LAUNCHPAD_API -#ifdef BUILDING_LAUNCHPAD - #define LAUNCHPAD_API __attribute__((visibility("default"))) -#else - #define LAUNCHPAD_API -#endif -#endif - -class LAUNCHPAD_API archive_permission { +class archive_permission { public: archive_permission(); ~archive_permission(); @@ -39,8 +33,6 @@ public: void set_lp(launchpad* lp_ptr); - // Public member variables (fields) - // Writable fields std::string archive_link; std::string component_name; std::string date_created; @@ -53,14 +45,19 @@ public: std::string pocket; std::string source_package_name; - // Read-only fields std::string http_etag; std::string resource_type_link; std::string self_link; + std::optional get() const; + bool patch(const nlohmann::json& data) const; + bool put(const nlohmann::json& data) const; + + std::generator getDistroSeries() const; + private: void parse_json(const std::string& json_data); launchpad* lp; }; -#endif // ARCHIVE_PERMISSION_H +#endif diff --git a/src/binary_package_publishing_history.cpp b/src/binary_package_publishing_history.cpp new file mode 100644 index 0000000..7c7f13d --- /dev/null +++ b/src/binary_package_publishing_history.cpp @@ -0,0 +1,178 @@ +#include "binary_package_publishing_history.h" +#include "launchpad.h" +#include +#include +#include +#include + +binary_package_publishing_history::binary_package_publishing_history() + : architecture_specific(false), + is_debug(false), + phased_update_percentage(-1), + lp(nullptr) {} + +binary_package_publishing_history::~binary_package_publishing_history() {} + +std::optional binary_package_publishing_history::parse(const std::string& json_data) { + binary_package_publishing_history b; + b.parse_json(json_data); + return b; +} + +void binary_package_publishing_history::parse_json(const std::string& json_data) { + if (json_data.empty()) { + std::cerr << "Error: Empty JSON data for binary_package_publishing_history::parse_json." << std::endl; + return; + } + + try { + auto data = nlohmann::json::parse(json_data); + + std::map> fields = { + {"architecture_specific", [this](const nlohmann::json& val) { architecture_specific = val.get(); }}, + {"archive_link", [this](const nlohmann::json& val) { archive_link = val.get(); }}, + {"binary_package_name", [this](const nlohmann::json& val) { binary_package_name = val.get(); }}, + {"binary_package_version", [this](const nlohmann::json& val) { binary_package_version = val.get(); }}, + {"build_link", [this](const nlohmann::json& val) { build_link = val.get(); }}, + {"component_name", [this](const nlohmann::json& val) { component_name = val.get(); }}, + {"copied_from_archive_link", [this](const nlohmann::json& val) { copied_from_archive_link = val.get(); }}, + {"creator_link", [this](const nlohmann::json& val) { creator_link = val.get(); }}, + {"date_created", [this](const nlohmann::json& val) { date_created = val.get(); }}, + {"date_made_pending", [this](const nlohmann::json& val) { date_made_pending = val.get(); }}, + {"date_published", [this](const nlohmann::json& val) { date_published = val.get(); }}, + {"date_removed", [this](const nlohmann::json& val) { date_removed = val.get(); }}, + {"date_superseded", [this](const nlohmann::json& val) { date_superseded = val.get(); }}, + {"display_name", [this](const nlohmann::json& val) { display_name = val.get(); }}, + {"distro_arch_series_link", [this](const nlohmann::json& val) { distro_arch_series_link = val.get(); }}, + {"http_etag", [this](const nlohmann::json& val) { http_etag = val.get(); }}, + {"is_debug", [this](const nlohmann::json& val) { is_debug = val.get(); }}, + {"phased_update_percentage", [this](const nlohmann::json& val) { + if (!val.is_null()) phased_update_percentage = val.get(); + else phased_update_percentage = -1; + }}, + {"pocket", [this](const nlohmann::json& val) { pocket = val.get(); }}, + {"priority_name", [this](const nlohmann::json& val) { priority_name = val.get(); }}, + {"removal_comment", [this](const nlohmann::json& val) { removal_comment = val.get(); }}, + {"removed_by_link", [this](const nlohmann::json& val) { removed_by_link = val.get(); }}, + {"resource_type_link", [this](const nlohmann::json& val) { resource_type_link = val.get(); }}, + {"scheduled_deletion_date", [this](const nlohmann::json& val) { scheduled_deletion_date = val.get(); }}, + {"section_name", [this](const nlohmann::json& val) { section_name = val.get(); }}, + {"self_link", [this](const nlohmann::json& val) { self_link = val.get(); }}, + {"source_package_name", [this](const nlohmann::json& val) { source_package_name = val.get(); }}, + {"source_package_version", [this](const nlohmann::json& val) { source_package_version = val.get(); }}, + {"status", [this](const nlohmann::json& val) { status = val.get(); }} + }; + + for (auto& [key, value] : data.items()) { + try { + if (fields.find(key) != fields.end()) { + fields[key](value); + fields.erase(key); + } + } catch (...) { + continue; + } + } + } catch (const nlohmann::json::parse_error& e) { + std::cerr << "JSON parse error in binary_package_publishing_history::parse_json: " << e.what() << std::endl; + } catch (const std::exception& e) { + std::cerr << "Unexpected error during JSON parsing: " << e.what() << std::endl; + } +} + +void binary_package_publishing_history::set_lp(launchpad* lp_ptr) { + lp = lp_ptr; +} + +std::optional binary_package_publishing_history::get() const { + if(self_link.empty()) return std::nullopt; + auto resp=lp->api_get(self_link); + if(!resp)return std::nullopt; + return parse(resp.value()); +} + +bool binary_package_publishing_history::patch(const nlohmann::json& data) const { + if(self_link.empty())return false; + auto resp=lp->api_patch(self_link,data); + return resp.has_value(); +} + +bool binary_package_publishing_history::put(const nlohmann::json& data) const { + if(self_link.empty())return false; + // For PUT, we emulate with POST & ws.op=replace or something similar, but doc says PUT should contain the full representation. + // We'll try POST with build_endpoint=false and full JSON. Actually we have no direct method for PUT in code. We'll just do a POST with `?ws.op=replace`. + std::string endpoint=self_link; + endpoint+="?ws.op=replace"; + auto resp=lp->api_patch(endpoint,data); + return resp.has_value(); +} + +std::optional binary_package_publishing_history::binaryFileUrls(bool include_meta) const { + if(self_link.empty())return std::nullopt; + std::mapparams; + params["ws.op"]="binaryFileUrls"; + if(include_meta)params["include_meta"]="true"; + auto resp=lp->api_get(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +std::optional binary_package_publishing_history::getDailyDownloadTotals(const std::string& start_date, const std::string& end_date) const { + if(self_link.empty())return std::nullopt; + std::mapparams; + params["ws.op"]="getDailyDownloadTotals"; + if(!start_date.empty())params["start_date"]=start_date; + if(!end_date.empty())params["end_date"]=end_date; + auto resp=lp->api_get(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +std::optional binary_package_publishing_history::getDownloadCount() const { + if(self_link.empty())return std::nullopt; + std::mapparams; + params["ws.op"]="getDownloadCount"; + auto resp=lp->api_get(self_link,params); + if(!resp)return std::nullopt; + auto j=nlohmann::json::parse(resp.value()); + if(j.is_number())return j.get(); + return std::nullopt; +} + +std::optional binary_package_publishing_history::getDownloadCounts(const std::string& start_date, const std::string& end_date) const { + if(self_link.empty())return std::nullopt; + std::mapparams; + params["ws.op"]="getDownloadCounts"; + if(!start_date.empty())params["start_date"]=start_date; + if(!end_date.empty())params["end_date"]=end_date; + auto resp=lp->api_get(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +std::optional binary_package_publishing_history::changeOverride(const std::optional& new_component, + const std::optional& new_phased_update_percentage, + const std::optional& new_priority, + const std::optional& new_section) const { + if(self_link.empty())return std::nullopt; + std::mapparams; + params["ws.op"]="changeOverride"; + if(new_component.has_value()) params["new_component"]=new_component.value(); + if(new_phased_update_percentage.has_value()) params["new_phased_update_percentage"]=std::to_string(new_phased_update_percentage.value()); + if(new_priority.has_value()) params["new_priority"]=new_priority.value(); + if(new_section.has_value()) params["new_section"]=new_section.value(); + auto resp=lp->api_post(self_link,params); + if(!resp)return std::nullopt; + auto obj=parse(resp.value()); + if(obj)obj->set_lp(lp); + return obj; +} + +bool binary_package_publishing_history::requestDeletion(const std::optional& removal_comment) const { + if(self_link.empty())return false; + std::mapparams; + params["ws.op"]="requestDeletion"; + if(removal_comment.has_value())params["removal_comment"]=removal_comment.value(); + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} diff --git a/src/binary_package_publishing_history.h b/src/binary_package_publishing_history.h new file mode 100644 index 0000000..6a27acd --- /dev/null +++ b/src/binary_package_publishing_history.h @@ -0,0 +1,69 @@ +#ifndef BINARY_PACKAGE_PUBLISHING_HISTORY_H +#define BINARY_PACKAGE_PUBLISHING_HISTORY_H + +#include +#include +#include +#include + +class launchpad; + +class binary_package_publishing_history { +public: + binary_package_publishing_history(); + ~binary_package_publishing_history(); + + static std::optional parse(const std::string& json_data); + void parse_json(const std::string& json_data); + void set_lp(launchpad* lp_ptr); + + bool architecture_specific; + std::string archive_link; + std::string binary_package_name; + std::string binary_package_version; + std::string build_link; + std::string component_name; + std::string copied_from_archive_link; + std::string creator_link; + std::string date_created; + std::string date_made_pending; + std::string date_published; + std::string date_removed; + std::string date_superseded; + std::string display_name; + std::string distro_arch_series_link; + std::string http_etag; + bool is_debug; + int phased_update_percentage; // -1 if None + std::string pocket; + std::string priority_name; + std::string removal_comment; + std::string removed_by_link; + std::string resource_type_link; + std::string scheduled_deletion_date; + std::string section_name; + std::string self_link; + std::string source_package_name; + std::string source_package_version; + std::string status; + + std::optional get() const; + bool patch(const nlohmann::json& data) const; + bool put(const nlohmann::json& data) const; + + std::optional binaryFileUrls(bool include_meta=false) const; + std::optional getDailyDownloadTotals(const std::string& start_date="", const std::string& end_date="") const; + std::optional getDownloadCount() const; + std::optional getDownloadCounts(const std::string& start_date="", const std::string& end_date="") const; + + std::optional changeOverride(const std::optional& new_component, + const std::optional& new_phased_update_percentage, + const std::optional& new_priority, + const std::optional& new_section) const; + bool requestDeletion(const std::optional& removal_comment) const; + +private: + launchpad* lp; +}; + +#endif // BINARY_PACKAGE_PUBLISHING_HISTORY_H diff --git a/src/distro_arch_series.cpp b/src/distro_arch_series.cpp new file mode 100644 index 0000000..2a17370 --- /dev/null +++ b/src/distro_arch_series.cpp @@ -0,0 +1,214 @@ +#include "distro_arch_series.h" +#include "launchpad.h" +#include "utils.h" +#include +#include + +distro_arch_series::distro_arch_series() + : enabled(false), + is_nominated_arch_indep(false), + official(false), + package_count(0), + supports_virtualized(false), + lp(nullptr) {} + +distro_arch_series::~distro_arch_series() {} + +std::optional distro_arch_series::parse(const std::string& json_data) { + distro_arch_series das; + das.parse_json(json_data); + return das; +} + +void distro_arch_series::parse_json(const std::string& json_data) { + if (json_data.empty()) { + std::cerr << "Error: Empty JSON data for distro_arch_series::parse_json." << std::endl; + return; + } + + try { + auto data = nlohmann::json::parse(json_data); + + auto get_bool = [&](const std::string& key, bool& field) { + if (data.contains(key) && data[key].is_boolean()) field = data[key].get(); + }; + + auto get_str = [&](const std::string& key, std::string& field) { + if (data.contains(key) && data[key].is_string()) field = data[key].get(); + }; + + auto get_int = [&](const std::string& key, int& field) { + if (data.contains(key) && data[key].is_number()) field = data[key].get(); + }; + + get_str("architecture_tag", architecture_tag); + get_str("chroot_url", chroot_url); + get_str("display_name", display_name); + get_str("distroseries_link", distroseries_link); + get_bool("enabled", enabled); + get_str("http_etag", http_etag); + get_bool("is_nominated_arch_indep", is_nominated_arch_indep); + get_str("main_archive_link", main_archive_link); + get_bool("official", official); + get_str("owner_link", owner_link); + get_int("package_count", package_count); + get_str("processor_link", processor_link); + get_str("resource_type_link", resource_type_link); + get_str("self_link", self_link); + get_bool("supports_virtualized", supports_virtualized); + get_str("title", title); + get_str("web_link", web_link); + + } catch (const nlohmann::json::parse_error& e) { + std::cerr << "JSON parse error in distro_arch_series::parse_json: " << e.what() << std::endl; + } catch (const std::exception& e) { + std::cerr << "Unexpected error during JSON parsing: " << e.what() << std::endl; + } +} + +void distro_arch_series::set_lp(launchpad* lp_ptr) { + lp = lp_ptr; +} + +std::optional distro_arch_series::get() const { + if (self_link.empty()) return std::nullopt; + auto resp = lp->api_get(self_link); + if (!resp) return std::nullopt; + auto das = parse(resp.value()); + if (das) das->set_lp(lp); + return das; +} + +bool distro_arch_series::patch(const nlohmann::json& data) const { + if (self_link.empty()) return false; + auto resp = lp->api_patch(self_link, data); + return resp.has_value(); +} + +bool distro_arch_series::put(const nlohmann::json& data) const { + if (self_link.empty()) return false; + // Emulate PUT with ws.op=replace + std::string endpoint = self_link + "?ws.op=replace"; + auto resp = lp->api_patch(endpoint, data); + return resp.has_value(); +} + +std::generator distro_arch_series::getBuildRecords(const std::string& build_state, + const std::string& pocket, + const std::string& source_name) const { + if (self_link.empty()) co_return; + std::map params; + params["ws.op"] = "getBuildRecords"; + if(!build_state.empty()) params["build_state"] = build_state; + if(!pocket.empty()) params["pocket"] = pocket; + if(!source_name.empty()) params["source_name"] = source_name; + auto response = lp->api_get(self_link, params); + if(!response) co_return; + auto data = nlohmann::json::parse(response.value()); + while (true) { + if (data.contains("entries") && data["entries"].is_array()) { + for (auto& e : data["entries"]) { + co_yield e; + } + } + if(!data.contains("next_collection_link") || data["next_collection_link"].is_null() || data["next_collection_link"] == "") break; + response = lp->api_get(data["next_collection_link"].get()); + if(!response) break; + data = nlohmann::json::parse(response.value()); + } +} + +std::optional distro_arch_series::getChrootHash(const std::string& image_type, + const std::string& pocket) const { + if (self_link.empty()) return std::nullopt; + std::map params; + params["ws.op"] = "getChrootHash"; + if(!image_type.empty()) params["image_type"] = image_type; + if(!pocket.empty()) params["pocket"] = pocket; + auto resp = lp->api_get(self_link, params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +std::optional distro_arch_series::getChrootURL(const std::optional& image_type, + const std::optional& pocket) const { + if (self_link.empty()) return std::nullopt; + std::map params; + params["ws.op"] = "getChrootURL"; + if(image_type.has_value()) params["image_type"] = image_type.value(); + if(pocket.has_value()) params["pocket"] = pocket.value(); + auto resp = lp->api_get(self_link, params); + if(!resp)return std::nullopt; + auto j = nlohmann::json::parse(resp.value()); + if(j.is_string()) return j.get(); + return std::nullopt; +} + +std::optional distro_arch_series::getSourceFilter() const { + if (self_link.empty()) return std::nullopt; + std::map params; + params["ws.op"] = "getSourceFilter"; + auto resp=lp->api_get(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +bool distro_arch_series::removeChroot(const std::optional& image_type, + const std::optional& pocket) const { + if(self_link.empty())return false; + std::mapparams; + params["ws.op"]="removeChroot"; + if(image_type.has_value()) params["image_type"]=image_type.value(); + if(pocket.has_value()) params["pocket"]=pocket.value(); + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool distro_arch_series::removeSourceFilter() const { + if(self_link.empty())return false; + std::mapparams; + params["ws.op"]="removeSourceFilter"; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool distro_arch_series::setChroot(const std::string& data_link, + const std::optional& image_type, + const std::optional& pocket, + const std::string& sha1sum) const { + if(self_link.empty())return false; + std::mapparams; + params["ws.op"]="setChroot"; + params["data"]=data_link; + if(image_type.has_value()) params["image_type"]=image_type.value(); + if(pocket.has_value()) params["pocket"]=pocket.value(); + params["sha1sum"]=sha1sum; + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool distro_arch_series::setChrootFromBuild(const std::string& filename, + const std::string& livefsbuild_link, + const std::optional& image_type, + const std::optional& pocket) const { + if(self_link.empty())return false; + std::mapparams; + params["ws.op"]="setChrootFromBuild"; + params["filename"]=filename; + params["livefsbuild"]=livefsbuild_link; + if(image_type.has_value()) params["image_type"]=image_type.value(); + if(pocket.has_value()) params["pocket"]=pocket.value(); + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool distro_arch_series::setSourceFilter(const std::string& packageset, + const std::string& sense) const { + if(self_link.empty())return false; + std::mapparams; + params["ws.op"]="setSourceFilter"; + params["packageset"]=packageset; + params["sense"]=sense; // must be "Include" or "Exclude" + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} diff --git a/src/distro_arch_series.h b/src/distro_arch_series.h new file mode 100644 index 0000000..2bea6bd --- /dev/null +++ b/src/distro_arch_series.h @@ -0,0 +1,74 @@ +#ifndef DISTRO_ARCH_SERIES_H +#define DISTRO_ARCH_SERIES_H + +#include +#include +#include +#include + +class launchpad; + +class distro_arch_series { +public: + distro_arch_series(); + ~distro_arch_series(); + + static std::optional parse(const std::string& json_data); + void parse_json(const std::string& json_data); + void set_lp(launchpad* lp_ptr); + + // Writable fields + std::string architecture_tag; + std::string display_name; + std::string distroseries_link; + bool enabled; + bool is_nominated_arch_indep; + std::string main_archive_link; + bool official; + std::string owner_link; + int package_count; + std::string title; + + // Read-only fields + std::string chroot_url; + std::string http_etag; + std::string processor_link; + std::string resource_type_link; + std::string self_link; + bool supports_virtualized; + std::string web_link; + + std::optional get() const; + bool patch(const nlohmann::json& data) const; + bool put(const nlohmann::json& data) const; + + // Custom GET methods + std::generator getBuildRecords(const std::string& build_state="", + const std::string& pocket="", + const std::string& source_name="") const; + std::optional getChrootHash(const std::string& image_type="Chroot tarball", + const std::string& pocket="Release") const; + std::optional getChrootURL(const std::optional& image_type=std::nullopt, + const std::optional& pocket=std::nullopt) const; + std::optional getSourceFilter() const; + + // Custom POST methods + bool removeChroot(const std::optional& image_type=std::nullopt, + const std::optional& pocket=std::nullopt) const; + bool removeSourceFilter() const; + bool setChroot(const std::string& data_link, + const std::optional& image_type=std::nullopt, + const std::optional& pocket=std::nullopt, + const std::string& sha1sum="") const; + bool setChrootFromBuild(const std::string& filename, + const std::string& livefsbuild_link, + const std::optional& image_type=std::nullopt, + const std::optional& pocket=std::nullopt) const; + bool setSourceFilter(const std::string& packageset, + const std::string& sense) const; + +private: + launchpad* lp; +}; + +#endif // DISTRO_ARCH_SERIES_H diff --git a/src/distro_arch_series_filter.cpp b/src/distro_arch_series_filter.cpp new file mode 100644 index 0000000..13092eb --- /dev/null +++ b/src/distro_arch_series_filter.cpp @@ -0,0 +1,74 @@ +#include "distro_arch_series_filter.h" +#include "launchpad.h" +#include +#include + +distro_arch_series_filter::distro_arch_series_filter() + : lp(nullptr) {} + +distro_arch_series_filter::~distro_arch_series_filter() {} + +std::optional distro_arch_series_filter::parse(const std::string& json_data) { + distro_arch_series_filter f; + f.parse_json(json_data); + return f; +} + +void distro_arch_series_filter::parse_json(const std::string& json_data) { + if (json_data.empty()) { + std::cerr << "Error: Empty JSON data for distro_arch_series_filter::parse_json." << std::endl; + return; + } + + try { + auto data = nlohmann::json::parse(json_data); + + auto get_str = [&](const std::string& key, std::string& field) { + if (data.contains(key) && data[key].is_string()) + field = data[key].get(); + }; + + get_str("creator_link", creator_link); + get_str("date_created", date_created); + get_str("date_last_modified", date_last_modified); + get_str("distroarchseries_link", distroarchseries_link); + get_str("http_etag", http_etag); + get_str("packageset_link", packageset_link); + get_str("resource_type_link", resource_type_link); + get_str("self_link", self_link); + get_str("sense", sense); + get_str("web_link", web_link); + + } catch (const nlohmann::json::parse_error& e) { + std::cerr << "JSON parse error in distro_arch_series_filter::parse_json: " << e.what() << std::endl; + } catch (const std::exception& e) { + std::cerr << "Unexpected error during JSON parsing: " << e.what() << std::endl; + } +} + +void distro_arch_series_filter::set_lp(launchpad* lp_ptr) { + lp = lp_ptr; +} + +std::optional distro_arch_series_filter::get() const { + if (self_link.empty()) return std::nullopt; + auto resp = lp->api_get(self_link); + if (!resp) return std::nullopt; + auto f = parse(resp.value()); + if (f) f->set_lp(lp); + return f; +} + +bool distro_arch_series_filter::patch(const nlohmann::json& data) const { + if (self_link.empty()) return false; + auto resp = lp->api_patch(self_link, data); + return resp.has_value(); +} + +bool distro_arch_series_filter::put(const nlohmann::json& data) const { + if (self_link.empty()) return false; + // Emulate PUT with ws.op=replace + std::string endpoint = self_link + "?ws.op=replace"; + auto resp = lp->api_patch(endpoint, data); + return resp.has_value(); +} diff --git a/src/distro_arch_series_filter.h b/src/distro_arch_series_filter.h new file mode 100644 index 0000000..9d81fec --- /dev/null +++ b/src/distro_arch_series_filter.h @@ -0,0 +1,39 @@ +#ifndef DISTRO_ARCH_SERIES_FILTER_H +#define DISTRO_ARCH_SERIES_FILTER_H + +#include +#include +#include + +class launchpad; + +class distro_arch_series_filter { +public: + distro_arch_series_filter(); + ~distro_arch_series_filter(); + + static std::optional parse(const std::string& json_data); + void parse_json(const std::string& json_data); + void set_lp(launchpad* lp_ptr); + + // Read-only fields + std::string creator_link; + std::string date_created; + std::string date_last_modified; + std::string distroarchseries_link; + std::string http_etag; + std::string packageset_link; + std::string resource_type_link; + std::string self_link; + std::string sense; // "Include" or "Exclude" + std::string web_link; + + std::optional get() const; + bool patch(const nlohmann::json& data) const; + bool put(const nlohmann::json& data) const; + +private: + launchpad* lp; +}; + +#endif diff --git a/src/distro_series.cpp b/src/distro_series.cpp new file mode 100644 index 0000000..0e8f9cb --- /dev/null +++ b/src/distro_series.cpp @@ -0,0 +1,521 @@ +#include "distro_series.h" +#include "launchpad.h" +#include "utils.h" +#include +#include + +distro_series::distro_series() + : active(false), + advertise_by_hash(false), + backports_not_automatic(false), + include_long_descriptions(false), + language_pack_full_export_requested(false), + proposed_not_automatic(false), + publish_by_hash(false), + publish_i18n_index(false), + strict_supported_component_dependencies(false), + supported(false), + lp(nullptr) {} + +distro_series::~distro_series() {} + +std::optional distro_series::parse(const std::string& json_data) { + distro_series ds; + ds.parse_json(json_data); + return ds; +} + +void distro_series::parse_json(const std::string& json_data) { + if (json_data.empty()) { + std::cerr << "Error: Empty JSON data for distro_series::parse_json." << std::endl; + return; + } + try { + auto data = nlohmann::json::parse(json_data); + + auto get_bool = [&](const std::string& key, bool& field) { + if (data.contains(key) && data[key].is_boolean()) field = data[key].get(); + }; + auto get_str = [&](const std::string& key, std::string& field) { + if (data.contains(key) && data[key].is_string()) field = data[key].get(); + }; + auto get_str_vec = [&](const std::string& key, std::vector& field) { + if (data.contains(key) && data[key].is_array()) { + field.clear(); + for (auto& e : data[key]) { + if (e.is_string()) field.push_back(e.get()); + } + } + }; + + get_bool("active", active); + get_bool("advertise_by_hash", advertise_by_hash); + get_str("all_milestones_collection_link", all_milestones_collection_link); + get_str("all_specifications_collection_link", all_specifications_collection_link); + get_str("architectures_collection_link", architectures_collection_link); + get_bool("backports_not_automatic", backports_not_automatic); + get_str("bug_reported_acknowledgement", bug_reported_acknowledgement); + get_str("bug_reporting_guidelines", bug_reporting_guidelines); + get_str("changeslist", changeslist); + get_str_vec("component_names", component_names); + get_str("date_created", date_created); + get_str("datereleased", datereleased); + get_str("description", description); + get_str("displayname", displayname); + get_str("distribution_link", distribution_link); + get_str("driver_link", driver_link); + get_str("drivers_collection_link", drivers_collection_link); + get_str("fullseriesname", fullseriesname); + get_str("http_etag", http_etag); + get_bool("include_long_descriptions", include_long_descriptions); + get_str_vec("index_compressors", index_compressors); + get_bool("language_pack_full_export_requested", language_pack_full_export_requested); + get_str("main_archive_link", main_archive_link); + get_str("name", name); + get_str("nominatedarchindep_link", nominatedarchindep_link); + get_str_vec("official_bug_tags", official_bug_tags); + get_str("owner_link", owner_link); + get_str("previous_series_link", previous_series_link); + get_bool("proposed_not_automatic", proposed_not_automatic); + get_bool("publish_by_hash", publish_by_hash); + get_bool("publish_i18n_index", publish_i18n_index); + get_str("registrant_link", registrant_link); + get_str("resource_type_link", resource_type_link); + get_str("self_link", self_link); + get_str("status", status); + get_bool("strict_supported_component_dependencies", strict_supported_component_dependencies); + get_str_vec("suite_names", suite_names); + get_str("summary", summary); + get_bool("supported", supported); + get_str("title", title); + get_str("translations_usage", translations_usage); + get_str("valid_specifications_collection_link", valid_specifications_collection_link); + get_str("version", version); + get_str("web_link", web_link); + + // These additional fields might not be present in the JSON, but we define them if they exist + // They are listed as additional read-only fields in the header, but were not found in the JSON keys above + // We'll just attempt to parse them similarly if they exist + get_str("active_milestones_collection_link", active_milestones_collection_link); + get_str("archive_mirrors_collection_link", archive_mirrors_collection_link); + get_str("archives_collection_link", archives_collection_link); + get_str("series_collection_link", series_collection_link); + get_str("vulnerabilities_collection_link", vulnerabilities_collection_link); + get_str("webhooks_collection_link", webhooks_collection_link); + + } catch (const nlohmann::json::parse_error& e) { + std::cerr << "JSON parse error in distro_series::parse_json: " << e.what() << std::endl; + } catch (const std::exception& e) { + std::cerr << "Unexpected error during JSON parsing: " << e.what() << std::endl; + } +} + +void distro_series::set_lp(launchpad* lp_ptr) { + lp = lp_ptr; +} + +std::optional distro_series::get() const { + if (self_link.empty()) return std::nullopt; + auto resp = lp->api_get(self_link); + if(!resp)return std::nullopt; + return parse(resp.value()); +} + +bool distro_series::patch(const nlohmann::json& data) const { + if (self_link.empty()) return false; + auto resp = lp->api_patch(self_link, data); + return resp.has_value(); +} + +bool distro_series::put(const nlohmann::json& data) const { + if (self_link.empty()) return false; + std::string endpoint = self_link + "?ws.op=replace"; + auto resp = lp->api_patch(endpoint, data); + return resp.has_value(); +} + +std::generator distro_series::getBuildRecords(const std::string& build_state, const std::string& pocket, const std::string& source_name) const { + if (!lp) co_return; + if (self_link.empty()) co_return; + + std::map params; + params["ws.op"] = "getBuildRecords"; + if(!build_state.empty()) params["build_state"]=build_state; + if(!pocket.empty()) params["pocket"]=pocket; + if(!source_name.empty()) params["source_name"]=source_name; + + auto response=lp->api_get(self_link, params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + while(true) { + if(data.contains("entries") && data["entries"].is_array()) { + for(auto&e:data["entries"]){ + co_yield e; + } + } + if(!data.contains("next_collection_link") || data["next_collection_link"].is_null() || data["next_collection_link"]=="") break; + response=lp->api_get(data["next_collection_link"].get()); + if(!response)break; + data=nlohmann::json::parse(response.value()); + } +} + +std::generator distro_series::getDerivedSeries() const { + if(!lp || self_link.empty()) co_return; + std::map params; + params["ws.op"]="getDerivedSeries"; + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + + while(true){ + if(data.contains("entries") && data["entries"].is_array()){ + for(auto&e:data["entries"]){ + auto ds_opt = parse(e.dump()); + if(ds_opt){ + ds_opt->set_lp(lp); + co_yield ds_opt.value(); + } + } + } + if(!data.contains("next_collection_link")||data["next_collection_link"].is_null()||data["next_collection_link"]=="")break; + response=lp->api_get(data["next_collection_link"].get()); + if(!response)break; + data=nlohmann::json::parse(response.value()); + } +} + +std::generator distro_series::getDifferenceComments(const std::string& since, const std::string& source_package_name) const { + if(!lp||self_link.empty()) co_return; + std::map params; + params["ws.op"]="getDifferenceComments"; + if(!since.empty()) params["since"]=since; + if(!source_package_name.empty()) params["source_package_name"]=source_package_name; + + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + + while(true){ + if(data.contains("entries") && data["entries"].is_array()){ + for(auto&e:data["entries"]){ + co_yield e; + } + } + if(!data.contains("next_collection_link")||data["next_collection_link"].is_null()||data["next_collection_link"]=="")break; + response=lp->api_get(data["next_collection_link"].get()); + if(!response)break; + data=nlohmann::json::parse(response.value()); + } +} + +std::generator distro_series::getDifferencesTo(const std::optional& parent_series, + const std::optional& difference_type, + const std::optional& source_package_name_filter, + const std::optional& status, + bool child_version_higher) const { + if(!lp||self_link.empty()) co_return; + std::map params; + params["ws.op"]="getDifferencesTo"; + if(parent_series.has_value()) params["parent_series"]=parent_series.value(); + if(difference_type.has_value()) params["difference_type"]=difference_type.value(); + if(source_package_name_filter.has_value()) params["source_package_name_filter"]=source_package_name_filter.value(); + if(status.has_value()) params["status"]=status.value(); + if(child_version_higher) params["child_version_higher"]="true"; + + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + // Usually differences might have entries too + while(true){ + if(data.contains("entries")&&data["entries"].is_array()){ + for(auto&e:data["entries"]){ + co_yield e; + } + } + if(!data.contains("next_collection_link")||data["next_collection_link"].is_null()||data["next_collection_link"]=="")break; + response=lp->api_get(data["next_collection_link"].get()); + if(!response)break; + data=nlohmann::json::parse(response.value()); + } +} + +std::optional distro_series::getDistroArchSeries(const std::string& archtag) const { + if(!lp||self_link.empty())return std::nullopt; + std::map params; + params["ws.op"]="getDistroArchSeries"; + params["archtag"]=archtag; + auto response=lp->api_get(self_link,params); + if(!response)return std::nullopt; + return nlohmann::json::parse(response.value()); +} + +std::generator distro_series::getPackageUploads(const std::optional& archive, + const std::optional& created_since_date, + const std::optional& custom_type, + bool exact_match, + const std::optional& name, + const std::optional& pocket, + const std::optional& status, + const std::optional& version) const { + if(!lp||self_link.empty()) co_return; + std::map params; + params["ws.op"]="getPackageUploads"; + if(archive.has_value()) params["archive"]=archive.value(); + if(created_since_date.has_value()) params["created_since_date"]=created_since_date.value(); + if(custom_type.has_value()) params["custom_type"]=custom_type.value(); + if(exact_match) params["exact_match"]="true"; + if(name.has_value()) params["name"]=name.value(); + if(pocket.has_value()) params["pocket"]=pocket.value(); + if(status.has_value()) params["status"]=status.value(); + if(version.has_value()) params["version"]=version.value(); + + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + + while(true){ + if(data.contains("entries") && data["entries"].is_array()){ + for(auto&e:data["entries"]) { + co_yield e; + } + } + if(!data.contains("next_collection_link")||data["next_collection_link"].is_null()||data["next_collection_link"]=="") break; + response=lp->api_get(data["next_collection_link"].get()); + if(!response)break; + data=nlohmann::json::parse(response.value()); + } +} + +std::generator distro_series::getParentSeries() const { + if(!lp||self_link.empty()) co_return; + std::map params; + params["ws.op"]="getParentSeries"; + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + while(true) { + if(data.contains("entries")&&data["entries"].is_array()){ + for(auto&e:data["entries"]){ + auto ds_opt=parse(e.dump()); + if(ds_opt){ + ds_opt->set_lp(lp); + co_yield ds_opt.value(); + } + } + } + if(!data.contains("next_collection_link")||data["next_collection_link"].is_null()||data["next_collection_link"]=="")break; + response=lp->api_get(data["next_collection_link"].get()); + if(!response)break; + data=nlohmann::json::parse(response.value()); + } +} + +std::optional distro_series::getSourcePackage(const std::string& name_) const { + if(!lp||self_link.empty())return std::nullopt; + std::map params; + params["ws.op"]="getSourcePackage"; + params["name"]=name_; + auto resp=lp->api_get(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +std::optional distro_series::getSpecification(const std::string& name_) const { + if(!lp||self_link.empty())return std::nullopt; + std::map params; + params["ws.op"]="getSpecification"; + params["name"]=name_; + auto resp=lp->api_get(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +std::optional distro_series::getSubscription(const std::string& person) const { + if(!lp||self_link.empty())return std::nullopt; + std::map params; + params["ws.op"]="getSubscription"; + params["person"]=person; + auto resp=lp->api_get(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +std::generator distro_series::getSubscriptions() const { + if(!lp||self_link.empty()) co_return; + std::map params; + params["ws.op"]="getSubscriptions"; + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + while(true) { + if(data.contains("entries")&&data["entries"].is_array()){ + for(auto&e:data["entries"]){ + co_yield e; + } + } + if(!data.contains("next_collection_link")||data["next_collection_link"].is_null()||data["next_collection_link"]=="")break; + response=lp->api_get(data["next_collection_link"].get()); + if(!response)break; + data=nlohmann::json::parse(response.value()); + } +} + +std::generator distro_series::getTranslationImportQueueEntries(const std::optional& import_status, + const std::optional& file_extension) const { + if(!lp||self_link.empty()) co_return; + std::mapparams; + params["ws.op"]="getTranslationImportQueueEntries"; + if(import_status.has_value()) params["import_status"]=import_status.value(); + if(file_extension.has_value()) params["file_extension"]=file_extension.value(); + + auto response=lp->api_get(self_link,params); + if(!response)co_return; + auto data=nlohmann::json::parse(response.value()); + while(true){ + if(data.contains("entries")&&data["entries"].is_array()){ + for(auto&e:data["entries"]){ + co_yield e; + } + } + if(!data.contains("next_collection_link")||data["next_collection_link"].is_null()||data["next_collection_link"]=="")break; + response=lp->api_get(data["next_collection_link"].get()); + if(!response)break; + data=nlohmann::json::parse(response.value()); + } +} + +std::optional distro_series::getTranslationTemplateStatistics() const { + if(!lp||self_link.empty())return std::nullopt; + std::mapparams; + params["ws.op"]="getTranslationTemplateStatistics"; + auto resp=lp->api_get(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +std::generator distro_series::getTranslationTemplates() const { + if(!lp||self_link.empty()) co_return; + std::mapparams; + params["ws.op"]="getTranslationTemplates"; + auto response=lp->api_get(self_link,params); + if(!response)co_return; + auto data=nlohmann::json::parse(response.value()); + while(true){ + if(data.contains("entries")&&data["entries"].is_array()){ + for(auto&e:data["entries"]){ + co_yield e; + } + } + if(!data.contains("next_collection_link")||data["next_collection_link"].is_null()||data["next_collection_link"]=="") break; + response=lp->api_get(data["next_collection_link"].get()); + if(!response) break; + data=nlohmann::json::parse(response.value()); + } +} + +std::generator distro_series::searchTasks(const std::map& params_in) const { + if(!lp||self_link.empty()) co_return; + std::map params = params_in; + params["ws.op"]="searchTasks"; + auto response=lp->api_get(self_link,params); + if(!response) co_return; + auto data=nlohmann::json::parse(response.value()); + while(true){ + if(data.contains("entries")&&data["entries"].is_array()){ + for(auto&e:data["entries"]){ + co_yield e; + } + } + if(!data.contains("next_collection_link")||data["next_collection_link"].is_null()||data["next_collection_link"]=="")break; + response=lp->api_get(data["next_collection_link"].get()); + if(!response)break; + data=nlohmann::json::parse(response.value()); + } +} + +std::optional distro_series::userHasBugSubscriptions() const { + if(!lp||self_link.empty())return std::nullopt; + std::mapparams; + params["ws.op"]="userHasBugSubscriptions"; + auto resp=lp->api_get(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +bool distro_series::addBugSubscription(const std::optional& subscriber) const { + if(!lp||self_link.empty())return false; + std::mapparams; + params["ws.op"]="addBugSubscription"; + if(subscriber.has_value()) params["subscriber"]=subscriber.value(); + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool distro_series::addBugSubscriptionFilter(const std::optional& subscriber) const { + if(!lp||self_link.empty())return false; + std::mapparams; + params["ws.op"]="addBugSubscriptionFilter"; + if(subscriber.has_value())params["subscriber"]=subscriber.value(); + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +bool distro_series::initDerivedDistroSeries(const std::optional& archindep_archtag, + const std::vector& architectures, + const std::vector& overlay_components, + const std::vector& overlay_pockets, + const std::vector& overlays, + const std::vector& packagesets, + const std::vector& parents, + bool rebuild) const { + if(!lp||self_link.empty())return false; + std::mapparams; + params["ws.op"]="initDerivedDistroSeries"; + if(archindep_archtag.has_value()) params["archindep_archtag"]=archindep_archtag.value(); + + auto join_list = [](const std::vector& vec){ + std::string joined; + for (auto& v:vec) { + joined+=v+","; + } + if(!joined.empty()) joined.pop_back(); + return joined; + }; + + if(!architectures.empty()) params["architectures"]=join_list(architectures); + if(!overlay_components.empty()) params["overlay_components"]=join_list(overlay_components); + if(!overlay_pockets.empty()) params["overlay_pockets"]=join_list(overlay_pockets); + if(!overlays.empty()) params["overlays"]=join_list(overlays); + if(!packagesets.empty()) params["packagesets"]=join_list(packagesets); + if(!parents.empty()) params["parents"]=join_list(parents); + if(rebuild) params["rebuild"]="true"; + + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} + +std::optional distro_series::newMilestone(const std::string& name, + const std::optional& code_name, + const std::optional& date_targeted, + const std::optional& summary) const { + if(!lp||self_link.empty())return std::nullopt; + std::mapparams; + params["ws.op"]="newMilestone"; + params["name"]=name; + if(code_name.has_value()) params["code_name"]=code_name.value(); + if(date_targeted.has_value()) params["date_targeted"]=date_targeted.value(); + if(summary.has_value()) params["summary"]=summary.value(); + auto resp=lp->api_post(self_link,params); + if(!resp)return std::nullopt; + return nlohmann::json::parse(resp.value()); +} + +bool distro_series::removeBugSubscription(const std::optional& subscriber) const { + if(!lp||self_link.empty())return false; + std::mapparams; + params["ws.op"]="removeBugSubscription"; + if(subscriber.has_value()) params["subscriber"]=subscriber.value(); + auto resp=lp->api_post(self_link,params); + return resp.has_value(); +} diff --git a/src/distro_series.h b/src/distro_series.h new file mode 100644 index 0000000..b2b1f2a --- /dev/null +++ b/src/distro_series.h @@ -0,0 +1,127 @@ +#ifndef DISTRO_SERIES_H +#define DISTRO_SERIES_H + +#include +#include +#include +#include +#include + +class launchpad; + +class distro_series { +public: + distro_series(); + ~distro_series(); + + static std::optional parse(const std::string& json_data); + void parse_json(const std::string& json_data); + void set_lp(launchpad* lp_ptr); + + bool active; + bool advertise_by_hash; + std::string all_milestones_collection_link; + std::string all_specifications_collection_link; + std::string architectures_collection_link; + bool backports_not_automatic; + std::string bug_reported_acknowledgement; + std::string bug_reporting_guidelines; + std::string changeslist; + std::vector component_names; + std::string date_created; + std::string datereleased; + std::string description; + std::string displayname; + std::string distribution_link; + std::string driver_link; + std::string drivers_collection_link; + std::string fullseriesname; + std::string http_etag; + bool include_long_descriptions; + std::vector index_compressors; + bool language_pack_full_export_requested; + std::string main_archive_link; + std::string name; + std::string nominatedarchindep_link; + std::vector official_bug_tags; + std::string owner_link; + std::string previous_series_link; + bool proposed_not_automatic; + bool publish_by_hash; + bool publish_i18n_index; + std::string registrant_link; + std::string resource_type_link; + std::string self_link; + std::string status; + bool strict_supported_component_dependencies; + std::vector suite_names; + std::string summary; + bool supported; + std::string title; + std::string translations_usage; + std::string valid_specifications_collection_link; + std::string version; + std::string web_link; + + std::string active_milestones_collection_link; + std::string archive_mirrors_collection_link; + std::string archives_collection_link; + std::string series_collection_link; + std::string vulnerabilities_collection_link; + std::string webhooks_collection_link; + + std::optional get() const; + bool patch(const nlohmann::json& data) const; + bool put(const nlohmann::json& data) const; + + std::generator getBuildRecords(const std::string& build_state="", const std::string& pocket="", const std::string& source_name="") const; + std::generator getDerivedSeries() const; + std::generator getDifferenceComments(const std::string& since="", const std::string& source_package_name="") const; + std::generator getDifferencesTo(const std::optional& parent_series, + const std::optional& difference_type, + const std::optional& source_package_name_filter, + const std::optional& status, + bool child_version_higher=false) const; + std::optional getDistroArchSeries(const std::string& archtag) const; + std::generator getPackageUploads(const std::optional& archive=std::nullopt, + const std::optional& created_since_date=std::nullopt, + const std::optional& custom_type=std::nullopt, + bool exact_match=false, + const std::optional& name=std::nullopt, + const std::optional& pocket=std::nullopt, + const std::optional& status=std::nullopt, + const std::optional& version=std::nullopt) const; + std::generator getParentSeries() const; + std::optional getSourcePackage(const std::string& name_) const; + std::optional getSpecification(const std::string& name_) const; + std::optional getSubscription(const std::string& person) const; + std::generator getSubscriptions() const; + std::generator getTranslationImportQueueEntries(const std::optional& import_status=std::nullopt, + const std::optional& file_extension=std::nullopt) const; + std::optional getTranslationTemplateStatistics() const; + std::generator getTranslationTemplates() const; + std::generator searchTasks(const std::map& params) const; + + std::optional userHasBugSubscriptions() const; + + bool addBugSubscription(const std::optional& subscriber=std::nullopt) const; + bool addBugSubscriptionFilter(const std::optional& subscriber=std::nullopt) const; + bool initDerivedDistroSeries(const std::optional& archindep_archtag, + const std::vector& architectures, + const std::vector& overlay_components, + const std::vector& overlay_pockets, + const std::vector& overlays, + const std::vector& packagesets, + const std::vector& parents, + bool rebuild=false) const; + std::optional newMilestone(const std::string& name, + const std::optional& code_name, + const std::optional& date_targeted, + const std::optional& summary) const; + bool removeBugSubscription(const std::optional& subscriber=std::nullopt) const; + +private: + launchpad* lp; +}; + +#endif