修复边拍边传自动上传范围,并避免上传状态被旧快照回退。

拍后传输下载的历史照片切换模式后不再自动补传;刷新相机文件仅同步本地。重试上传后刷新单张状态,并忽略乱序 pipeline 通知导致的已上传回退。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-30 14:50:45 +08:00
parent ceac8ba67e
commit 5ebcec1cfc
5 changed files with 98 additions and 11 deletions

View File

@ -16,9 +16,11 @@
1. 相机服务回调 `onNewAsset`
2. Pipeline 下载文件到本地并更新任务状态。
3. 若开启自动上传调用 `CameraAssetUploadSink.upload`
3.照片是在开启自动上传时由 `onNewAsset` 新发现,加入自动上传资格队列并调用 `CameraAssetUploadSink.upload`
4. 上传成功后任务状态变为 `uploaded`
`syncExistingPhotos()` 仅同步相机历史照片到本地任务列表,不授予自动上传资格。拍后传输模式下已下载的 `downloaded` 任务,切换到边拍边传后仍保持待上传,只有业务层显式调用 `uploadAssets(withIDs:)``retryUpload(...)` 时才会上传。
## 与业务层关系
- 旅拍相册通过 `TravelAlbumMaterialUploader` 实现 SinkOSS 上传 + `upload-material` 登记。

View File

@ -17,6 +17,7 @@ final class CameraTransferPipeline {
private var uploadSink: (any CameraAssetUploadSink)?
private var uploadEnabled = true
private var activeUploadAssetIDs: Set<String> = []
private var autoUploadEligibleAssetIDs: Set<String> = []
private var pendingDeferredNotifyTask: Task<Void, Never>?
private var lastDeferredNotifyDate = Date.distantPast
private var lastNotifiedProgressByAssetID: [String: Int] = [:]
@ -67,12 +68,13 @@ final class CameraTransferPipeline {
///
func retryFailedUploads() async {
guard uploadEnabled, uploadSink != nil else { return }
let failedAssetIDs = tasks.filter { $0.status == .failed }.map(\.assetID)
for index in tasks.indices where tasks[index].status == .failed {
tasks[index].status = .downloaded
tasks[index].errorMessage = nil
}
notify()
processUploadQueue()
await uploadAssets(withIDs: failedAssetIDs)
}
/// asset
@ -150,6 +152,7 @@ final class CameraTransferPipeline {
try? FileManager.default.removeItem(at: localURL)
}
tasks.removeAll()
autoUploadEligibleAssetIDs.removeAll()
notify()
}
@ -167,10 +170,19 @@ final class CameraTransferPipeline {
if inFlightAssetIDs.contains(asset.id) { return }
if tasks.contains(where: { $0.assetID == asset.id && $0.status == .downloading }) { return }
let isKnownAsset = tasks.contains { $0.assetID == asset.id }
let shouldAutoUpload = !skipIfExists
&& uploadEnabled
&& uploadSink != nil
&& (!isKnownAsset || autoUploadEligibleAssetIDs.contains(asset.id))
inFlightAssetIDs.insert(asset.id)
defer { inFlightAssetIDs.remove(asset.id) }
let assetID = asset.id
if shouldAutoUpload {
autoUploadEligibleAssetIDs.insert(assetID)
}
if let existingIndex = tasks.firstIndex(where: { $0.assetID == assetID }) {
tasks[existingIndex].status = .downloading
tasks[existingIndex].errorMessage = nil
@ -187,8 +199,7 @@ final class CameraTransferPipeline {
await downloadAsset(assetID: assetID, asset: asset)
if uploadEnabled,
uploadSink != nil,
if shouldAutoUpload,
tasks.first(where: { $0.assetID == assetID })?.status == .downloaded {
processUploadQueue()
}
@ -237,12 +248,16 @@ final class CameraTransferPipeline {
}
}
///
///
private func processUploadQueue() {
guard uploadEnabled, uploadSink != nil else { return }
guard uploadSink != nil else { return }
while activeUploadAssetIDs.count < maxConcurrentUploads,
let task = tasks.first(where: { $0.status == .downloaded && !activeUploadAssetIDs.contains($0.assetID) }) {
let task = tasks.first(where: {
$0.status == .downloaded
&& !activeUploadAssetIDs.contains($0.assetID)
&& autoUploadEligibleAssetIDs.contains($0.assetID)
}) {
startConcurrentUpload(assetID: task.assetID)
}
}
@ -292,6 +307,7 @@ final class CameraTransferPipeline {
tasks[taskIndex].status = .uploaded
tasks[taskIndex].remoteURL = remoteURL
tasks[taskIndex].progress = 100
autoUploadEligibleAssetIDs.remove(assetID)
lastNotifiedProgressByAssetID.removeValue(forKey: assetID)
notify()
return