将 otg_swift OTG 引擎迁入 Core/CameraOTG,并接入旅拍有线传图与历史导入。
恢复 USB 相机连接、边拍边传、三品牌驱动与 SwiftUI 历史导入页,通过适配层对接现有本地上传与 OSS 管道。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@ -142,6 +142,96 @@ final class WiredCameraTransferPipeline {
|
||||
lastNotifiedProgressByAssetID.removeValue(forKey: assetID)
|
||||
}
|
||||
|
||||
/// 将相机已下载到本地的照片纳入传输管道。
|
||||
func ingestDownloadedAsset(
|
||||
assetID: String,
|
||||
filename: String,
|
||||
localPath: String,
|
||||
capturedAt: Date?,
|
||||
autoUpload: Bool
|
||||
) {
|
||||
guard !excludedAssetIDs.contains(assetID) else { return }
|
||||
|
||||
let capturedAtString = capturedAt.map { CameraTransferTask.capturedAtString(from: $0) }
|
||||
?? CameraTransferTask.capturedAtString(forLocalPath: localPath)
|
||||
|
||||
if let index = tasks.firstIndex(where: { $0.assetID == assetID }) {
|
||||
tasks[index].localPath = localPath
|
||||
if tasks[index].capturedAt?.isEmpty != false {
|
||||
tasks[index].capturedAt = capturedAtString
|
||||
}
|
||||
if tasks[index].status != .uploaded {
|
||||
tasks[index].status = .downloaded
|
||||
}
|
||||
tasks[index].errorMessage = nil
|
||||
} else {
|
||||
tasks.insert(
|
||||
CameraTransferTask(
|
||||
assetID: assetID,
|
||||
filename: filename,
|
||||
localPath: localPath,
|
||||
capturedAt: capturedAtString,
|
||||
status: .downloaded
|
||||
),
|
||||
at: 0
|
||||
)
|
||||
}
|
||||
|
||||
if autoUpload, uploadEnabled, uploadSink != nil {
|
||||
autoUploadEligibleAssetIDs.insert(assetID)
|
||||
processUploadQueue()
|
||||
}
|
||||
|
||||
notify()
|
||||
}
|
||||
|
||||
/// 标记 asset 正在从相机下载。
|
||||
func markDownloading(assetID: String, filename: String) {
|
||||
guard !excludedAssetIDs.contains(assetID) else { return }
|
||||
if let index = tasks.firstIndex(where: { $0.assetID == assetID }) {
|
||||
tasks[index].status = .downloading
|
||||
tasks[index].errorMessage = nil
|
||||
} else {
|
||||
tasks.insert(
|
||||
CameraTransferTask(assetID: assetID, filename: filename, status: .downloading),
|
||||
at: 0
|
||||
)
|
||||
}
|
||||
notify()
|
||||
}
|
||||
|
||||
/// 标记 asset 下载失败。
|
||||
func failDownload(assetID: String, message: String) {
|
||||
guard let index = tasks.firstIndex(where: { $0.assetID == assetID }) else { return }
|
||||
tasks[index].status = .failed
|
||||
tasks[index].errorMessage = message
|
||||
notify()
|
||||
}
|
||||
|
||||
/// 并发上传边拍边传新照片;拍后传输任务只能由用户显式上传。
|
||||
private func processUploadQueue() {
|
||||
guard uploadSink != nil else { return }
|
||||
|
||||
while activeUploadAssetIDs.count < maxConcurrentUploads,
|
||||
let task = tasks.first(where: {
|
||||
$0.status == .downloaded
|
||||
&& !activeUploadAssetIDs.contains($0.assetID)
|
||||
&& autoUploadEligibleAssetIDs.contains($0.assetID)
|
||||
}) {
|
||||
startConcurrentUpload(assetID: task.assetID)
|
||||
}
|
||||
}
|
||||
|
||||
private func startConcurrentUpload(assetID: String) {
|
||||
guard !activeUploadAssetIDs.contains(assetID) else { return }
|
||||
activeUploadAssetIDs.insert(assetID)
|
||||
Task { @MainActor in
|
||||
await self.uploadTask(assetID: assetID)
|
||||
self.activeUploadAssetIDs.remove(assetID)
|
||||
self.processUploadQueue()
|
||||
}
|
||||
}
|
||||
|
||||
private func uploadAssetIfNeeded(assetID: String) async {
|
||||
if activeUploadAssetIDs.contains(assetID) { return }
|
||||
guard let index = tasks.firstIndex(where: { $0.assetID == assetID }) else { return }
|
||||
|
||||
Reference in New Issue
Block a user