将 otg_swift OTG 引擎迁入 Core/CameraOTG,并接入旅拍有线传图与历史导入。

恢复 USB 相机连接、边拍边传、三品牌驱动与 SwiftUI 历史导入页,通过适配层对接现有本地上传与 OSS 管道。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-06 11:22:07 +08:00
parent d7fec35715
commit be9c844bd3
50 changed files with 5721 additions and 20 deletions

View File

@ -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 }