|
|
@@ -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
|