// // CloudTransferWorkers.swift // suixinkan // import Foundation import Photos /// 云盘 OSS 上传协议,便于传输管理注入测试替身。 @MainActor protocol CloudDriveUploading { /// 上传云盘文件并返回远程 URL。 func uploadCloudDriveFile( data: Data, fileName: String, fileType: Int, scenicId: Int, onProgress: @escaping (Int) -> Void ) async throws -> String } extension OSSUploadService: CloudDriveUploading {} /// 云盘下载协议。 protocol CloudFileDownloading { /// 下载远程文件并返回本地临时文件 URL。 func downloadFile( remoteURL: String, fileName: String, onProgress: @escaping @Sendable (Int) -> Void ) async throws -> URL } /// URLSession 实现的云盘下载器。 struct CloudURLSessionDownloader: CloudFileDownloading { func downloadFile( remoteURL: String, fileName: String, onProgress: @escaping @Sendable (Int) -> Void ) async throws -> URL { guard let url = URL(string: remoteURL) else { throw CloudTransferError.invalidURL } onProgress(1) let (temporaryURL, _) = try await URLSession.shared.download(from: url) try Task.checkCancellation() let directory = FileManager.default.temporaryDirectory.appendingPathComponent("cloud_drive_downloads", isDirectory: true) try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) let destinationURL = directory.appendingPathComponent(CloudTransferFileName.sanitized(fileName)) if FileManager.default.fileExists(atPath: destinationURL.path) { try FileManager.default.removeItem(at: destinationURL) } try FileManager.default.moveItem(at: temporaryURL, to: destinationURL) onProgress(100) return destinationURL } } /// 云盘下载后的系统相册保存协议。 protocol CloudPhotoSaving { /// 将图片或视频保存到系统相册。 func saveToPhotoLibrary(fileURL: URL, fileType: Int) async throws } /// Photos 框架实现的相册保存器。 struct CloudPhotoLibrarySaver: CloudPhotoSaving { func saveToPhotoLibrary(fileURL: URL, fileType: Int) async throws { let status = await PHPhotoLibrary.requestAuthorization(for: .addOnly) guard status == .authorized || status == .limited else { throw CloudTransferError.photoPermissionDenied } try await PHPhotoLibrary.shared().performChanges { if fileType == 1 { PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: fileURL) } else { PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: fileURL) } } } } /// 云盘传输错误。 enum CloudTransferError: LocalizedError, Equatable { case invalidURL case localFileMissing case photoPermissionDenied var errorDescription: String? { switch self { case .invalidURL: "文件地址不存在" case .localFileMissing: "本地文件不存在" case .photoPermissionDenied: "请允许访问相册后再下载" } } } /// 云盘传输文件名工具。 enum CloudTransferFileName { /// 清理文件名中不适合落盘的字符。 static func sanitized(_ fileName: String) -> String { let trimmed = fileName.trimmingCharacters(in: .whitespacesAndNewlines) var unsafe = CharacterSet.controlCharacters unsafe.formUnion(CharacterSet(charactersIn: "/\\")) let value = trimmed.unicodeScalars.reduce(into: "") { result, scalar in result += unsafe.contains(scalar) ? "_" : String(scalar) } return value.isEmpty ? "cloud_file" : value } }