This commit is contained in:
Your Name
2025-04-30 22:11:22 +12:00
parent 9c0ade961f
commit ed5a9d99a3
3 changed files with 5070 additions and 45 deletions

4928
src/litecask.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -6,6 +6,8 @@
#include <fstream>
#include <random> // For random number generation
#include <chrono> // For seeding random number generator
#include <vector> // For getAllKeys
#include <string_view> // For litecask values
namespace dropshell {
@@ -30,26 +32,61 @@ private:
Server::Server(const ServerConfig& config)
: config_(config), running_(false) {
// Create object store directory if it doesn't exist
std::filesystem::create_directories(config_.object_store_path);
// Ensure object store directory exists
try {
std::filesystem::create_directories(config_.object_store_path);
} catch (const std::filesystem::filesystem_error& e) {
std::cerr << "Failed to create object store directory: " << config_.object_store_path << " - " << e.what() << std::endl;
// Consider throwing an exception or exiting
return;
}
// Set up and open the litecask datastore
datastore_path_ = config_.object_store_path / "index";
try {
std::filesystem::create_directories(datastore_path_);
} catch (const std::filesystem::filesystem_error& e) {
std::cerr << "Failed to create datastore directory: " << datastore_path_ << " - " << e.what() << std::endl;
// Consider throwing an exception or exiting
return;
}
if (datastore_.open(datastore_path_.string()) != litecask::Status::Ok) {
std::cerr << "Failed to open litecask datastore at " << datastore_path_ << std::endl;
// Consider throwing an exception or exiting
}
}
Server::~Server() {
stop();
datastore_.close(); // Close the datastore
}
bool Server::start() {
if (!datastore_.isOpen()) {
std::cerr << "Datastore is not open. Cannot start server." << std::endl;
return false;
}
setup_routes();
std::cout << "Server starting on " << config_.host << ":" << config_.port << std::endl;
running_ = true;
return server_.listen(config_.host.c_str(), config_.port);
// Use server_.listen in a separate thread or make it non-blocking if needed
// For simplicity, keeping it blocking for now.
if (!server_.listen(config_.host.c_str(), config_.port)) {
running_ = false;
std::cerr << "Failed to listen on " << config_.host << ":" << config_.port << std::endl;
return false;
}
return true; // Should not be reached if listen is blocking and successful
}
void Server::stop() {
if (running_) {
server_.stop();
server_.stop(); // httplib stop is non-blocking
running_ = false;
std::cout << "Server stopped." << std::endl;
}
}
@@ -75,62 +112,108 @@ void Server::setup_routes() {
});
// Upload object
server_.Put("/([^/]+)/([^/]+)", [this](const httplib::Request& req, httplib::Response& res) {
server_.Put("/([^/]+)/(.*)", [this](const httplib::Request& req, httplib::Response& res) { // Adjusted regex slightly for label:tag
handle_put_object(req, res);
});
}
void Server::handle_get_object(const httplib::Request& req, httplib::Response& res) {
const auto& path = req.matches[1];
uint64_t hash = 0;
const auto& key = req.matches[1].str(); // Use .str() to get std::string
std::string hash_str;
try {
hash = std::stoull(path);
} catch (...) {
// Try to find hash by label:tag
auto it = label_tag_index_.find(path);
if (it == label_tag_index_.end()) {
res.status = 404;
res.set_content("Object not found", "text/plain");
return;
// Check if the key looks like a hash (numeric)
bool is_hash_lookup = true;
for (char c : key) {
if (!std::isdigit(c)) {
is_hash_lookup = false;
break;
}
hash = it->second;
}
std::filesystem::path file_path = config_.object_store_path / std::to_string(hash);
if (!std::filesystem::exists(file_path)) {
if (!is_hash_lookup) {
// Lookup by label:tag in the datastore
std::string_view value_sv;
auto rc = datastore_.get(key, value_sv);
if (rc == litecask::RESULT_CODE::LC_OK) {
hash_str = std::string(value_sv);
} else if (rc == litecask::RESULT_CODE::LC_ERR_NOT_FOUND) {
res.status = 404;
res.set_content("Object not found (label:tag)", "text/plain");
return;
} else {
std::cerr << "Datastore get error: " << static_cast<int>(rc) << std::endl;
res.status = 500;
res.set_content("Datastore error on get", "text/plain");
return;
}
} else {
// Lookup directly by hash
hash_str = key;
}
// Construct the file path using the hash string
std::filesystem::path file_path = config_.object_store_path / hash_str;
if (!std::filesystem::exists(file_path) || !std::filesystem::is_regular_file(file_path)) {
res.status = 404;
res.set_content("Object file not found", "text/plain");
return;
}
// Send file using Response::set_file_content
res.set_file_content(file_path.string());
// Need to determine content type, default to octet-stream
std::string content_type = "application/octet-stream"; // Basic default
res.set_file_content(file_path.string(), content_type);
// httplib should set status to 200 automatically for set_file_content if successful
}
void Server::handle_get_hash(const httplib::Request& req, httplib::Response& res) {
const auto& path = req.matches[1];
auto it = label_tag_index_.find(path);
if (it == label_tag_index_.end()) {
const auto& label_tag = req.matches[1].str();
std::string_view value_sv;
auto rc = datastore_.get(label_tag, value_sv);
if (rc == litecask::RESULT_CODE::LC_OK) {
res.set_content(std::string(value_sv), "text/plain");
} else if (rc == litecask::RESULT_CODE::LC_ERR_NOT_FOUND) {
res.status = 404;
res.set_content("Label:tag not found", "text/plain");
return;
} else {
std::cerr << "Datastore get error: " << static_cast<int>(rc) << std::endl;
res.status = 500;
res.set_content("Datastore error on get", "text/plain");
}
res.set_content(std::to_string(it->second), "text/plain");
}
void Server::handle_get_directory(const httplib::Request& req, httplib::Response& res) {
void Server::handle_get_directory(const httplib::Request& /*req*/, httplib::Response& res) {
std::vector<std::string> keys;
// Assuming getAllKeys exists and is efficient enough. Check litecask docs.
// This might be slow for very large datastores.
if (datastore_.getAllKeys(keys) != litecask::RESULT_CODE::LC_OK) {
std::cerr << "Failed to get all keys from datastore." << std::endl;
res.status = 500;
res.set_content("Failed to retrieve directory listing", "text/plain");
return;
}
std::stringstream ss;
for (const auto& [labeltag, hash] : label_tag_index_) {
ss << labeltag << "," << hash << "\n";
std::string_view value_sv;
for (const auto& key : keys) {
auto rc = datastore_.get(key, value_sv);
if (rc == litecask::RESULT_CODE::LC_OK) {
ss << key << "," << std::string(value_sv) << "\n";
} else if (rc == litecask::RESULT_CODE::LC_ERR_NOT_FOUND) {
std::cerr << "Key found by getAllKeys but not found by get: " << key << std::endl;
// Skip this key or handle error
} else {
std::cerr << "Datastore get error for key " << key << ": " << static_cast<int>(rc) << std::endl;
// Skip this key or handle error
}
}
res.set_content(ss.str(), "text/plain");
}
void Server::handle_put_object(const httplib::Request& req, httplib::Response& res) {
const auto& token = req.matches[1];
const auto& label_tag = req.matches[2];
const auto& token = req.matches[1].str();
const auto& label_tag = req.matches[2].str();
if (!validate_write_token(token)) {
res.status = 403;
@@ -176,7 +259,8 @@ void Server::handle_put_object(const httplib::Request& req, httplib::Response& r
}
// Move file to final location
std::filesystem::path final_path = config_.object_store_path / std::to_string(hash);
std::string hash_str = std::to_string(hash);
std::filesystem::path final_path = config_.object_store_path / hash_str;
if (!std::filesystem::exists(final_path)) {
try {
std::filesystem::rename(temp_path, final_path);
@@ -192,20 +276,37 @@ void Server::handle_put_object(const httplib::Request& req, httplib::Response& r
// temp_file_deleter will automatically remove the temp file
}
// Update indices
label_tag_index_[label + ":" + tag] = hash;
// Update datastore index
auto rc = datastore_.put(label_tag, hash_str);
if (rc != litecask::RESULT_CODE::LC_OK) {
std::cerr << "Datastore put error: " << static_cast<int>(rc) << std::endl;
// Decide how to handle this - the object is stored, but index failed.
// Maybe attempt removal of the object file? Or just log error.
res.status = 500;
res.set_content("Failed to update datastore index", "text/plain");
// Consider removing the successfully stored object file if index fails
try {
if (!std::filesystem::remove(final_path)) {
std::cerr << "Failed to remove object file after index failure: " << final_path << std::endl;
}
} catch (const std::filesystem::filesystem_error& e) {
std::cerr << "Error removing object file after index failure: " << e.what() << std::endl;
}
return;
}
res.set_content(std::to_string(hash), "text/plain");
res.set_content(hash_str, "text/plain");
}
bool Server::validate_write_token(const std::string& token) const {
// Ensure config_.write_tokens is accessible and valid
return std::find(config_.write_tokens.begin(), config_.write_tokens.end(), token) != config_.write_tokens.end();
}
std::pair<std::string, std::string> Server::parse_label_tag(const std::string& label_tag) const {
size_t colon_pos = label_tag.find(':');
if (colon_pos == std::string::npos) {
return {"", ""};
if (colon_pos == std::string::npos || colon_pos == 0 || colon_pos == label_tag.length() - 1) {
return {"", ""}; // Ensure label and tag are not empty
}
return {label_tag.substr(0, colon_pos), label_tag.substr(colon_pos + 1)};
}

View File

@@ -3,11 +3,12 @@
#include "config.hpp"
#include "httplib.hpp"
#include "litecask.hpp"
#include <string>
#include <unordered_map>
#include <memory>
#include <thread>
#include <atomic>
#include <filesystem>
namespace dropshell {
@@ -20,12 +21,6 @@ public:
void stop();
private:
struct ObjectInfo {
std::string label;
std::string tag;
uint64_t hash;
};
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);
@@ -36,8 +31,9 @@ private:
const ServerConfig& config_;
httplib::Server server_;
litecask::Datastore datastore_;
std::filesystem::path datastore_path_;
std::atomic<bool> running_;
std::unordered_map<std::string, uint64_t> label_tag_index_;
};
} // namespace dropshell