Prechádzať zdrojové kódy

feat: add file operation methods to database client (upload, download, info, delete, list)

Fszontagh 4 mesiacov pred
rodič
commit
ef4738a30a

+ 48 - 0
client/include/smartbotic/database/client.hpp

@@ -3,6 +3,7 @@
 #include <nlohmann/json.hpp>
 
 #include <functional>
+#include <map>
 #include <memory>
 #include <optional>
 #include <string>
@@ -316,6 +317,53 @@ public:
     [[nodiscard]] bool healthCheck();
     [[nodiscard]] std::optional<HealthInfo> getHealthInfo();
 
+    // ===== File Operations =====
+
+    struct FileUploadMeta {
+        std::string name;
+        std::string mime_type;
+        std::string file_type;      // "plugin", "document", "generated"
+        std::string related_id;     // plugin_id, conversation_id, etc.
+        bool is_public = false;
+        std::map<std::string, std::string> metadata;
+    };
+
+    struct FileUploadResult {
+        std::string id;             // New UUID (file record)
+        uint64_t size = 0;
+        std::string checksum;
+        bool deduplicated = false;
+    };
+
+    struct FileRecord {
+        std::string id;
+        std::string name;
+        std::string mime_type;
+        uint64_t size = 0;
+        std::string file_type;
+        std::string related_id;
+        std::string checksum;
+        bool is_public = false;
+        uint32_t ref_count = 0;
+        uint64_t created_at = 0;
+        std::map<std::string, std::string> metadata;
+    };
+
+    struct FileListResult {
+        std::vector<FileRecord> files;
+        uint64_t total_count = 0;
+        bool has_more = false;
+    };
+
+    FileUploadResult uploadFile(const std::vector<uint8_t>& data, const FileUploadMeta& meta);
+    [[nodiscard]] std::vector<uint8_t> downloadFile(const std::string& id);
+    [[nodiscard]] std::optional<FileRecord> getFileInfo(const std::string& id);
+    bool deleteFile(const std::string& id);
+    [[nodiscard]] FileListResult listFiles(const std::string& file_type = "",
+                                            const std::string& related_id = "",
+                                            uint32_t limit = 100, uint32_t offset = 0,
+                                            const std::string& checksum = "");
+
 private:
     class Impl;
     std::unique_ptr<Impl> impl_;

+ 184 - 0
client/src/client.cpp

@@ -886,6 +886,167 @@ public:
         return info;
     }
 
+    // ===== File Operations =====
+
+    Client::FileUploadResult uploadFile(const std::vector<uint8_t>& data, const Client::FileUploadMeta& meta) {
+        grpc::ClientContext context;
+        auto timeout_ms = config_.timeoutMs + (data.size() / (1024 * 1024)) * 1000;
+        context.set_deadline(
+            std::chrono::system_clock::now() + std::chrono::milliseconds(timeout_ms));
+
+        smartbotic::databasepb::UploadFileResponse response;
+        auto writer = stub_->UploadFile(&context, &response);
+
+        // First chunk: metadata
+        smartbotic::databasepb::FileChunk metaChunk;
+        auto* m = metaChunk.mutable_metadata();
+        m->set_name(meta.name);
+        m->set_mime_type(meta.mime_type);
+        m->set_file_type(meta.file_type);
+        m->set_related_id(meta.related_id);
+        m->set_is_public(meta.is_public);
+        for (const auto& [key, value] : meta.metadata) {
+            (*m->mutable_metadata())[key] = value;
+        }
+        writer->Write(metaChunk);
+
+        // Data chunks (64KB each)
+        constexpr size_t CHUNK_SIZE = 64 * 1024;
+        for (size_t offset = 0; offset < data.size(); offset += CHUNK_SIZE) {
+            smartbotic::databasepb::FileChunk dataChunk;
+            size_t chunkSize = std::min(CHUNK_SIZE, data.size() - offset);
+            dataChunk.set_data(data.data() + offset, chunkSize);
+            if (!writer->Write(dataChunk)) break;
+        }
+
+        writer->WritesDone();
+        auto status = writer->Finish();
+        if (!status.ok()) {
+            spdlog::error("Client::uploadFile failed: {}", status.error_message());
+            throw std::runtime_error(status.error_message());
+        }
+
+        return {response.id(), response.size(), response.checksum(), response.deduplicated()};
+    }
+
+    std::vector<uint8_t> downloadFile(const std::string& id) {
+        smartbotic::databasepb::DownloadFileRequest request;
+        request.set_id(id);
+
+        grpc::ClientContext context;
+        context.set_deadline(
+            std::chrono::system_clock::now() + std::chrono::milliseconds(config_.timeoutMs * 10));
+
+        auto reader = stub_->DownloadFile(&context, request);
+
+        std::vector<uint8_t> fileData;
+        smartbotic::databasepb::FileChunk chunk;
+        while (reader->Read(&chunk)) {
+            if (chunk.has_data()) {
+                const auto& d = chunk.data();
+                fileData.insert(fileData.end(), d.begin(), d.end());
+            }
+        }
+
+        auto status = reader->Finish();
+        if (!status.ok()) {
+            spdlog::error("Client::downloadFile failed: {}", status.error_message());
+            throw std::runtime_error(status.error_message());
+        }
+        return fileData;
+    }
+
+    std::optional<Client::FileRecord> getFileInfo(const std::string& id) {
+        smartbotic::databasepb::GetFileInfoRequest request;
+        request.set_id(id);
+
+        smartbotic::databasepb::FileInfo response;
+        grpc::ClientContext context;
+        setDeadline(context);
+
+        auto status = stub_->GetFileInfo(&context, request, &response);
+        if (!status.ok()) {
+            if (status.error_code() == grpc::StatusCode::NOT_FOUND) return std::nullopt;
+            spdlog::error("Client::getFileInfo failed: {}", status.error_message());
+            throw std::runtime_error(status.error_message());
+        }
+
+        Client::FileRecord record;
+        record.id = response.id();
+        record.name = response.name();
+        record.mime_type = response.mime_type();
+        record.size = response.size();
+        record.file_type = response.file_type();
+        record.related_id = response.related_id();
+        record.checksum = response.checksum();
+        record.is_public = response.is_public();
+        record.ref_count = response.ref_count();
+        record.created_at = response.created_at();
+        for (const auto& [key, value] : response.metadata()) {
+            record.metadata[key] = value;
+        }
+        return record;
+    }
+
+    bool deleteFile(const std::string& id) {
+        smartbotic::databasepb::DeleteFileRequest request;
+        request.set_id(id);
+
+        smartbotic::databasepb::DeleteFileResponse response;
+        grpc::ClientContext context;
+        setDeadline(context);
+
+        auto status = stub_->DeleteFile(&context, request, &response);
+        if (!status.ok()) {
+            spdlog::error("Client::deleteFile failed: {}", status.error_message());
+            return false;
+        }
+        return response.deleted();
+    }
+
+    Client::FileListResult listFiles(const std::string& file_type,
+                                      const std::string& related_id,
+                                      uint32_t limit, uint32_t offset,
+                                      const std::string& checksum) {
+        smartbotic::databasepb::ListFilesRequest request;
+        if (!file_type.empty()) request.set_file_type(file_type);
+        if (!related_id.empty()) request.set_related_id(related_id);
+        request.set_limit(limit);
+        request.set_offset(offset);
+        if (!checksum.empty()) request.set_checksum(checksum);
+
+        smartbotic::databasepb::ListFilesResponse response;
+        grpc::ClientContext context;
+        setDeadline(context);
+
+        auto status = stub_->ListFiles(&context, request, &response);
+        if (!status.ok()) {
+            spdlog::error("Client::listFiles failed: {}", status.error_message());
+            throw std::runtime_error(status.error_message());
+        }
+
+        Client::FileListResult result;
+        result.total_count = response.total_count();
+        result.has_more = response.has_more();
+        for (const auto& f : response.files()) {
+            Client::FileRecord record;
+            record.id = f.id();
+            record.name = f.name();
+            record.mime_type = f.mime_type();
+            record.size = f.size();
+            record.file_type = f.file_type();
+            record.related_id = f.related_id();
+            record.checksum = f.checksum();
+            record.is_public = f.is_public();
+            record.created_at = f.created_at();
+            for (const auto& [key, value] : f.metadata()) {
+                record.metadata[key] = value;
+            }
+            result.files.push_back(std::move(record));
+        }
+        return result;
+    }
+
 private:
     void setDeadline(grpc::ClientContext& context) {
         context.set_deadline(
@@ -1050,4 +1211,27 @@ std::optional<Client::StatsInfo> Client::getStats() {
     return impl_->getStats();
 }
 
+Client::FileUploadResult Client::uploadFile(const std::vector<uint8_t>& data, const FileUploadMeta& meta) {
+    return impl_->uploadFile(data, meta);
+}
+
+std::vector<uint8_t> Client::downloadFile(const std::string& id) {
+    return impl_->downloadFile(id);
+}
+
+std::optional<Client::FileRecord> Client::getFileInfo(const std::string& id) {
+    return impl_->getFileInfo(id);
+}
+
+bool Client::deleteFile(const std::string& id) {
+    return impl_->deleteFile(id);
+}
+
+Client::FileListResult Client::listFiles(const std::string& file_type,
+                                          const std::string& related_id,
+                                          uint32_t limit, uint32_t offset,
+                                          const std::string& checksum) {
+    return impl_->listFiles(file_type, related_id, limit, offset, checksum);
+}
+
 } // namespace smartbotic::database