cleanup method
Some checks failed
Build-Test-Publish / create-manifest (push) Has been cancelled
Build-Test-Publish / build (linux/amd64) (push) Has been cancelled
Build-Test-Publish / build (linux/arm64) (push) Has been cancelled

This commit is contained in:
j
2026-01-03 16:04:53 +13:00
parent 612cded1b1
commit dc6e1ff344
6 changed files with 143 additions and 3 deletions

View File

@@ -159,6 +159,23 @@ void HttpController::deleteObject(const drogon::HttpRequestPtr &req,
} }
} }
void HttpController::deleteOldObjects(const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
auto server = Server::getInstance();
if (server) {
server->handle_delete_old_objects(req, std::move(callback));
} else {
auto resp = drogon::HttpResponse::newHttpResponse();
resp->setStatusCode(drogon::k500InternalServerError);
// Try to add security headers if server instance is available
auto srv = Server::getInstance();
if (srv) {
srv->add_security_headers(resp);
}
callback(resp);
}
}
void HttpController::getStatus(const drogon::HttpRequestPtr &req, void HttpController::getStatus(const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback) { std::function<void(const drogon::HttpResponsePtr &)> &&callback) {
auto resp = drogon::HttpResponse::newHttpResponse(); auto resp = drogon::HttpResponse::newHttpResponse();

View File

@@ -17,6 +17,7 @@ public:
ADD_METHOD_TO(HttpController::updateObject, "/update", {drogon::Put}); ADD_METHOD_TO(HttpController::updateObject, "/update", {drogon::Put});
ADD_METHOD_TO(HttpController::getMetadata, "/meta/{1}", {drogon::Get}); ADD_METHOD_TO(HttpController::getMetadata, "/meta/{1}", {drogon::Get});
ADD_METHOD_TO(HttpController::deleteObject, "/deleteobject", {drogon::Get}); ADD_METHOD_TO(HttpController::deleteObject, "/deleteobject", {drogon::Get});
ADD_METHOD_TO(HttpController::deleteOldObjects, "/deleteoldobjects", {drogon::Get});
ADD_METHOD_TO(HttpController::getStatus, "/status", {drogon::Get}); ADD_METHOD_TO(HttpController::getStatus, "/status", {drogon::Get});
ADD_METHOD_TO(HttpController::getObject, "/object/{1}", {drogon::Get}); ADD_METHOD_TO(HttpController::getObject, "/object/{1}", {drogon::Get});
ADD_METHOD_TO(HttpController::getRoot, "/", {drogon::Get}); ADD_METHOD_TO(HttpController::getRoot, "/", {drogon::Get});
@@ -53,7 +54,10 @@ public:
void deleteObject(const drogon::HttpRequestPtr &req, void deleteObject(const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback); std::function<void(const drogon::HttpResponsePtr &)> &&callback);
void deleteOldObjects(const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback);
void getStatus(const drogon::HttpRequestPtr &req, void getStatus(const drogon::HttpRequestPtr &req,
std::function<void(const drogon::HttpResponsePtr &)> &&callback); std::function<void(const drogon::HttpResponsePtr &)> &&callback);

View File

@@ -238,7 +238,7 @@ bool Database::get(const std::string& hash_or_labeltag, dbEntry& entry) {
bool Database::list(std::vector<dbEntry>& entries) { bool Database::list(std::vector<dbEntry>& entries) {
std::string sql = "SELECT hash, labeltags, metadata FROM objects;"; std::string sql = "SELECT hash, labeltags, metadata FROM objects;";
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) { if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
return false; return false;
} }
@@ -249,7 +249,35 @@ bool Database::list(std::vector<dbEntry>& entries) {
entry.hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)); entry.hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
std::string labeltags_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1)); std::string labeltags_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
std::string metadata_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2)); std::string metadata_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
entry.labeltags = nlohmann::json::parse(labeltags_str).get<std::vector<std::string>>();
entry.metadata = nlohmann::json::parse(metadata_str);
entries.push_back(entry);
}
sqlite3_finalize(stmt);
return true;
}
bool Database::list_by_label(const std::string& label, std::vector<dbEntry>& entries) {
// Find all entries that have at least one labeltag starting with "label:"
std::string sql = "SELECT hash, labeltags, metadata FROM objects WHERE labeltags LIKE ?;";
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
return false;
}
std::string pattern = "%\"" + label + ":%";
sqlite3_bind_text(stmt, 1, pattern.c_str(), -1, SQLITE_STATIC);
entries.clear();
while (sqlite3_step(stmt) == SQLITE_ROW) {
dbEntry entry;
entry.hash = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
std::string labeltags_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 1));
std::string metadata_str = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 2));
entry.labeltags = nlohmann::json::parse(labeltags_str).get<std::vector<std::string>>(); entry.labeltags = nlohmann::json::parse(labeltags_str).get<std::vector<std::string>>();
entry.metadata = nlohmann::json::parse(metadata_str); entry.metadata = nlohmann::json::parse(metadata_str);
entries.push_back(entry); entries.push_back(entry);

View File

@@ -25,6 +25,7 @@ class Database {
bool remove_by_hash(const std::string& hash); bool remove_by_hash(const std::string& hash);
bool get(const std::string& hash_or_labeltag, dbEntry& entry); bool get(const std::string& hash_or_labeltag, dbEntry& entry);
bool list(std::vector<dbEntry>& entries); bool list(std::vector<dbEntry>& entries);
bool list_by_label(const std::string& label, std::vector<dbEntry>& entries);
bool update_or_insert(const dbEntry& entry); bool update_or_insert(const dbEntry& entry);
private: private:
std::filesystem::path path_; std::filesystem::path path_;

View File

@@ -6,6 +6,7 @@
#include <chrono> // For seeding random number generator #include <chrono> // For seeding random number generator
#include <vector> // For getAllKeys #include <vector> // For getAllKeys
#include <string_view> // For litecask values #include <string_view> // For litecask values
#include <set> // For handle_delete_old_objects
#include <stdexcept> // For std::runtime_error #include <stdexcept> // For std::runtime_error
#include <sstream> #include <sstream>
@@ -476,6 +477,94 @@ void Server::handle_delete_object(const drogon::HttpRequestPtr& req, std::functi
callback(resp); callback(resp);
} }
void Server::handle_delete_old_objects(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback) {
auto resp = drogon::HttpResponse::newHttpResponse();
std::map<std::string, std::string> params;
if (!validate_write_request(req, resp, {"label"}, params)) {
add_security_headers(resp);
callback(resp);
return;
}
std::string label = params["label"];
// Get all entries for this label
std::vector<dbEntry> entries;
if (!db_->list_by_label(label, entries)) {
resp->setStatusCode(drogon::k500InternalServerError);
nlohmann::json response = {{"result", "error"}, {"error", "Failed to list objects for label: " + label}};
resp->setBody(response.dump());
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
add_security_headers(resp);
callback(resp);
return;
}
if (entries.empty()) {
nlohmann::json response = {{"result", "success"}, {"deleted", 0}, {"message", "No objects found for label: " + label}};
resp->setBody(response.dump());
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
add_security_headers(resp);
callback(resp);
return;
}
// Find hashes that have at least one tag containing 'latest' for this label
std::set<std::string> hashes_to_keep;
for (const auto& entry : entries) {
for (const auto& labeltag : entry.labeltags) {
// Check if this labeltag belongs to our label and contains 'latest'
if (labeltag.rfind(label + ":", 0) == 0) {
std::string tag = labeltag.substr(label.length() + 1);
if (tag.find("latest") != std::string::npos) {
hashes_to_keep.insert(entry.hash);
break;
}
}
}
}
// Delete entries that don't have a 'latest' tag
int deleted_count = 0;
std::vector<std::string> errors;
for (const auto& entry : entries) {
if (hashes_to_keep.find(entry.hash) != hashes_to_keep.end()) {
continue; // Skip entries with 'latest' tags
}
// Remove from database
if (!db_->remove_by_hash(entry.hash)) {
errors.push_back("Failed to remove database entry for hash: " + entry.hash);
continue;
}
// Remove the file
std::filesystem::path file_path = config_.object_store_path / entry.hash;
if (std::filesystem::exists(file_path) && std::filesystem::is_regular_file(file_path)) {
try {
std::filesystem::remove(file_path);
} catch (const std::filesystem::filesystem_error& e) {
errors.push_back("Failed to delete file for hash " + entry.hash + ": " + e.what());
}
}
deleted_count++;
}
nlohmann::json response;
if (errors.empty()) {
response = {{"result", "success"}, {"deleted", deleted_count}};
} else {
response = {{"result", "partial"}, {"deleted", deleted_count}, {"errors", errors}};
}
resp->setBody(response.dump());
resp->setContentTypeCode(drogon::CT_APPLICATION_JSON);
add_security_headers(resp);
callback(resp);
}
void Server::handle_exists(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key) { void Server::handle_exists(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key) {
nlohmann::json response; nlohmann::json response;
dbEntry entry; dbEntry entry;

View File

@@ -47,6 +47,7 @@ public:
void handle_get_directory(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback); void handle_get_directory(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback);
void handle_get_metadata(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key); void handle_get_metadata(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key);
void handle_delete_object(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback); void handle_delete_object(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback);
void handle_delete_old_objects(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback);
void handle_exists(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key); void handle_exists(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback, const std::string& key);
void handle_cors_preflight(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback); void handle_cors_preflight(const drogon::HttpRequestPtr& req, std::function<void(const drogon::HttpResponsePtr &)>&& callback);
void add_cors_headers(const drogon::HttpRequestPtr& req, const drogon::HttpResponsePtr& res); void add_cors_headers(const drogon::HttpRequestPtr& req, const drogon::HttpResponsePtr& res);