From ee5803e622bc516280e4b99c1f06f047352fbe64 Mon Sep 17 00:00:00 2001 From: Silvan Calarco Date: Fri, 31 May 2024 18:34:23 +0200 Subject: [PATCH] DistroqueryAPI: first part of implementation of /package API service --- src/CMakeLists.txt | 1 + src/DistroqueryAPI.cpp | 167 +++++++++++++++--- src/DistroqueryAPI.hpp | 7 +- src/distroquery.cpp | 70 ++------ src/distroquery_functions.cpp | 77 ++++++++ ...troquery.hpp => distroquery_functions.hpp} | 18 +- 6 files changed, 255 insertions(+), 85 deletions(-) create mode 100644 src/distroquery_functions.cpp rename src/include/{distroquery.hpp => distroquery_functions.hpp} (76%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d020638..d001682 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -67,6 +67,7 @@ target_compile_options(distromatic PUBLIC -O2 -g -Wall -std=gnu11 -pedantic ${RP # -fno-toplevel-reorder add_executable(distroquery + distroquery_functions.cpp DistroqueryAPI.cpp distroquery.cpp functions.c diff --git a/src/DistroqueryAPI.cpp b/src/DistroqueryAPI.cpp index a8af246..9198004 100644 --- a/src/DistroqueryAPI.cpp +++ b/src/DistroqueryAPI.cpp @@ -18,29 +18,13 @@ */ #include "DistroqueryAPI.hpp" - -vector split(string str, string token) { - vectorresult; - while(str.size()){ - auto index = str.find(token); - if(index!=string::npos){ - result.push_back(str.substr(0,index)); - str = str.substr(index+token.size()); - if(str.size()==0)result.push_back(str); - }else{ - result.push_back(str); - str = ""; - } - } - return result; -} +#include DistroqueryAPI::DistroqueryAPI(configTag* conf): config(conf) { } json DistroqueryAPI::configToJsonRepositories() { json j; - j["repositories"] = {}; struct configTag *ct = config; while (ct) { json archs = {}; @@ -59,12 +43,141 @@ json DistroqueryAPI::configToJsonRepositories() { { "description", ct->description }, { "parents", parents } }; - j["repositories"].push_back(repository); + j.push_back(repository); ct = ct->next; } return j; } +void DistroqueryAPI::sendErrorResponse(string message) { + json j; + j["error"] = message; + cout << j.dump(); + exit(0); +} + +json DistroqueryAPI::getPackageSourceDetailsById(string repository, long id) { + json j; + string sql; + sqlite3_stmt *stmt; + + struct configTag* ct = findRepositoryByTag(repository.c_str()); + if (ct == NULL) { + j["error"] = "repository with tag '" + repository + "' does not exist"; + return j; + } + + auto dbs = openRepositoryDatabase(ct); + if (!dbs) { + j["error"] = "error opening sources database for repository " + repository; + return j; + } + sql = "SELECT * FROM sources,packagers WHERE sources.id=" + to_string(id) + + " AND sources.id_packager=packagers.id"; + if (!sqlite3_prepare_v2(dbs, sql.c_str(), sql.length(), &stmt, NULL) == SQLITE_OK) { + j["error"] = "error preparing query '" + sql + "'"; + return j; + } + if (sqlite3_step(stmt) == SQLITE_ROW) { + j["id"] = sqlite3_column_int(stmt,sqlite3_find_column_id(stmt, NULL, "id")); + j["name"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "name"))); + j["summary"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "summary"))); + j["epoch"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "epoch"))); + j["version"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "version"))); + j["release"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "release"))); + j["group"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "groupdescr"))); + j["license"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "license"))); + j["url"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "url"))); + //get_favicon_from_url((const char*)sqlite3_column_text(stmt1,sqlite3_find_column_id(stmt1, NULL, "url")),buffer,PATH_MAX); + j["size"] = sqlite3_column_int(stmt,sqlite3_find_column_id(stmt, NULL, "size")); + j["maintainer"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, "packagers", "name"))); + // Convert buildtime to ISO-8601 + auto itt = (time_t)sqlite3_column_int(stmt,sqlite3_find_column_id(stmt, NULL, "buildtime")); + ostringstream ss; + ss << std::put_time(gmtime(&itt), "%FT%TZ"); + j["buildtime"] = ss.str(); + // Source URL + j["source_url"] = "https://src.openmamba.org/rpms/" + string(j["name"]); + } else { + j["error"] = "no results from query '" + sql + "'"; + return j; + } + return j; +} + +json DistroqueryAPI::getPackageDetails(string repository, string package, string arch) { + json j; + string sql; + sqlite3_stmt *stmt, *stmt2; + + struct configTag* ct = findRepositoryByTag(repository.c_str()); + if (ct == NULL) { + j["error"] = "repository with tag '" + repository + "' does not exist"; + return j; + } + + auto db = openRepositoryDatabase(ct, arch); + if (!db) { + j["error"] = "error opening database for repository " + repository + " and arch " + arch; + return j; + } + + sql = "SELECT * FROM packages WHERE name = '" + package + "'"; + if (sqlite3_prepare_v2(db, sql.c_str(), sql.length(), &stmt, NULL) == SQLITE_OK) { + long id; + if (sqlite3_step(stmt) == SQLITE_ROW) { + id = sqlite3_column_int(stmt,sqlite3_find_column_id(stmt, NULL, "id")); + j["id"] = id; + j["name"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "name"))); + j["summary"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "summary"))); + j["epoch"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "epoch"))); + j["version"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "version"))); + j["release"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "release"))); + j["group"] = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "groupdescr"))); + string pkg_arch = arch; + if (sqlite3_find_column_id(stmt, NULL, "arch") > 0) { + pkg_arch = reinterpret_cast(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "arch"))); + } + j["arch"] = pkg_arch; + j["size"] = sqlite3_column_int(stmt,sqlite3_find_column_id(stmt, NULL, "size")); + + } else { + j["error"] = "no results from query '" + sql + "'"; + return j; + } + + // Source info + long id_source = sqlite3_column_int(stmt,sqlite3_find_column_id(stmt, NULL, "id_source")); + j["source"] = getPackageSourceDetailsById(repository, id_source); + + // Download URL + j["download_url"] = string(ct->download_prefix) + ct->download_dir + "/RPMS." + string(j["arch"]) + "/" + + string(j["name"]) + "-" + string(j["version"]) + "-" + string(j["release"]) + "." + string(j["arch"]) + ".rpm"; + + // Brothers + sql = "SELECT * FROM packages WHERE id_source=" + to_string(id_source) + " AND NOT id=" + to_string(id); + j["brothers"] = json::array(); + if (sqlite3_prepare_v2(db, sql.c_str(), sql.length(), &stmt2, NULL) == SQLITE_OK) { + while (sqlite3_step(stmt2) == SQLITE_ROW) { + j["brothers"].push_back(reinterpret_cast(sqlite3_column_text(stmt2,sqlite3_find_column_id(stmt2, NULL, "name")))); + } + sqlite3_finalize(stmt2); + } + + // Obsoletes + sql = "SELECT * FROM obsoletes WHERE id_package=" + to_string(id); + j["obsoletesbrothers"] = json::array(); + + + } else { + j["error"] = "error preparing query '" + sql + "'"; + return j; + } + + return j; + +} + void DistroqueryAPI::getApiResponse(string path_info) { cout << "Content-Type: application/json;charset=utf-8\n\n" << endl; @@ -80,15 +193,21 @@ void DistroqueryAPI::getApiResponse(string path_info) { if (v.size() > 1) query_string = v[1]; if (path_split.size() > 0 ) { - if (path_split[0] == "get_repositories") { - // API service: get_repository + if (path_split[0] == "repositories") { + // API service: repositories + if (path_split.size() != 1) + sendErrorResponse("expected exactly 1 argument for " + path_split[0]); cout << configToJsonRepositories(); + } else if (path_split[0] == "package") { + // API service: package + if (path_split.size() != 4) + sendErrorResponse("expected exactly 4 arguments for " + path_split[0]); + auto details = getPackageDetails(path_split[1], path_split[2], path_split[3]); + cout << details.dump(); } else { - cout << setw(4) << json::meta() << endl; + sendErrorResponse("invalid request for service '" + path_split[0]); } } else { - cout << "{\"path_info\":\"" << path << "\",\"query_string\":\"" << query_string << "\"}"; + sendErrorResponse("empty request"); } - - exit(0); } diff --git a/src/DistroqueryAPI.hpp b/src/DistroqueryAPI.hpp index 8c2c738..b13659a 100644 --- a/src/DistroqueryAPI.hpp +++ b/src/DistroqueryAPI.hpp @@ -24,13 +24,13 @@ #include #include -#include #include #include "config.h" +#include "distroquery_functions.hpp" using namespace std; -using json = nlohmann::json; +using json = nlohmann::ordered_json; class DistroqueryAPI { public: @@ -40,6 +40,9 @@ class DistroqueryAPI { private: configTag *config; json configToJsonRepositories(); + void sendErrorResponse(string message); + json getPackageDetails(string repository, string package, string arch); + json getPackageSourceDetailsById(string repository, long id); }; #endif // __DISTROQUERY_API_H diff --git a/src/distroquery.cpp b/src/distroquery.cpp index b574973..1aada88 100644 --- a/src/distroquery.cpp +++ b/src/distroquery.cpp @@ -17,7 +17,7 @@ * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ -#include "distroquery.hpp" +#include "distroquery_functions.hpp" #include "DistroqueryAPI.hpp" #include @@ -114,23 +114,6 @@ char *url_decode(char *str) { return buf; } -int sqlite3_find_column_id(sqlite3_stmt *stmt, const char* table, const char* name) { - const char* colname; - int id = 0; - - while ((colname = sqlite3_column_name(stmt, id))) { - if (!strcmp(colname, name)) { - if (table) { - if (!strcmp(sqlite3_column_table_name(stmt, id), table)) return id; - } else { - return id; - } - } - id++; - } - return -1; -} - int find_query_arch(char* arch) { int i; @@ -195,28 +178,6 @@ char* resolveFilePath(sqlite3 *db, long id, char *buffer) { return buffer; } -sqlite3* openRepositoryDatabase(struct configTag* ct, char* arch) { - char dbname[PATH_MAX]; - sqlite3* db; - - if (arch) { - snprintf(dbname, PATH_MAX, "%s%s-%s.db", ct->repository_dir, ct->tag, arch); - if (sqlite3_open_v2(dbname, (sqlite3**)&db, SQLITE_OPEN_READONLY, NULL)) { - if (db) sqlite3_close(db); - fprintf(stderr, "ERROR: unable to open sqlite3 db %s\n", dbname); - return NULL; - } - } else { - snprintf(dbname, PATH_MAX, "%s%s-sources.db", ct->repository_dir, ct->tag); - if (sqlite3_open_v2(dbname, (sqlite3**)&db, SQLITE_OPEN_READONLY, NULL)) { - if (db) sqlite3_close(db); - fprintf(stderr, "ERROR: unable to open sqlite3 db %s\n", dbname); - return NULL; - } - } - return db; -} - void attachCtDatabases(struct configTag* ct, sqlite3 *db, char* arch) { string dbname; string sql; @@ -875,11 +836,12 @@ void printSpecialQueryResponse() { repository = query + 8; ct = findRepositoryByTag(repository); if (!ct) return; - db = openRepositoryDatabase(ct, NULL); + db = openRepositoryDatabase(ct); + if (!db) return; attachCtDatabases(ct, db, NULL); if (ct->arch[0]) { - dbb = openRepositoryDatabase(ct, NULL); + dbb = openRepositoryDatabase(ct); if (!dbb) return; for (a = 0; a < ARCHS_MAX && ct->arch[a]; a++) { attachRepositoryDatabases(ct, dbb, ct->arch[a], 0); @@ -1041,7 +1003,6 @@ void printSpecialQueryResponse() { void printPackageData() { int i, j; - char dbname[PATH_MAX]; sqlite3 *db, *dbs, *dbf; sqlite3_stmt *statement, *stmt1, *stmt2; char sql[PATH_MAX]; @@ -1053,12 +1014,8 @@ void printPackageData() { int package_id; char *package_name = NULL, *package_summary = NULL; - snprintf(dbname, PATH_MAX, "%s%s-%s.db", ct->repository_dir, ct->tag, query_arch); - if (sqlite3_open_v2(dbname, (sqlite3**)&db, SQLITE_OPEN_READONLY, NULL)) { - if (db) sqlite3_close(db); - fprintf(stderr, "ERROR: unable to open sqlite3 db %s\n", dbname); - return; - } + db = openRepositoryDatabase(ct, query_arch); + if (!db) return; if (!reply_plain) printf("<%s>repository_dir, ct->tag); - if (sqlite3_open_v2(dbname, &dbs, SQLITE_OPEN_READONLY, NULL)) { - if (dbs) sqlite3_close(dbs); - dbs = NULL; - fprintf(stderr, "ERROR: unable to open sqlite3 db %s; aborting.\n", dbname); - return; - } + dbs = openRepositoryDatabase(ct); + if (!dbs) return; + snprintf(sql, PATH_MAX, "SELECT * FROM sources,packagers WHERE sources.id=%d AND sources.id_packager=packagers.id", sqlite3_column_int(statement,sqlite3_find_column_id(statement, NULL, "id_source"))); @@ -1289,8 +1242,8 @@ void printPackageData() { } /* files list */ - snprintf(dbname, PATH_MAX, "%s%s-%s-files.db", ct->repository_dir, ct->tag, query_arch); - if (!sqlite3_open_v2(dbname, &dbf, SQLITE_OPEN_READONLY, NULL)) { + dbf = openRepositoryDatabase(ct, query_arch, "-files"); + if (dbf != NULL) { printf("

%s:
", _("Files list")); snprintf(sql, PATH_MAX, "SELECT * FROM packages_files_rel,fileusers,filegroups WHERE" " packages_files_rel.name='%s' AND" @@ -1630,6 +1583,7 @@ main(int argc, char *argv[]) if (strncmp(path_info, "/api/v1/", strlen(API_PREFIX)) == 0) { DistroqueryAPI *api = new DistroqueryAPI(firstconfigtag); api->getApiResponse(&path_info[strlen(API_PREFIX)]); + exit(0); } else if (strlen(path_info) > 0) { cout << "Content-Type: text/html;charset=utf-8" << endl; cout << "Status: 400 Bad request" << endl << endl; diff --git a/src/distroquery_functions.cpp b/src/distroquery_functions.cpp new file mode 100644 index 0000000..bd6b19f --- /dev/null +++ b/src/distroquery_functions.cpp @@ -0,0 +1,77 @@ +/* + * distroquery_functions - Basic functions used by distroquery + * + * Copyright (C) 2024 by Silvan Calarco + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of version 2 of the GNU General Public License as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY, to the extent permitted by law; without even the implied + * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "distroquery_functions.hpp" +#include "config.h" +#include + +vector split(string str, string token) { + vectorresult; + while(str.size()){ + auto index = str.find(token); + if(index!=string::npos){ + result.push_back(str.substr(0,index)); + str = str.substr(index+token.size()); + if(str.size()==0)result.push_back(str); + }else{ + result.push_back(str); + str = ""; + } + } + return result; +} + +sqlite3* openRepositoryDatabase(struct configTag* ct, string arch, string append) { + string dbname; + sqlite3* db; + + if (arch != "" && arch != "sources") { + dbname = string(ct->repository_dir) + ct->tag + "-" + arch + append + ".db"; + if (sqlite3_open_v2(dbname.c_str(), (sqlite3**)&db, SQLITE_OPEN_READONLY, NULL)) { + if (db) sqlite3_close(db); + fprintf(stderr, "ERROR: unable to open sqlite3 db %s\n", dbname.c_str()); + return NULL; + } + } else { + dbname = string(ct->repository_dir) + ct->tag + "-sources" + append + ".db"; + if (sqlite3_open_v2(dbname.c_str(), (sqlite3**)&db, SQLITE_OPEN_READONLY, NULL)) { + if (db) sqlite3_close(db); + fprintf(stderr, "ERROR: unable to open sqlite3 db %s\n", dbname.c_str()); + return NULL; + } + } + return db; +} + +int sqlite3_find_column_id(sqlite3_stmt *stmt, const char* table, const char* name) { + const char* colname; + int id = 0; + + while ((colname = sqlite3_column_name(stmt, id))) { + if (!strcmp(colname, name)) { + if (table) { + if (!strcmp(sqlite3_column_table_name(stmt, id), table)) return id; + } else { + return id; + } + } + id++; + } + return -1; +} diff --git a/src/include/distroquery.hpp b/src/include/distroquery_functions.hpp similarity index 76% rename from src/include/distroquery.hpp rename to src/include/distroquery_functions.hpp index 0c7ee74..c285101 100644 --- a/src/include/distroquery.hpp +++ b/src/include/distroquery_functions.hpp @@ -1,5 +1,5 @@ /* - * distroquery - tool for querying data generated by distromatic + * distroquery_functions - Basic functions used by distroquery * * Copyright (C) 2024 by Silvan Calarco * @@ -16,6 +16,20 @@ * this program; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ + +#ifndef __DISTROQUERY_FUNCTIONS_H +#define __DISTROQUERY_FUNCTIONS_H + +#include +#include +#include + +using namespace std; + +vector split(string str, string token); +sqlite3* openRepositoryDatabase(struct configTag* ct, string arch = "", string append = ""); +int sqlite3_find_column_id(sqlite3_stmt *stmt, const char* table, const char* name); + #define HDSIZE 16 #define SSSIZE 24 @@ -31,3 +45,5 @@ extern "C" int get_favicon_from_url(const char* url,char *buf,int bufsize); extern "C" void backtraceHandler(int sig); extern "C" struct configTag* findRepositoryByTag(const char *tag); extern "C" struct configTag* read_configuration(const char *confFile); + +#endif // __DISTROQUERY_FUNCTIONS_H