Add metadata!

This commit is contained in:
Your Name
2025-05-03 10:14:16 +12:00
parent 16754a48d4
commit 24a4c66c13
8 changed files with 283 additions and 149 deletions

View File

@@ -1,5 +1,3 @@
#include "server.hpp"
#include "hash.hpp"
#include <filesystem>
#include <iostream>
#include <sstream>
@@ -11,6 +9,12 @@
#include <sqlite3.h> // Include SQLite
#include <stdexcept> // For std::runtime_error
#include "server.hpp"
#include "hash.hpp"
#include "compress.hpp"
namespace simple_object_storage {
// Simple RAII helper for file deletion
@@ -46,36 +50,18 @@ void execute_sql(sqlite3* db, const char* sql, const std::string& error_msg_pref
}
bool Server::init_db() {
db_path_ = config_.object_store_path / "index.db";
int rc = sqlite3_open(db_path_.c_str(), &db_);
if (rc != SQLITE_OK) {
std::cerr << "Failed to open/create SQLite database '" << db_path_ << "': " << sqlite3_errmsg(db_) << std::endl;
db_ = nullptr; // Ensure db_ is null if open failed
return false;
}
try {
// Enable WAL mode for better concurrency
execute_sql(db_, "PRAGMA journal_mode=WAL;", "Failed to set WAL mode");
// Create table if it doesn't exist
const char* create_table_sql =
"CREATE TABLE IF NOT EXISTS objects ("
"label_tag TEXT PRIMARY KEY UNIQUE NOT NULL, "
"hash TEXT NOT NULL);";
execute_sql(db_, create_table_sql, "Failed to create objects table");
std::filesystem::path db_path = config_.object_store_path / "index.db";
db_ = std::make_unique<Database>(db_path);
return true;
} catch (const std::runtime_error& e) {
std::cerr << "Database initialization error: " << e.what() << std::endl;
sqlite3_close(db_);
db_ = nullptr;
return false;
}
return true;
}
Server::Server(const ServerConfig& config)
: config_(config), running_(false), db_(nullptr) {
: config_(config), running_(false) {
// Ensure object store directory exists
try {
std::filesystem::create_directories(config_.object_store_path);
@@ -93,10 +79,6 @@ Server::Server(const ServerConfig& config)
Server::~Server() {
stop();
if (db_) {
sqlite3_close(db_); // Close the database connection
db_ = nullptr;
}
}
bool Server::start() {
@@ -151,9 +133,14 @@ void Server::setup_routes() {
});
// Upload object
server_.Put("/([^/]+)/(.*)", [this](const httplib::Request& req, httplib::Response& res) { // Adjusted regex slightly for label:tag
server_.Put("/([^/]+)/(.*)", [this](const httplib::Request& req, httplib::Response& res) {
handle_put_object(req, res);
});
// Get metadata for label:tag
server_.Get("/meta/(.*)", [this](const httplib::Request& req, httplib::Response& res) {
handle_get_metadata(req, res);
});
}
void Server::handle_get_object(const httplib::Request& req, httplib::Response& res) {
@@ -170,51 +157,23 @@ void Server::handle_get_object(const httplib::Request& req, httplib::Response& r
}
if (!is_hash_lookup) {
// Lookup by label:tag in the SQLite database
sqlite3_stmt* stmt = nullptr;
const char* sql = "SELECT hash FROM objects WHERE label_tag = ?;";
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
std::cerr << "Failed to prepare statement (get hash): " << sqlite3_errmsg(db_) << std::endl;
res.status = 500;
res.set_content("Database error preparing statement", "text/plain");
return;
}
sqlite3_bind_text(stmt, 1, key.c_str(), -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const unsigned char* text = sqlite3_column_text(stmt, 0);
if (text) {
hash_str = reinterpret_cast<const char*>(text);
}
} else if (rc == SQLITE_DONE) {
// Not found
sqlite3_finalize(stmt);
// Lookup by label:tag in the database
dbEntry entry;
if (!db_->get(key, entry)) {
res.status = 404;
res.set_content("Object not found (label:tag)", "text/plain");
return;
} else {
std::cerr << "Failed to execute statement (get hash): " << sqlite3_errmsg(db_) << std::endl;
sqlite3_finalize(stmt);
res.status = 500;
res.set_content("Database error executing statement", "text/plain");
return;
}
sqlite3_finalize(stmt);
hash_str = entry.hash;
} else {
// Lookup directly by hash
hash_str = key;
}
if (hash_str.empty()) {
// Should have been caught earlier if not found, but as a safeguard
res.status = 404;
res.set_content("Object hash could not be determined", "text/plain");
return;
res.status = 404;
res.set_content("Object hash could not be determined", "text/plain");
return;
}
// Construct the file path using the hash string
@@ -233,68 +192,30 @@ void Server::handle_get_object(const httplib::Request& req, httplib::Response& r
void Server::handle_get_hash(const httplib::Request& req, httplib::Response& res) {
const auto& label_tag = req.matches[1].str();
sqlite3_stmt* stmt = nullptr;
const char* sql = "SELECT hash FROM objects WHERE label_tag = ?;";
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
std::cerr << "Failed to prepare statement (get hash direct): " << sqlite3_errmsg(db_) << std::endl;
res.status = 500;
res.set_content("Database error preparing statement", "text/plain");
dbEntry entry;
if (!db_->get(label_tag, entry)) {
res.status = 404;
res.set_content("Label:tag not found", "text/plain");
return;
}
sqlite3_bind_text(stmt, 1, label_tag.c_str(), -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (rc == SQLITE_ROW) {
const unsigned char* text = sqlite3_column_text(stmt, 0);
if (text) {
res.set_content(reinterpret_cast<const char*>(text), "text/plain");
}
} else if (rc == SQLITE_DONE) {
res.status = 404;
res.set_content("Label:tag not found", "text/plain");
} else {
std::cerr << "Failed to execute statement (get hash direct): " << sqlite3_errmsg(db_) << std::endl;
res.status = 500;
res.set_content("Database error executing statement", "text/plain");
}
sqlite3_finalize(stmt);
res.set_content(entry.hash, "text/plain");
}
void Server::handle_get_directory(const httplib::Request& /*req*/, httplib::Response& res) {
std::stringstream ss;
sqlite3_stmt* stmt = nullptr;
const char* sql = "SELECT label_tag, hash FROM objects;";
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
std::cerr << "Failed to prepare statement (get dir): " << sqlite3_errmsg(db_) << std::endl;
std::vector<dbEntry> entries;
if (!db_->list(entries)) {
res.status = 500;
res.set_content("Database error preparing statement", "text/plain");
res.set_content("Database error retrieving directory", "text/plain");
return;
}
while ((rc = sqlite3_step(stmt)) == SQLITE_ROW) {
const unsigned char* label_tag_text = sqlite3_column_text(stmt, 0);
const unsigned char* hash_text = sqlite3_column_text(stmt, 1);
if (label_tag_text && hash_text) {
ss << reinterpret_cast<const char*>(label_tag_text) << ","
<< reinterpret_cast<const char*>(hash_text) << "\n";
}
for (const auto& entry : entries) {
ss << entry.label_tag << "," << entry.hash << "\n";
}
if (rc != SQLITE_DONE) {
std::cerr << "Failed to execute/iterate statement (get dir): " << sqlite3_errmsg(db_) << std::endl;
// Don't overwrite potential results, but log error
if (ss.str().empty()) { // Only send error if no data was retrieved
res.status = 500;
res.set_content("Database error executing statement", "text/plain");
}
}
sqlite3_finalize(stmt);
res.set_content(ss.str(), "text/plain");
}
@@ -345,6 +266,8 @@ void Server::handle_put_object(const httplib::Request& req, httplib::Response& r
return;
}
nlohmann::json metadata = get_metadata(temp_path.string());
// Move file to final location
std::string hash_str = std::to_string(hash);
std::filesystem::path final_path = config_.object_store_path / hash_str;
@@ -360,27 +283,13 @@ void Server::handle_put_object(const httplib::Request& req, httplib::Response& r
}
}
// Update SQLite index (INSERT OR REPLACE)
sqlite3_stmt* stmt = nullptr;
const char* sql = "INSERT OR REPLACE INTO objects (label_tag, hash) VALUES (?, ?);";
int rc = sqlite3_prepare_v2(db_, sql, -1, &stmt, nullptr);
if (rc != SQLITE_OK) {
std::cerr << "Failed to prepare statement (put object): " << sqlite3_errmsg(db_) << std::endl;
res.status = 500;
res.set_content("Database error preparing statement", "text/plain");
// Attempt to clean up the moved file if index fails
try { if (std::filesystem::exists(final_path)) std::filesystem::remove(final_path); } catch(...) {};
return;
}
// Update database index
dbEntry entry;
entry.label_tag = label_tag;
entry.hash = hash_str;
entry.metadata = nlohmann::json::object(); // Empty metadata for now
sqlite3_bind_text(stmt, 1, label_tag.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, hash_str.c_str(), -1, SQLITE_STATIC);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) {
std::cerr << "Failed to execute statement (put object): " << sqlite3_errmsg(db_) << std::endl;
if (!db_->insert(entry)) {
res.status = 500;
res.set_content("Failed to update database index", "text/plain");
// Attempt to clean up the moved file if index fails
@@ -391,6 +300,25 @@ void Server::handle_put_object(const httplib::Request& req, httplib::Response& r
res.set_content(hash_str, "text/plain");
}
void Server::handle_get_metadata(const httplib::Request& req, httplib::Response& res) {
const auto& label_tag = req.matches[1].str();
dbEntry entry;
if (!db_->get(label_tag, entry)) {
res.status = 404;
res.set_content("Metadata not found for label:tag: " + label_tag, "text/plain");
return;
}
try {
res.set_content(entry.metadata.dump(), "application/json");
} catch (const nlohmann::json::exception& e) {
std::cerr << "Error serializing metadata for " << label_tag << ": " << e.what() << std::endl;
res.status = 500;
res.set_content("Internal server error: Failed to serialize metadata", "text/plain");
}
}
bool Server::validate_write_token(const std::string& token) const {
return std::find(config_.write_tokens.begin(), config_.write_tokens.end(), token) != config_.write_tokens.end();
}
@@ -403,4 +331,24 @@ std::pair<std::string, std::string> Server::parse_label_tag(const std::string& l
return {label_tag.substr(0, colon_pos), label_tag.substr(colon_pos + 1)};
}
nlohmann::json Server::get_metadata(const std::string &file_path) const
{
nlohmann::json metadata;
// get the file size
metadata["file_size"] = std::filesystem::file_size(file_path);
// get the file modification time
auto ftime = std::filesystem::last_write_time(file_path);
auto sctp = std::chrono::time_point_cast<std::chrono::system_clock::duration>(
ftime - std::filesystem::file_time_type::clock::now()
+ std::chrono::system_clock::now()
);
metadata["file_modification_time"] = std::chrono::system_clock::to_time_t(sctp);
metadata["tgz_content_hash"] = get_hash_from_tgz(file_path);
return metadata;
}
} // namespace simple_object_storage