fix: resolve duplicate-file failures, alreadySafe reset, and backup restart loop
BackupEngine: after all retries fail, call fileExists() before recording a failure. If the file is on NAS (uploaded earlier but fileExists silently threw on the pre-check), count it as skipped and add it to manifestEntries — this ensures writeManifest includes every safe asset and prevents alreadySafe from dropping to 1 after the post-backup reconcile. BackupView: remove the immediate refreshAndWait from onChange(.failed). That call raced with refreshAfterBackup's writeManifest, cancelled the manifest- correct refresh, then reconciled against a stale 1-entry NAS manifest. Auto-resolve (.failed → .completed when needBackup == 0) is now owned by the coordinator so it runs after writeManifest finishes. AutoBackupCoordinator: add backupTriggerArmed flag (set by onActive/ handleNASReachable, cleared by handleReconciliationComplete). Post-backup reconciles from refreshAfterBackup are no longer able to re-trigger auto- backup — only an explicit app-open or LAN-join can arm the trigger. Also added auto-resolve: calls engine.resolveSuccess() when job is .failed but needBackup == 0, which fires the .completed green-flash path in BackupView. Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
@@ -306,17 +306,38 @@ final class BackupEngine: ObservableObject {
|
||||
}
|
||||
|
||||
if !uploadSucceeded, let error = lastUploadError {
|
||||
logger.error("Upload permanently failed for \(asset.filename, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
job.fileFailed()
|
||||
failed += 1
|
||||
if assetIdx < queueItems.count {
|
||||
queueItems[assetIdx].status = .failed
|
||||
queueItems[assetIdx].completedAt = Date()
|
||||
queueItems[assetIdx].error = BackupQueueItem.UploadError(
|
||||
code: (error as NSError).domain,
|
||||
message: error.localizedDescription,
|
||||
timestamp: Date()
|
||||
)
|
||||
// Before recording a failure, verify the file isn't already on NAS.
|
||||
// fileExists() may have returned nil earlier due to a transient SMB error;
|
||||
// if it's actually there, the asset is safe — count it as skipped, not failed.
|
||||
if (try? await transfer.fileExists(at: remotePath)) == true {
|
||||
job.fileCompleted(skipped: true)
|
||||
skipped += 1
|
||||
if assetIdx < queueItems.count {
|
||||
queueItems[assetIdx].status = .skipped
|
||||
queueItems[assetIdx].completedAt = Date()
|
||||
}
|
||||
manifestEntries.append(ManifestEntry(
|
||||
localIdentifier: asset.localIdentifier,
|
||||
filename: asset.filename,
|
||||
creationDate: asset.creationDate,
|
||||
fileSize: 0,
|
||||
remotePath: remotePath,
|
||||
uploadedAt: Date()
|
||||
))
|
||||
logger.info("Upload failed but file confirmed on NAS — marking safe: \(asset.filename, privacy: .public)")
|
||||
} else {
|
||||
logger.error("Upload permanently failed for \(asset.filename, privacy: .public): \(error.localizedDescription, privacy: .public)")
|
||||
job.fileFailed()
|
||||
failed += 1
|
||||
if assetIdx < queueItems.count {
|
||||
queueItems[assetIdx].status = .failed
|
||||
queueItems[assetIdx].completedAt = Date()
|
||||
queueItems[assetIdx].error = BackupQueueItem.UploadError(
|
||||
code: (error as NSError).domain,
|
||||
message: error.localizedDescription,
|
||||
timestamp: Date()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user