From dbfc4e20f37ab1b34388724b80929000d7c1388d Mon Sep 17 00:00:00 2001 From: "yanmu.why" Date: Tue, 15 Oct 2024 17:23:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20download=E5=A4=84=E7=90=86403=E9=94=99?= =?UTF-8?q?=E8=AF=AF=20&=20upload=E6=9C=80=E5=A4=A7=E5=88=86=E7=89=87?= =?UTF-8?q?=E9=99=90=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AliyunpanSDK.podspec | 2 +- .../Demo-iOS/FileListViewController.swift | 4 ++-- Demo/Demo/Demo-iOS/ViewController.swift | 4 ++-- Sources/AliyunpanSDK/AliyunpanError.swift | 4 ++++ Sources/AliyunpanSDK/AliyunpanSDK.swift | 2 +- .../Download/AliyunpanDownloadChunk.swift | 16 +++++++++++-- .../Download/AliyunpanDownloadTask.swift | 10 ++++---- .../Download/DownloadChunkOperation.swift | 24 +++++++++++++------ .../Upload/AliyunpanUploader.swift | 14 +++++++---- Sources/AliyunpanSDK/Info.plist | 2 +- 10 files changed, 57 insertions(+), 25 deletions(-) diff --git a/AliyunpanSDK.podspec b/AliyunpanSDK.podspec index d50fbc8..c0970da 100644 --- a/AliyunpanSDK.podspec +++ b/AliyunpanSDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = "AliyunpanSDK" - spec.version = "0.3.4" + spec.version = "0.3.5" spec.summary = "Aliyunpan OpenSDK-iOS" spec.description = <<-DESC diff --git a/Demo/Demo/Demo-iOS/FileListViewController.swift b/Demo/Demo/Demo-iOS/FileListViewController.swift index c67fdbf..4b00d6e 100644 --- a/Demo/Demo/Demo-iOS/FileListViewController.swift +++ b/Demo/Demo/Demo-iOS/FileListViewController.swift @@ -178,7 +178,7 @@ extension FileListViewController: UICollectionViewDelegate { extension FileListViewController: FileCellDelegate { func fileCell(_ cell: FileCell, willDownload item: DisplayItem) { - guard let url = FileManager.default.urls(for: .downloadsDirectory, in: .userDomainMask).first else { + guard let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return } @@ -188,7 +188,7 @@ extension FileListViewController: FileCellDelegate { } else { let file = item.file let filename = file.name.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) ?? "" - let destination = url.appendingPathComponent(filename) + let destination = url.appendingPathComponent("Download").appendingPathComponent(filename) client.downloader.download(file: file, to: destination) } } diff --git a/Demo/Demo/Demo-iOS/ViewController.swift b/Demo/Demo/Demo-iOS/ViewController.swift index 0bcd5b6..c87401c 100644 --- a/Demo/Demo/Demo-iOS/ViewController.swift +++ b/Demo/Demo/Demo-iOS/ViewController.swift @@ -75,7 +75,7 @@ class ViewController: UIViewController { let file = try await client.uploader .upload( fileURL: url, - fileName: "test_\(Date().timeIntervalSince1970).pdf", + fileName: url.lastPathComponent, driveId: driveId, folderId: "root", useProof: true) @@ -186,7 +186,7 @@ extension ViewController: UICollectionViewDelegate { } } case .uploadFileToRoot: - let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: [.item]) + let documentPickerController = UIDocumentPickerViewController(forOpeningContentTypes: [.item], asCopy: true) documentPickerController.delegate = self present(documentPickerController, animated: true) case .createFolderOnRoot: diff --git a/Sources/AliyunpanSDK/AliyunpanError.swift b/Sources/AliyunpanSDK/AliyunpanError.swift index 0a92a45..1241209 100644 --- a/Sources/AliyunpanSDK/AliyunpanError.swift +++ b/Sources/AliyunpanSDK/AliyunpanError.swift @@ -88,6 +88,10 @@ public struct AliyunpanError { case userCancelled /// 缺少 client case invalidClient + /// 服务器错误 + case serverError + /// 未知错误 + case unknownError } /// 上传错误 diff --git a/Sources/AliyunpanSDK/AliyunpanSDK.swift b/Sources/AliyunpanSDK/AliyunpanSDK.swift index 5b8a640..6e2de43 100644 --- a/Sources/AliyunpanSDK/AliyunpanSDK.swift +++ b/Sources/AliyunpanSDK/AliyunpanSDK.swift @@ -52,4 +52,4 @@ public class Aliyunpan { } } -let version = "0.3.4" +let version = "0.3.5" diff --git a/Sources/AliyunpanSDK/HTTPRequest/Download/AliyunpanDownloadChunk.swift b/Sources/AliyunpanSDK/HTTPRequest/Download/AliyunpanDownloadChunk.swift index 0b1aee9..ab3681b 100644 --- a/Sources/AliyunpanSDK/HTTPRequest/Download/AliyunpanDownloadChunk.swift +++ b/Sources/AliyunpanSDK/HTTPRequest/Download/AliyunpanDownloadChunk.swift @@ -7,13 +7,21 @@ import Foundation -public struct AliyunpanDownloadChunk: Equatable { +public struct AliyunpanDownloadChunk: Equatable, CustomStringConvertible { public let start: Int64 public let end: Int64 + public let index: Int init(start: Int64, end: Int64) { self.start = start self.end = end + self.index = -1 + } + + init(start: Int64, end: Int64, index: Int) { + self.start = start + self.end = end + self.index = index } init?(rangeString: String, fileSize: Int64) { @@ -29,6 +37,10 @@ public struct AliyunpanDownloadChunk: Equatable { } else { end = fileSize } - self = Self(start: start, end: end) + self = Self(start: start, end: end, index: -1) + } + + public var description: String { + "[chunk-\(index)]: \(start)-\(end)" } } diff --git a/Sources/AliyunpanSDK/HTTPRequest/Download/AliyunpanDownloadTask.swift b/Sources/AliyunpanSDK/HTTPRequest/Download/AliyunpanDownloadTask.swift index 8c3b489..75095ad 100644 --- a/Sources/AliyunpanSDK/HTTPRequest/Download/AliyunpanDownloadTask.swift +++ b/Sources/AliyunpanSDK/HTTPRequest/Download/AliyunpanDownloadTask.swift @@ -180,7 +180,7 @@ extension AliyunpanDownloadTask { var chunks: [AliyunpanDownloadChunk] { stride(from: 0, to: totalSize, by: Int64.Stride(chunkSize)).map { - AliyunpanDownloadChunk(start: $0, end: min($0 + Int64(chunkSize), totalSize)) + AliyunpanDownloadChunk(start: $0, end: min($0 + Int64(chunkSize), totalSize), index: Int($0 / chunkSize)) } } @@ -270,9 +270,11 @@ extension AliyunpanDownloadTask: DownloadChunkOperationDelegate { } } catch AliyunpanError.DownloadError.downloadURLExpired { Logger.log(.error, msg: "[Downloader][\(file.name)], \(chunkIndex)/\(chunks.count) error, downloadURLExpired") - - // 下载链接过期 - retry(chunk: operation.chunk) + // 等待1s重试 + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + guard let self else { return } + self.retry(chunk: operation.chunk) + } } catch let error as NSError where error.domain == NSURLErrorDomain { Logger.log(.error, msg: "[Downloader][\(file.name)], \(chunkIndex)/\(chunks.count) error, \(error)") diff --git a/Sources/AliyunpanSDK/HTTPRequest/Download/DownloadChunkOperation.swift b/Sources/AliyunpanSDK/HTTPRequest/Download/DownloadChunkOperation.swift index c7b781f..acf719d 100644 --- a/Sources/AliyunpanSDK/HTTPRequest/Download/DownloadChunkOperation.swift +++ b/Sources/AliyunpanSDK/HTTPRequest/Download/DownloadChunkOperation.swift @@ -167,12 +167,18 @@ extension DownloadChunkOperation: URLSessionDownloadDelegate { func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) { if let error { chunkOperationDidCompleteWithError(error) - } else if let response = task.response as? HTTPURLResponse, - response.statusCode == 403, - // https://help.aliyun.com/zh/oss/support/0002-00000069 - response.value(forHTTPHeaderField: "x-oss-ec") == "0002-00000069" { - chunkOperationDidCompleteWithError( - AliyunpanError.DownloadError.downloadURLExpired) + } else if let response = task.response as? HTTPURLResponse { + if response.statusCode == 403 { + // https://help.aliyun.com/zh/oss/support/0002-00000069 + chunkOperationDidCompleteWithError( + AliyunpanError.DownloadError.downloadURLExpired) + } else if response.statusCode < 200 || response.statusCode >= 300{ + chunkOperationDidCompleteWithError(AliyunpanError.DownloadError.serverError) + } else { + /// 200 - 300 之间的不认为是错误 + } + } else { + chunkOperationDidCompleteWithError(AliyunpanError.DownloadError.unknownError) } } @@ -183,7 +189,11 @@ extension DownloadChunkOperation: URLSessionDownloadDelegate { func urlSession(_ session: URLSession, downloadTask: URLSessionDownloadTask, didFinishDownloadingTo location: URL) { do { let data = try Data(contentsOf: location) - chunkOperatioDidFinishDownload(data) + if data.count == chunk.end - chunk.start { + chunkOperatioDidFinishDownload(data) + } else { + /// 大小不一致时,会触发 urlSession的 didCompleteWithError方法,这里不做处理 + } } catch { chunkOperationDidCompleteWithError(error) } diff --git a/Sources/AliyunpanSDK/HTTPRequest/Upload/AliyunpanUploader.swift b/Sources/AliyunpanSDK/HTTPRequest/Upload/AliyunpanUploader.swift index 7a51371..5a37e7e 100644 --- a/Sources/AliyunpanSDK/HTTPRequest/Upload/AliyunpanUploader.swift +++ b/Sources/AliyunpanSDK/HTTPRequest/Upload/AliyunpanUploader.swift @@ -38,8 +38,12 @@ fileprivate extension Array where Element == AliyunpanFile.PartInfo { /// 上传器 public class AliyunpanUploader: NSObject { weak var client: AliyunpanClient? - /// 每 2G 分片 - private static let chunkSize: Int64 = 2_000_000_000 + /// 默认每 2G 分片,最大1000片 + private static let defaultMaxChunkCount: Int64 = 1000 + private static let defaultChunkSize: Int64 = 2_000_000_000 + private static func realChunkSize(fileSize: Int64) -> Int64 { + max(defaultChunkSize, fileSize / defaultMaxChunkCount) + } /// 创建上传任务,并适当分片 private func createUploadTask( @@ -51,7 +55,7 @@ public class AliyunpanUploader: NSObject { folderId: String, checkNameMode: AliyunpanFile.CheckNameMode ) async throws -> AliyunpanScope.File.CreateFile.Response { - let partInfoList = [AliyunpanFile.PartInfo](fileSize: fileSize, chunkSize: Self.chunkSize) + let partInfoList = [AliyunpanFile.PartInfo](fileSize: fileSize, chunkSize: Self.realChunkSize(fileSize: fileSize)) let task = try await client.send( AliyunpanScope.File.CreateFile( @@ -118,7 +122,7 @@ public class AliyunpanUploader: NSObject { throw AliyunpanError.AuthorizeError.accessTokenInvalid } - let partInfoList = [AliyunpanFile.PartInfo](fileSize: fileSize, chunkSize: Self.chunkSize) + let partInfoList = [AliyunpanFile.PartInfo](fileSize: fileSize, chunkSize: Self.realChunkSize(fileSize: fileSize)) var isPreHashMatched = false // 大于 10M 的文件先预校验 @@ -232,7 +236,7 @@ public class AliyunpanUploader: NSObject { "Content-Length": "\(partInfo.part_size ?? 0)", "Content-Type": "" // 不能传 Cotent-Type,否则会失败 ] - let beginOffset = Int64(index) * Self.chunkSize + let beginOffset = Int64(index) * Self.realChunkSize(fileSize: fileSize) let endOffset = beginOffset + Int64(partInfo.part_size ?? 0) let data = try FileManager.default.dataChunk(at: fileURL, in: Int(beginOffset)..CFBundlePackageType FMWK CFBundleShortVersionString - 0.3.4 + 0.3.5 CFBundleSignature ???? CFBundleVersion