DistroqueryAPI: first part of implementation of /package API service

This commit is contained in:
Silvan Calarco 2024-05-31 18:34:23 +02:00
parent 758da30631
commit ee5803e622
6 changed files with 255 additions and 85 deletions

View File

@ -67,6 +67,7 @@ target_compile_options(distromatic PUBLIC -O2 -g -Wall -std=gnu11 -pedantic ${RP
# -fno-toplevel-reorder # -fno-toplevel-reorder
add_executable(distroquery add_executable(distroquery
distroquery_functions.cpp
DistroqueryAPI.cpp DistroqueryAPI.cpp
distroquery.cpp distroquery.cpp
functions.c functions.c

View File

@ -18,29 +18,13 @@
*/ */
#include "DistroqueryAPI.hpp" #include "DistroqueryAPI.hpp"
#include <functions.h>
vector<string> split(string str, string token) {
vector<string>result;
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;
}
DistroqueryAPI::DistroqueryAPI(configTag* conf): config(conf) { DistroqueryAPI::DistroqueryAPI(configTag* conf): config(conf) {
} }
json DistroqueryAPI::configToJsonRepositories() { json DistroqueryAPI::configToJsonRepositories() {
json j; json j;
j["repositories"] = {};
struct configTag *ct = config; struct configTag *ct = config;
while (ct) { while (ct) {
json archs = {}; json archs = {};
@ -59,12 +43,141 @@ json DistroqueryAPI::configToJsonRepositories() {
{ "description", ct->description }, { "description", ct->description },
{ "parents", parents } { "parents", parents }
}; };
j["repositories"].push_back(repository); j.push_back(repository);
ct = ct->next; ct = ct->next;
} }
return j; 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<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "name")));
j["summary"] = reinterpret_cast<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "summary")));
j["epoch"] = reinterpret_cast<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "epoch")));
j["version"] = reinterpret_cast<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "version")));
j["release"] = reinterpret_cast<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "release")));
j["group"] = reinterpret_cast<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "groupdescr")));
j["license"] = reinterpret_cast<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "license")));
j["url"] = reinterpret_cast<const char*>(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<const char*>(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<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "name")));
j["summary"] = reinterpret_cast<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "summary")));
j["epoch"] = reinterpret_cast<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "epoch")));
j["version"] = reinterpret_cast<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "version")));
j["release"] = reinterpret_cast<const char*>(sqlite3_column_text(stmt,sqlite3_find_column_id(stmt, NULL, "release")));
j["group"] = reinterpret_cast<const char*>(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<const char*>(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<const char*>(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) { void DistroqueryAPI::getApiResponse(string path_info) {
cout << "Content-Type: application/json;charset=utf-8\n\n" << endl; 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 (v.size() > 1) query_string = v[1];
if (path_split.size() > 0 ) { if (path_split.size() > 0 ) {
if (path_split[0] == "get_repositories") { if (path_split[0] == "repositories") {
// API service: get_repository // API service: repositories
if (path_split.size() != 1)
sendErrorResponse("expected exactly 1 argument for " + path_split[0]);
cout << configToJsonRepositories(); 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 { } else {
cout << setw(4) << json::meta() << endl; sendErrorResponse("invalid request for service '" + path_split[0]);
} }
} else { } else {
cout << "{\"path_info\":\"" << path << "\",\"query_string\":\"" << query_string << "\"}"; sendErrorResponse("empty request");
} }
exit(0);
} }

View File

@ -24,13 +24,13 @@
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <vector>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "config.h" #include "config.h"
#include "distroquery_functions.hpp"
using namespace std; using namespace std;
using json = nlohmann::json; using json = nlohmann::ordered_json;
class DistroqueryAPI { class DistroqueryAPI {
public: public:
@ -40,6 +40,9 @@ class DistroqueryAPI {
private: private:
configTag *config; configTag *config;
json configToJsonRepositories(); json configToJsonRepositories();
void sendErrorResponse(string message);
json getPackageDetails(string repository, string package, string arch);
json getPackageSourceDetailsById(string repository, long id);
}; };
#endif // __DISTROQUERY_API_H #endif // __DISTROQUERY_API_H

View File

@ -17,7 +17,7 @@
* 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/ */
#include "distroquery.hpp" #include "distroquery_functions.hpp"
#include "DistroqueryAPI.hpp" #include "DistroqueryAPI.hpp"
#include <getopt.h> #include <getopt.h>
@ -114,23 +114,6 @@ char *url_decode(char *str) {
return buf; 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 find_query_arch(char* arch) {
int i; int i;
@ -195,28 +178,6 @@ char* resolveFilePath(sqlite3 *db, long id, char *buffer) {
return 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) { void attachCtDatabases(struct configTag* ct, sqlite3 *db, char* arch) {
string dbname; string dbname;
string sql; string sql;
@ -875,11 +836,12 @@ void printSpecialQueryResponse() {
repository = query + 8; repository = query + 8;
ct = findRepositoryByTag(repository); ct = findRepositoryByTag(repository);
if (!ct) return; if (!ct) return;
db = openRepositoryDatabase(ct, NULL); db = openRepositoryDatabase(ct);
if (!db) return;
attachCtDatabases(ct, db, NULL); attachCtDatabases(ct, db, NULL);
if (ct->arch[0]) { if (ct->arch[0]) {
dbb = openRepositoryDatabase(ct, NULL); dbb = openRepositoryDatabase(ct);
if (!dbb) return; if (!dbb) return;
for (a = 0; a < ARCHS_MAX && ct->arch[a]; a++) { for (a = 0; a < ARCHS_MAX && ct->arch[a]; a++) {
attachRepositoryDatabases(ct, dbb, ct->arch[a], 0); attachRepositoryDatabases(ct, dbb, ct->arch[a], 0);
@ -1041,7 +1003,6 @@ void printSpecialQueryResponse() {
void printPackageData() { void printPackageData() {
int i, j; int i, j;
char dbname[PATH_MAX];
sqlite3 *db, *dbs, *dbf; sqlite3 *db, *dbs, *dbf;
sqlite3_stmt *statement, *stmt1, *stmt2; sqlite3_stmt *statement, *stmt1, *stmt2;
char sql[PATH_MAX]; char sql[PATH_MAX];
@ -1053,12 +1014,8 @@ void printPackageData() {
int package_id; int package_id;
char *package_name = NULL, *package_summary = NULL; char *package_name = NULL, *package_summary = NULL;
snprintf(dbname, PATH_MAX, "%s%s-%s.db", ct->repository_dir, ct->tag, query_arch); db = openRepositoryDatabase(ct, query_arch);
if (sqlite3_open_v2(dbname, (sqlite3**)&db, SQLITE_OPEN_READONLY, NULL)) { if (!db) return;
if (db) sqlite3_close(db);
fprintf(stderr, "ERROR: unable to open sqlite3 db %s\n", dbname);
return;
}
if (!reply_plain) printf("<%s><![CDATA[", reply_xmltag); if (!reply_plain) printf("<%s><![CDATA[", reply_xmltag);
@ -1071,13 +1028,9 @@ void printPackageData() {
package_name = strdup((const char*)sqlite3_column_text(statement,sqlite3_find_column_id(statement, NULL, "name"))); package_name = strdup((const char*)sqlite3_column_text(statement,sqlite3_find_column_id(statement, NULL, "name")));
package_summary = strdup((const char*)sqlite3_column_text(statement,sqlite3_find_column_id(statement, NULL, "summary"))); package_summary = strdup((const char*)sqlite3_column_text(statement,sqlite3_find_column_id(statement, NULL, "summary")));
snprintf(dbname, PATH_MAX, "%s%s-sources.db", ct->repository_dir, ct->tag); dbs = openRepositoryDatabase(ct);
if (sqlite3_open_v2(dbname, &dbs, SQLITE_OPEN_READONLY, NULL)) { if (!dbs) return;
if (dbs) sqlite3_close(dbs);
dbs = NULL;
fprintf(stderr, "ERROR: unable to open sqlite3 db %s; aborting.\n", dbname);
return;
}
snprintf(sql, PATH_MAX, snprintf(sql, PATH_MAX,
"SELECT * FROM sources,packagers WHERE sources.id=%d AND sources.id_packager=packagers.id", "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"))); sqlite3_column_int(statement,sqlite3_find_column_id(statement, NULL, "id_source")));
@ -1289,8 +1242,8 @@ void printPackageData() {
} }
/* files list */ /* files list */
snprintf(dbname, PATH_MAX, "%s%s-%s-files.db", ct->repository_dir, ct->tag, query_arch); dbf = openRepositoryDatabase(ct, query_arch, "-files");
if (!sqlite3_open_v2(dbname, &dbf, SQLITE_OPEN_READONLY, NULL)) { if (dbf != NULL) {
printf("<br><br>%s:<br><table class='queryfiletable'>", _("Files list")); printf("<br><br>%s:<br><table class='queryfiletable'>", _("Files list"));
snprintf(sql, PATH_MAX, "SELECT * FROM packages_files_rel,fileusers,filegroups WHERE" snprintf(sql, PATH_MAX, "SELECT * FROM packages_files_rel,fileusers,filegroups WHERE"
" packages_files_rel.name='%s' AND" " 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) { if (strncmp(path_info, "/api/v1/", strlen(API_PREFIX)) == 0) {
DistroqueryAPI *api = new DistroqueryAPI(firstconfigtag); DistroqueryAPI *api = new DistroqueryAPI(firstconfigtag);
api->getApiResponse(&path_info[strlen(API_PREFIX)]); api->getApiResponse(&path_info[strlen(API_PREFIX)]);
exit(0);
} else if (strlen(path_info) > 0) { } else if (strlen(path_info) > 0) {
cout << "Content-Type: text/html;charset=utf-8" << endl; cout << "Content-Type: text/html;charset=utf-8" << endl;
cout << "Status: 400 Bad request" << endl << endl; cout << "Status: 400 Bad request" << endl << endl;

View File

@ -0,0 +1,77 @@
/*
* distroquery_functions - Basic functions used by distroquery
*
* Copyright (C) 2024 by Silvan Calarco <silvan.calarco@mambasoft.it>
*
* 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 <string.h>
vector<string> split(string str, string token) {
vector<string>result;
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;
}

View File

@ -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 <silvan.calarco@mambasoft.it> * Copyright (C) 2024 by Silvan Calarco <silvan.calarco@mambasoft.it>
* *
@ -16,6 +16,20 @@
* this program; if not, write to the Free Software Foundation, Inc., * this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
*/ */
#ifndef __DISTROQUERY_FUNCTIONS_H
#define __DISTROQUERY_FUNCTIONS_H
#include <iostream>
#include <vector>
#include <sqlite3.h>
using namespace std;
vector<string> 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 HDSIZE 16
#define SSSIZE 24 #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" void backtraceHandler(int sig);
extern "C" struct configTag* findRepositoryByTag(const char *tag); extern "C" struct configTag* findRepositoryByTag(const char *tag);
extern "C" struct configTag* read_configuration(const char *confFile); extern "C" struct configTag* read_configuration(const char *confFile);
#endif // __DISTROQUERY_FUNCTIONS_H