filename
This commit is contained in:
@@ -21,7 +21,7 @@ Write access is controlled by tokens.
|
|||||||
- `curl http://localhost:8123/meta/squashkiwi:latest`
|
- `curl http://localhost:8123/meta/squashkiwi:latest`
|
||||||
- a simple welcome page is served at `/index.html` for those browsing to the site.
|
- a simple welcome page is served at `/index.html` for those browsing to the site.
|
||||||
- to upload a file (via http put)
|
- to upload a file (via http put)
|
||||||
- `curl -T object_file http://localhost:8123/WRITE_TOKEN/LABEL:TAG`
|
- `curl -T object_file http://localhost:8123/WRITE_TOKEN/LABEL:TAG?filename="FILENAME"`
|
||||||
- the object_file is uploaded, hashed, added to the registry (if that hash doesn't already exist), and {label:tag,hash} is added to the directory index.
|
- the object_file is uploaded, hashed, added to the registry (if that hash doesn't already exist), and {label:tag,hash} is added to the directory index.
|
||||||
- the server is configured via a configuration file which allows setting:
|
- the server is configured via a configuration file which allows setting:
|
||||||
- the list of write access tokens
|
- the list of write access tokens
|
||||||
|
@@ -22,9 +22,10 @@ uint64_t get_hash_from_tgz(const std::string &file_path)
|
|||||||
std::ifstream file(file_path, std::ios::binary);
|
std::ifstream file(file_path, std::ios::binary);
|
||||||
if (!file) return 0;
|
if (!file) return 0;
|
||||||
|
|
||||||
char buffer[2];
|
int result = system("gunzip -t file.gz");
|
||||||
file.read(buffer, 2);
|
if (result != 0) { // not a gzip file.
|
||||||
if (buffer[0] != 0x1F || buffer[1] != 0x8B) return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// gunzip the file to a new temporary directory
|
// gunzip the file to a new temporary directory
|
||||||
TempDirectory temp_dir_manager("tgz_unpack_"); // Creates dir and schedules cleanup
|
TempDirectory temp_dir_manager("tgz_unpack_"); // Creates dir and schedules cleanup
|
||||||
@@ -32,9 +33,8 @@ uint64_t get_hash_from_tgz(const std::string &file_path)
|
|||||||
|
|
||||||
// unpack the file on disk
|
// unpack the file on disk
|
||||||
std::string command = "tar -zxzf " + file_path + " -C " + temp_dir;
|
std::string command = "tar -zxzf " + file_path + " -C " + temp_dir;
|
||||||
int result = system(command.c_str()); // Basic tar extraction - requires 'tar' command
|
result = system(command.c_str()); // Basic tar extraction - requires 'tar' command
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
std::cerr << "Error unpacking tgz file: " << file_path << std::endl;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -228,4 +228,22 @@ bool Database::list(std::vector<dbEntry>& entries) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Database::update_or_insert(const dbEntry& entry) {
|
||||||
|
std::string sql = "INSERT OR REPLACE INTO objects (label_tag, hash, metadata) VALUES (?, ?, ?);";
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
|
||||||
|
if (sqlite3_prepare_v2(db_, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_bind_text(stmt, 1, entry.label_tag.c_str(), -1, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_text(stmt, 2, entry.hash.c_str(), -1, SQLITE_STATIC);
|
||||||
|
std::string metadata_str = entry.metadata.dump();
|
||||||
|
sqlite3_bind_text(stmt, 3, metadata_str.c_str(), -1, SQLITE_STATIC);
|
||||||
|
|
||||||
|
bool success = sqlite3_step(stmt) == SQLITE_DONE;
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace simple_object_storage
|
} // namespace simple_object_storage
|
||||||
|
@@ -25,7 +25,8 @@ class Database {
|
|||||||
bool remove(const std::string& label_tag);
|
bool remove(const std::string& label_tag);
|
||||||
bool get(const std::string& label_tag, dbEntry& entry);
|
bool get(const std::string& label_tag, dbEntry& entry);
|
||||||
bool update(const std::string& label_tag, const dbEntry& entry);
|
bool update(const std::string& label_tag, const dbEntry& entry);
|
||||||
bool list(std::vector<dbEntry>& entries);
|
bool list(std::vector<dbEntry>& entries);
|
||||||
|
bool update_or_insert(const dbEntry& entry);
|
||||||
private:
|
private:
|
||||||
std::filesystem::path path_;
|
std::filesystem::path path_;
|
||||||
sqlite3* db_;
|
sqlite3* db_;
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
#include "server.hpp"
|
#include "server.hpp"
|
||||||
#include "hash.hpp"
|
#include "hash.hpp"
|
||||||
#include "compress.hpp"
|
#include "compress.hpp"
|
||||||
|
#include "string_utils.hpp" // Include the new utility header
|
||||||
|
|
||||||
|
|
||||||
namespace simple_object_storage {
|
namespace simple_object_storage {
|
||||||
@@ -265,7 +266,21 @@ void Server::handle_put_object(const httplib::Request& req, httplib::Response& r
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json metadata = get_metadata(temp_path.string());
|
nlohmann::json metadata;
|
||||||
|
|
||||||
|
// Check for filename query parameter
|
||||||
|
std::string filename = "";
|
||||||
|
if (req.has_param("filename")) {
|
||||||
|
filename = req.get_param_value("filename");
|
||||||
|
metadata["original_filename"] = filename;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if filename ends with ".tgz" using the utility function
|
||||||
|
if (utils::ends_with(filename, ".tgz")) {
|
||||||
|
metadata["tgz_content_hash"] = get_hash_from_tgz(temp_path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
add_file_metadata(temp_path.string(), metadata);
|
||||||
|
|
||||||
// Move file to final location
|
// Move file to final location
|
||||||
std::string hash_str = std::to_string(hash);
|
std::string hash_str = std::to_string(hash);
|
||||||
@@ -286,9 +301,9 @@ void Server::handle_put_object(const httplib::Request& req, httplib::Response& r
|
|||||||
dbEntry entry;
|
dbEntry entry;
|
||||||
entry.label_tag = label_tag;
|
entry.label_tag = label_tag;
|
||||||
entry.hash = hash_str;
|
entry.hash = hash_str;
|
||||||
entry.metadata = metadata; // Empty metadata for now
|
entry.metadata = metadata; // Store the potentially updated metadata
|
||||||
|
|
||||||
if (!db_->insert(entry)) {
|
if (!db_->update_or_insert(entry)) {
|
||||||
res.status = 500;
|
res.status = 500;
|
||||||
res.set_content("Failed to update database index", "text/plain");
|
res.set_content("Failed to update database index", "text/plain");
|
||||||
// Attempt to clean up the moved file if index fails
|
// Attempt to clean up the moved file if index fails
|
||||||
@@ -330,10 +345,8 @@ 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)};
|
return {label_tag.substr(0, colon_pos), label_tag.substr(colon_pos + 1)};
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json Server::get_metadata(const std::string &file_path) const
|
void Server::add_file_metadata(const std::string &file_path, nlohmann::json &metadata) const
|
||||||
{
|
{
|
||||||
nlohmann::json metadata;
|
|
||||||
|
|
||||||
// get the file size
|
// get the file size
|
||||||
metadata["file_size"] = std::filesystem::file_size(file_path);
|
metadata["file_size"] = std::filesystem::file_size(file_path);
|
||||||
|
|
||||||
@@ -344,10 +357,6 @@ nlohmann::json Server::get_metadata(const std::string &file_path) const
|
|||||||
+ std::chrono::system_clock::now()
|
+ std::chrono::system_clock::now()
|
||||||
);
|
);
|
||||||
metadata["file_modification_time"] = std::chrono::system_clock::to_time_t(sctp);
|
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
|
} // namespace simple_object_storage
|
@@ -30,7 +30,7 @@ private:
|
|||||||
void handle_get_metadata(const httplib::Request& req, httplib::Response& res);
|
void handle_get_metadata(const httplib::Request& req, httplib::Response& res);
|
||||||
bool validate_write_token(const std::string& token) const;
|
bool validate_write_token(const std::string& token) const;
|
||||||
std::pair<std::string, std::string> parse_label_tag(const std::string& label_tag) const;
|
std::pair<std::string, std::string> parse_label_tag(const std::string& label_tag) const;
|
||||||
nlohmann::json get_metadata(const std::string& file_path) const;
|
void add_file_metadata(const std::string &file_path, nlohmann::json &metadata) const;
|
||||||
|
|
||||||
bool init_db();
|
bool init_db();
|
||||||
|
|
||||||
|
15
src/string_utils.cpp
Normal file
15
src/string_utils.cpp
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
#include "string_utils.hpp"
|
||||||
|
|
||||||
|
namespace simple_object_storage {
|
||||||
|
namespace utils {
|
||||||
|
|
||||||
|
bool ends_with(const std::string& value, const std::string& suffix) {
|
||||||
|
if (suffix.length() > value.length()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Use rfind to check if the suffix matches the end of the value
|
||||||
|
return value.rfind(suffix) == value.length() - suffix.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace utils
|
||||||
|
} // namespace simple_object_storage
|
21
src/string_utils.hpp
Normal file
21
src/string_utils.hpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef STRING_UTILS_HPP
|
||||||
|
#define STRING_UTILS_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace simple_object_storage {
|
||||||
|
namespace utils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Checks if a string ends with a specified suffix.
|
||||||
|
*
|
||||||
|
* @param value The string to check.
|
||||||
|
* @param suffix The suffix to look for.
|
||||||
|
* @return true if the string ends with the suffix, false otherwise.
|
||||||
|
*/
|
||||||
|
bool ends_with(const std::string& value, const std::string& suffix);
|
||||||
|
|
||||||
|
} // namespace utils
|
||||||
|
} // namespace simple_object_storage
|
||||||
|
|
||||||
|
#endif // STRING_UTILS_HPP
|
Reference in New Issue
Block a user