import Foundation public class ObjectStorageClient { // Object Storage HTTP API Client // Supports S3, MinIO, Ceph RADOS Gateway // No DriverKit Entitlement required private let endpoint: String private let accessKey: String private let secretKey: String private let session: URLSession public init(endpoint: String, accessKey: String, secretKey: String) { self.endpoint = endpoint self.accessKey = accessKey self.secretKey = secretKey self.session = URLSession.shared print("ObjectStorageClient initializing...") print(" - Endpoint: \(endpoint)") print(" - Access Key: \(accessKey)") } // MARK: - Bucket Operations public func createBucket(bucketName: String) -> Bool { print("Creating bucket: \(bucketName)") let url = URL(string: "\(endpoint)/\(bucketName)")! var request = URLRequest(url: url) request.httpMethod = "PUT" // Add authentication headers addAuthHeaders(request: &request, method: "PUT", path: "/\(bucketName)") let task = session.dataTask(with: request) { data, response, error in if let error = error { print("Error creating bucket: \(error)") return } if let httpResponse = response as? HTTPURLResponse { if httpResponse.statusCode == 200 || httpResponse.statusCode == 204 { print("Bucket created successfully: \(bucketName)") } else { print("Failed to create bucket: HTTP \(httpResponse.statusCode)") } } } task.resume() return true } public func listBuckets() -> [String] { print("Listing buckets...") var buckets: [String] = [] let url = URL(string: "\(endpoint)")! var request = URLRequest(url: url) request.httpMethod = "GET" addAuthHeaders(request: &request, method: "GET", path: "/") let semaphore = DispatchSemaphore(value: 0) let task = session.dataTask(with: request) { data, response, error in if let error = error { print("Error listing buckets: \(error)") semaphore.signal() return } if let httpResponse = response as? HTTPURLResponse { if httpResponse.statusCode == 200 { print("Buckets listed successfully") // Parse XML response (simplified) if let data = data { let xmlString = String(data: data, encoding: .utf8) print("Response: \(xmlString ?? "")") } } else { print("Failed to list buckets: HTTP \(httpResponse.statusCode)") } } semaphore.signal() } task.resume() semaphore.wait() return buckets } // MARK: - Object Operations public func uploadObject(bucket: String, key: String, data: Data) -> Bool { print("Uploading object: \(key) to bucket: \(bucket)") let url = URL(string: "\(endpoint)/\(bucket)/\(key)")! var request = URLRequest(url: url) request.httpMethod = "PUT" request.httpBody = data addAuthHeaders(request: &request, method: "PUT", path: "/\(bucket)/\(key)") let semaphore = DispatchSemaphore(value: 0) var success = false let task = session.dataTask(with: request) { data, response, error in if let error = error { print("Error uploading object: \(error)") semaphore.signal() return } if let httpResponse = response as? HTTPURLResponse { if httpResponse.statusCode == 200 { print("Object uploaded successfully: \(key)") success = true } else { print("Failed to upload object: HTTP \(httpResponse.statusCode)") } } semaphore.signal() } task.resume() semaphore.wait() return success } public func downloadObject(bucket: String, key: String) -> Data? { print("Downloading object: \(key) from bucket: \(bucket)") let url = URL(string: "\(endpoint)/\(bucket)/\(key)")! var request = URLRequest(url: url) request.httpMethod = "GET" addAuthHeaders(request: &request, method: "GET", path: "/\(bucket)/\(key)") let semaphore = DispatchSemaphore(value: 0) var downloadedData: Data? = nil let task = session.dataTask(with: request) { data, response, error in if let error = error { print("Error downloading object: \(error)") semaphore.signal() return } if let httpResponse = response as? HTTPURLResponse { if httpResponse.statusCode == 200 { print("Object downloaded successfully: \(key)") downloadedData = data } else { print("Failed to download object: HTTP \(httpResponse.statusCode)") } } semaphore.signal() } task.resume() semaphore.wait() return downloadedData } public func deleteObject(bucket: String, key: String) -> Bool { print("Deleting object: \(key) from bucket: \(bucket)") let url = URL(string: "\(endpoint)/\(bucket)/\(key)")! var request = URLRequest(url: url) request.httpMethod = "DELETE" addAuthHeaders(request: &request, method: "DELETE", path: "/\(bucket)/\(key)") let semaphore = DispatchSemaphore(value: 0) var success = false let task = session.dataTask(with: request) { data, response, error in if let error = error { print("Error deleting object: \(error)") semaphore.signal() return } if let httpResponse = response as? HTTPURLResponse { if httpResponse.statusCode == 204 || httpResponse.statusCode == 200 { print("Object deleted successfully: \(key)") success = true } else { print("Failed to delete object: HTTP \(httpResponse.statusCode)") } } semaphore.signal() } task.resume() semaphore.wait() return success } // MARK: - Authentication private func addAuthHeaders(request: inout URLRequest, method: String, path: String) { // Simplified AWS Signature v4 authentication // For POC, using basic headers let timestamp = ISO8601DateFormatter().string(from: Date()) request.addValue(accessKey, forHTTPHeaderField: "X-Amz-Access-Key") request.addValue(timestamp, forHTTPHeaderField: "X-Amz-Date") request.addValue("application/octet-stream", forHTTPHeaderField: "Content-Type") // Full AWS Signature v4 implementation would require: // 1. Create canonical request // 2. Create string to sign // 3. Calculate signature // 4. Add Authorization header // For POC, simplified headers are sufficient } // MARK: - Test Operations public func testConnection() -> Bool { print("Testing Object Storage connection...") let url = URL(string: "\(endpoint)")! var request = URLRequest(url: url) request.httpMethod = "GET" let semaphore = DispatchSemaphore(value: 0) var connected = false let task = session.dataTask(with: request) { data, response, error in if let error = error { print("Connection test failed: \(error)") semaphore.signal() return } if let httpResponse = response as? HTTPURLResponse { if httpResponse.statusCode == 200 || httpResponse.statusCode == 403 { // 403 is acceptable - means endpoint exists but auth required print("Connection test successful: HTTP \(httpResponse.statusCode)") connected = true } else { print("Connection test failed: HTTP \(httpResponse.statusCode)") } } semaphore.signal() } task.resume() semaphore.wait() return connected } public func testObjectOperations() { print("\n=== Object Storage Operations Test ===") // Test 1: Connection test print("Test 1: Connection Test") let connected = testConnection() if !connected { print(" Result: ❌ FAILED - Object Storage service not available") print(" Note: Skipping Object Storage tests") print(" To enable Object Storage tests, start MinIO or S3-compatible service") print("\n=== Object Storage Operations Test Skipped ===") return } print(" Result: ✅ SUCCESS - Object Storage service available") // Test 2: Create bucket print("\nTest 2: Create Bucket") let bucketCreated = createBucket(bucketName: "markbase-test-bucket") print(" Result: \(bucketCreated ? "✅ SUCCESS" : "❌ FAILED")") // Test 3: Upload object print("\nTest 3: Upload Object") let testData = "MarkBaseFS Object Storage Test".data(using: .utf8)! let uploaded = uploadObject(bucket: "markbase-test-bucket", key: "test.txt", data: testData) print(" Result: \(uploaded ? "✅ SUCCESS" : "❌ FAILED")") // Test 4: Download object print("\nTest 4: Download Object") let downloadedData = downloadObject(bucket: "markbase-test-bucket", key: "test.txt") if downloadedData != nil { print(" Result: ✅ SUCCESS") print(" Data size: \(downloadedData!.count) bytes") } else { print(" Result: ❌ FAILED") } // Test 5: Delete object print("\nTest 5: Delete Object") let deleted = deleteObject(bucket: "markbase-test-bucket", key: "test.txt") print(" Result: \(deleted ? "✅ SUCCESS" : "❌ FAILED")") print("\n=== Object Storage Operations Test Complete ===") } } // MARK: - Object Storage Configuration public struct ObjectStorageConfig { let endpoint: String let accessKey: String let secretKey: String let region: String public init(endpoint: String, accessKey: String, secretKey: String, region: String = "us-east-1") { self.endpoint = endpoint self.accessKey = accessKey self.secretKey = secretKey self.region = region } public static func minIODefault() -> ObjectStorageConfig { return ObjectStorageConfig( endpoint: "http://localhost:9000", accessKey: "minio_access_key", secretKey: "minio_secret_key" ) } public static func s3Default() -> ObjectStorageConfig { return ObjectStorageConfig( endpoint: "https://s3.amazonaws.com", accessKey: "aws_access_key", secretKey: "aws_secret_key" ) } public static func cephDefault() -> ObjectStorageConfig { return ObjectStorageConfig( endpoint: "http://localhost:7480", accessKey: "ceph_access_key", secretKey: "ceph_secret_key" ) } }