feat: 添加云盘与消息中心功能
This commit is contained in:
@ -0,0 +1,113 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user