From 52dcaada2d8e1de682f98bdc88536a3f64602af1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 25 May 2025 11:30:18 +1200 Subject: [PATCH] Tidy --- src/put_handler.cpp | 176 ++++++++++++++++++++++++++++++++++++++++ src/put_handler.hpp | 21 +++++ src/server.cpp | 191 ++------------------------------------------ src/server.hpp | 39 ++++----- src/utils.cpp | 28 +++++++ src/utils.hpp | 19 +++++ 6 files changed, 267 insertions(+), 207 deletions(-) create mode 100644 src/put_handler.cpp create mode 100644 src/put_handler.hpp create mode 100644 src/utils.cpp create mode 100644 src/utils.hpp diff --git a/src/put_handler.cpp b/src/put_handler.cpp new file mode 100644 index 0000000..df5ba5b --- /dev/null +++ b/src/put_handler.cpp @@ -0,0 +1,176 @@ +#include "put_handler.hpp" +#include "hash.hpp" +#include "compress.hpp" +#include "string_utils.hpp" +#include "utils.hpp" + +#include +#include +#include +#include + +namespace simple_object_storage { + +PutHandler::PutHandler(Server& server) : server_(server) {} + +void PutHandler::handle_put_object(const httplib::Request& req, httplib::Response& res) { + // Check all request parameters first before processing any data + + std::map params; + if (!server_.validate_write_request(req, res, {}, params)) { // No required params now since token is in header + return; + } + + // 1. Check we're in the /upload path + if (req.path != "/upload") { + res.status = 404; + nlohmann::json response = {{"result", "error"}, {"error", "Not found - put requests must be to /upload"}}; + res.set_content(response.dump(), "application/json"); + return; + } + + // Parse the multipart form data + if (!req.has_file("file")) { + res.status = 400; + nlohmann::json response = {{"result", "error"}, {"error", "No file provided in upload"}}; + res.set_content(response.dump(), "application/json"); + return; + } + + // Get the file data + const auto& file = req.get_file_value("file"); + + // Parse metadata if provided + nlohmann::json metadata; + if (req.has_file("metadata")) { + try { + const auto& metadata_file = req.get_file_value("metadata"); + metadata = nlohmann::json::parse(metadata_file.content); + } catch (const nlohmann::json::parse_error& e) { + res.status = 400; + nlohmann::json response = {{"result", "error"}, {"error", "Invalid JSON metadata: " + std::string(e.what())}}; + res.set_content(response.dump(), "application/json"); + return; + } + } + + // Validate required metadata fields + if (!metadata.contains("labeltag")) { + res.status = 400; + nlohmann::json response = {{"result", "error"}, {"error", "Missing required metadata field: labeltag"}}; + res.set_content(response.dump(), "application/json"); + return; + } + + // Extract labeltag and validate format + std::string labeltag = metadata["labeltag"]; + auto [label, tag] = server_.parse_label_tag(labeltag); + if (label.empty() || tag.empty()) { + res.status = 400; + nlohmann::json response = {{"result", "error"}, {"error", "Invalid label:tag format"}}; + res.set_content(response.dump(), "application/json"); + return; + } + + // Add filename to metadata if not provided + if (!metadata.contains("filename")) { + metadata["filename"] = file.filename; + } + + // Now that all parameters are validated, process the upload + + // Generate a random number for the temporary filename + std::mt19937_64 rng(std::chrono::high_resolution_clock::now().time_since_epoch().count()); + std::uniform_int_distribution dist; + uint64_t random_num = dist(rng); + std::string temp_filename = "temp_" + std::to_string(random_num); + + // Create temporary file + std::filesystem::path temp_path = server_.config_.object_store_path / temp_filename; + std::ofstream temp_file(temp_path, std::ios::binary); + if (!temp_file.is_open()) { + res.status = 500; + nlohmann::json response = {{"result", "error"}, {"error", "Failed to create temporary file"}}; + res.set_content(response.dump(), "application/json"); + return; + } + + // Write file content to temporary file + if (!temp_file.write(file.content.c_str(), file.content.size())) { + res.status = 500; + nlohmann::json response = {{"result", "error"}, {"error", "Failed to write to temporary file"}}; + res.set_content(response.dump(), "application/json"); + temp_file.close(); + std::filesystem::remove(temp_path); + return; + } + + temp_file.close(); + + // Ensure the temporary file is removed even if errors occur + ScopeFileDeleter temp_file_deleter(temp_path); + + // Calculate hash + uint64_t hash = hash_file(temp_path.string()); + if (hash == 0) { + res.status = 500; + nlohmann::json response = {{"result", "error"}, {"error", "Failed to calculate hash"}}; + res.set_content(response.dump(), "application/json"); + return; + } + + // Add file metadata + add_file_metadata(temp_path.string(), metadata); + + // Check if filename ends with ".tgz" using the utility function + if (utils::ends_with(metadata["filename"], ".tgz")) { + metadata["tgz_content_hash"] = get_hash_from_tgz(temp_path.string()); + } + + // Move file to final location + std::filesystem::path final_path = server_.config_.object_store_path / std::to_string(hash); + if (!std::filesystem::exists(final_path)) { + try { + std::filesystem::rename(temp_path, final_path); + temp_file_deleter.release(); + } catch (const std::filesystem::filesystem_error& e) { + std::cerr << "Error renaming temp file: " << e.what() << std::endl; + res.status = 500; + nlohmann::json response = {{"result", "error"}, {"error", "Failed to store object file"}}; + res.set_content(response.dump(), "application/json"); + return; + } + } + + // Update database index + dbEntry entry; + entry.label_tag = labeltag; + entry.hash = std::to_string(hash); + entry.metadata = metadata; // Store the complete metadata + + if (!server_.db_->update_or_insert(entry)) { + res.status = 500; + nlohmann::json response = {{"result", "error"}, {"error", "Failed to update database index"}}; + res.set_content(response.dump(), "application/json"); + // Attempt to clean up the moved file if index fails + try { if (std::filesystem::exists(final_path)) std::filesystem::remove(final_path); } catch(...) {}; + return; + } + + res.set_content(nlohmann::json({{"result", "success"}, {"hash", std::to_string(hash)}}).dump(), "application/json"); +} + +void PutHandler::add_file_metadata(const std::string& file_path, nlohmann::json& metadata) const { + // 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( + 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); +} + +} // namespace simple_object_storage \ No newline at end of file diff --git a/src/put_handler.hpp b/src/put_handler.hpp new file mode 100644 index 0000000..e6fac8e --- /dev/null +++ b/src/put_handler.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include +#include +#include "server.hpp" +#include "httplib.hpp" +#include "json.hpp" + +namespace simple_object_storage { + +class PutHandler { +public: + PutHandler(Server& server); + void handle_put_object(const httplib::Request& req, httplib::Response& res); + +private: + Server& server_; + void add_file_metadata(const std::string& file_path, nlohmann::json& metadata) const; +}; + +} // namespace simple_object_storage \ No newline at end of file diff --git a/src/server.cpp b/src/server.cpp index 57459c9..472ad57 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -13,30 +13,11 @@ #include "hash.hpp" #include "compress.hpp" #include "string_utils.hpp" // Include the new utility header - +#include "put_handler.hpp" +#include "utils.hpp" namespace simple_object_storage { -// Simple RAII helper for file deletion -class ScopeFileDeleter { -public: - ScopeFileDeleter(const std::filesystem::path& path) : path_(path), released_(false) {} - ~ScopeFileDeleter() { - if (!released_) { - try { - if (std::filesystem::exists(path_)) { - std::filesystem::remove(path_); - } - } catch (const std::filesystem::filesystem_error& e) { - std::cerr << "Error deleting temp file: " << path_ << " - " << e.what() << std::endl; - } - } - } - void release() { released_ = true; } -private: - std::filesystem::path path_; - bool released_; -}; bool Server::init_db() { try { @@ -108,6 +89,9 @@ Server::Server(const ServerConfig& config) // Error already printed in init_db // Consider throwing or setting an error state } + + // Initialize the put handler + put_handler_ = std::make_unique(*this); } Server::~Server() { @@ -171,7 +155,7 @@ void Server::setup_routes() { // Upload object with streaming support server_.Put("/upload", [this](const httplib::Request& req, httplib::Response& res) { - handle_put_object(req, res); + put_handler_->handle_put_object(req, res); }); // Handle PUT requests to other paths @@ -186,7 +170,6 @@ void Server::setup_routes() { handle_get_metadata(req, res); }); - // Delete an object (and all tags on that object) server_.Get("/deleteobject", [this](const httplib::Request& req, httplib::Response& res) { handle_delete_object(req, res); @@ -286,153 +269,6 @@ void Server::handle_get_directory(const httplib::Request& /*req*/, httplib::Resp res.set_content(response.dump(), "application/json"); } -void Server::handle_put_object(const httplib::Request& req, httplib::Response& res) { - // Check all request parameters first before processing any data - - std::map params; - if (!validate_write_request(req, res, {}, params)) { // No required params now since token is in header - return; - } - - // 1. Check we're in the /upload path - if (req.path != "/upload") { - res.status = 404; - nlohmann::json response = {{"result", "error"}, {"error", "Not found - put requests must be to /upload"}}; - res.set_content(response.dump(), "application/json"); - return; - } - - // Parse the multipart form data - if (!req.has_file("file")) { - res.status = 400; - nlohmann::json response = {{"result", "error"}, {"error", "No file provided in upload"}}; - res.set_content(response.dump(), "application/json"); - return; - } - - // Get the file data - const auto& file = req.get_file_value("file"); - - // Parse metadata if provided - nlohmann::json metadata; - if (req.has_file("metadata")) { - try { - const auto& metadata_file = req.get_file_value("metadata"); - metadata = nlohmann::json::parse(metadata_file.content); - } catch (const nlohmann::json::parse_error& e) { - res.status = 400; - nlohmann::json response = {{"result", "error"}, {"error", "Invalid JSON metadata: " + std::string(e.what())}}; - res.set_content(response.dump(), "application/json"); - return; - } - } - - // Validate required metadata fields - if (!metadata.contains("labeltag")) { - res.status = 400; - nlohmann::json response = {{"result", "error"}, {"error", "Missing required metadata field: labeltag"}}; - res.set_content(response.dump(), "application/json"); - return; - } - - // Extract labeltag and validate format - std::string labeltag = metadata["labeltag"]; - auto [label, tag] = parse_label_tag(labeltag); - if (label.empty() || tag.empty()) { - res.status = 400; - nlohmann::json response = {{"result", "error"}, {"error", "Invalid label:tag format"}}; - res.set_content(response.dump(), "application/json"); - return; - } - - // Add filename to metadata if not provided - if (!metadata.contains("filename")) { - metadata["filename"] = file.filename; - } - - // Now that all parameters are validated, process the upload - - // Generate a random number for the temporary filename - std::mt19937_64 rng(std::chrono::high_resolution_clock::now().time_since_epoch().count()); - std::uniform_int_distribution dist; - uint64_t random_num = dist(rng); - std::string temp_filename = "temp_" + std::to_string(random_num); - - // Create temporary file - std::filesystem::path temp_path = config_.object_store_path / temp_filename; - std::ofstream temp_file(temp_path, std::ios::binary); - if (!temp_file.is_open()) { - res.status = 500; - nlohmann::json response = {{"result", "error"}, {"error", "Failed to create temporary file"}}; - res.set_content(response.dump(), "application/json"); - return; - } - - // Write file content to temporary file - if (!temp_file.write(file.content.c_str(), file.content.size())) { - res.status = 500; - nlohmann::json response = {{"result", "error"}, {"error", "Failed to write to temporary file"}}; - res.set_content(response.dump(), "application/json"); - temp_file.close(); - std::filesystem::remove(temp_path); - return; - } - - temp_file.close(); - - // Ensure the temporary file is removed even if errors occur - ScopeFileDeleter temp_file_deleter(temp_path); - - // Calculate hash - uint64_t hash = hash_file(temp_path.string()); - if (hash == 0) { - res.status = 500; - nlohmann::json response = {{"result", "error"}, {"error", "Failed to calculate hash"}}; - res.set_content(response.dump(), "application/json"); - return; - } - - // Add file metadata - add_file_metadata(temp_path.string(), metadata); - - // Check if filename ends with ".tgz" using the utility function - if (utils::ends_with(metadata["filename"], ".tgz")) { - metadata["tgz_content_hash"] = get_hash_from_tgz(temp_path.string()); - } - - // Move file to final location - std::filesystem::path final_path = config_.object_store_path / std::to_string(hash); - if (!std::filesystem::exists(final_path)) { - try { - std::filesystem::rename(temp_path, final_path); - temp_file_deleter.release(); - } catch (const std::filesystem::filesystem_error& e) { - std::cerr << "Error renaming temp file: " << e.what() << std::endl; - res.status = 500; - nlohmann::json response = {{"result", "error"}, {"error", "Failed to store object file"}}; - res.set_content(response.dump(), "application/json"); - return; - } - } - - // Update database index - dbEntry entry; - entry.label_tag = labeltag; - entry.hash = std::to_string(hash); - entry.metadata = metadata; // Store the complete metadata - - if (!db_->update_or_insert(entry)) { - res.status = 500; - nlohmann::json response = {{"result", "error"}, {"error", "Failed to update database index"}}; - res.set_content(response.dump(), "application/json"); - // Attempt to clean up the moved file if index fails - try { if (std::filesystem::exists(final_path)) std::filesystem::remove(final_path); } catch(...) {}; - return; - } - - res.set_content(nlohmann::json({{"result", "success"}, {"hash", std::to_string(hash)}}).dump(), "application/json"); -} - void Server::handle_get_metadata(const httplib::Request& req, httplib::Response& res) { const auto& label_tag = req.matches[1].str(); @@ -463,20 +299,6 @@ std::pair Server::parse_label_tag(const std::string& l return {label_tag.substr(0, colon_pos), label_tag.substr(colon_pos + 1)}; } -void Server::add_file_metadata(const std::string &file_path, nlohmann::json &metadata) const -{ - // 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( - 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); -} - void Server::handle_delete_object(const httplib::Request& req, httplib::Response& res) { std::map params; if (!validate_write_request(req, res, {"hash"}, params)) { @@ -515,7 +337,6 @@ void Server::handle_delete_object(const httplib::Request& req, httplib::Response res.set_content(response.dump(), "application/json"); } - void Server::handle_exists(const httplib::Request& req, httplib::Response& res) { const auto& key = req.matches[1].str(); std::string hash_str = key; diff --git a/src/server.hpp b/src/server.hpp index 0620c26..1a5e49b 100644 --- a/src/server.hpp +++ b/src/server.hpp @@ -1,18 +1,17 @@ -#ifndef SERVER_HPP -#define SERVER_HPP +#pragma once -#include "config.hpp" -#include "httplib.hpp" -#include "database.hpp" #include +#include #include -#include -#include -#include -#include +#include "httplib.hpp" +#include "json.hpp" +#include "database.hpp" +#include "config.hpp" namespace simple_object_storage { +class PutHandler; // Forward declaration + class Server { public: Server(const ServerConfig& config); @@ -20,31 +19,27 @@ public: bool start(); void stop(); + bool validate_write_request(const httplib::Request& req, httplib::Response& res, const std::vector& required_params, std::map& params); + std::pair parse_label_tag(const std::string& label_tag) const; + + // Make these public so PutHandler can access them + ServerConfig config_; + std::unique_ptr db_; private: void setup_routes(); void handle_get_object(const httplib::Request& req, httplib::Response& res); void handle_get_hash(const httplib::Request& req, httplib::Response& res); void handle_get_directory(const httplib::Request& req, httplib::Response& res); - void handle_put_object(const httplib::Request& req, httplib::Response& res); void handle_get_metadata(const httplib::Request& req, httplib::Response& res); void handle_delete_object(const httplib::Request& req, httplib::Response& res); void handle_exists(const httplib::Request& req, httplib::Response& res); - std::pair parse_label_tag(const std::string& label_tag) const; - void add_file_metadata(const std::string &file_path, nlohmann::json &metadata) const; bool init_db(); - bool validate_write_request(const httplib::Request& req, httplib::Response& res, const std::vector& required_params, - std::map& params); - -private: - const ServerConfig& config_; httplib::Server server_; - std::unique_ptr db_; - std::atomic running_; + bool running_; + std::unique_ptr put_handler_; }; -} // namespace simple_object_storage - -#endif \ No newline at end of file +} // namespace simple_object_storage \ No newline at end of file diff --git a/src/utils.cpp b/src/utils.cpp new file mode 100644 index 0000000..353da82 --- /dev/null +++ b/src/utils.cpp @@ -0,0 +1,28 @@ +#include "utils.hpp" + +namespace simple_object_storage +{ + + ScopeFileDeleter::ScopeFileDeleter(const std::filesystem::path &path) : path_(path), released_(false) {} + + ScopeFileDeleter::~ScopeFileDeleter() + { + if (!released_) + { + try + { + if (std::filesystem::exists(path_)) + { + std::filesystem::remove(path_); + } + } + catch (const std::filesystem::filesystem_error &e) + { + std::cerr << "Error deleting temp file: " << path_ << " - " << e.what() << std::endl; + } + } + } + + void ScopeFileDeleter::release() { released_ = true; } + +} diff --git a/src/utils.hpp b/src/utils.hpp new file mode 100644 index 0000000..b9c0a1a --- /dev/null +++ b/src/utils.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +namespace simple_object_storage { + +// Simple RAII helper for file deletion +class ScopeFileDeleter { +public: + ScopeFileDeleter(const std::filesystem::path& path); + ~ScopeFileDeleter(); + void release(); +private: + std::filesystem::path path_; + bool released_; +}; + +} \ No newline at end of file