diff --git a/README.md b/README.md index d55e9bb..d5bd35f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Write access is controlled by tokens. - `curl http://localhost:8123/meta/squashkiwi:latest` - a simple welcome page is served at `/index.html` for those browsing to the site. - 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 server is configured via a configuration file which allows setting: - the list of write access tokens diff --git a/src/compress.cpp b/src/compress.cpp index e4800a6..1647199 100644 --- a/src/compress.cpp +++ b/src/compress.cpp @@ -22,9 +22,10 @@ uint64_t get_hash_from_tgz(const std::string &file_path) std::ifstream file(file_path, std::ios::binary); if (!file) return 0; - char buffer[2]; - file.read(buffer, 2); - if (buffer[0] != 0x1F || buffer[1] != 0x8B) return 0; + int result = system("gunzip -t file.gz"); + if (result != 0) { // not a gzip file. + return 0; + } // gunzip the file to a new temporary directory 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 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) { - std::cerr << "Error unpacking tgz file: " << file_path << std::endl; return 0; } diff --git a/src/database.cpp b/src/database.cpp index 647a22d..bae4292 100644 --- a/src/database.cpp +++ b/src/database.cpp @@ -228,4 +228,22 @@ bool Database::list(std::vector& entries) { 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 diff --git a/src/database.hpp b/src/database.hpp index ae2f000..a3f927c 100644 --- a/src/database.hpp +++ b/src/database.hpp @@ -25,7 +25,8 @@ class Database { bool remove(const std::string& label_tag); bool get(const std::string& label_tag, dbEntry& entry); bool update(const std::string& label_tag, const dbEntry& entry); - bool list(std::vector& entries); + bool list(std::vector& entries); + bool update_or_insert(const dbEntry& entry); private: std::filesystem::path path_; sqlite3* db_; diff --git a/src/server.cpp b/src/server.cpp index 031ec39..3b962a3 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -13,6 +13,7 @@ #include "server.hpp" #include "hash.hpp" #include "compress.hpp" +#include "string_utils.hpp" // Include the new utility header namespace simple_object_storage { @@ -265,7 +266,21 @@ void Server::handle_put_object(const httplib::Request& req, httplib::Response& r 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 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; entry.label_tag = label_tag; 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.set_content("Failed to update database index", "text/plain"); // Attempt to clean up the moved file if index fails @@ -330,10 +345,8 @@ std::pair 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 +void Server::add_file_metadata(const std::string &file_path, nlohmann::json &metadata) const { - nlohmann::json metadata; - // get the file size 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() ); 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 \ No newline at end of file diff --git a/src/server.hpp b/src/server.hpp index e68b0fe..a3c2c85 100644 --- a/src/server.hpp +++ b/src/server.hpp @@ -30,7 +30,7 @@ private: void handle_get_metadata(const httplib::Request& req, httplib::Response& res); bool validate_write_token(const std::string& token) const; std::pair 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(); diff --git a/src/string_utils.cpp b/src/string_utils.cpp new file mode 100644 index 0000000..ae0e13e --- /dev/null +++ b/src/string_utils.cpp @@ -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 \ No newline at end of file diff --git a/src/string_utils.hpp b/src/string_utils.hpp new file mode 100644 index 0000000..517e0f5 --- /dev/null +++ b/src/string_utils.hpp @@ -0,0 +1,21 @@ +#ifndef STRING_UTILS_HPP +#define STRING_UTILS_HPP + +#include + +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 \ No newline at end of file