Make backup status reconciliation memory-safe
Replace full [PhotoAsset] array enumeration with PHFetchResult batch enumeration (autoreleasepool + Task.yield per batch) to keep memory bounded during reconciliation. Introduce ManifestIndex with two O(1) Set<String> lookups (localIdentifier + lowercased filename) instead of O(n×m) linear scans. Scope manifest Data+struct inside buildManifestIndex so they are released by ARC before the comparison loop begins. Add UIApplication.didReceiveMemoryWarningNotification handler to cancel in-flight tasks under memory pressure. Separate debounceTask from refreshTask to avoid cancelling long-running reconciles on every photo library event. fetchResultForReconciliation returns PHFetchResult directly — no [PhotoAsset] array is ever allocated during reconciliation. Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
@@ -1,4 +1,54 @@
|
||||
import Foundation
|
||||
import Photos
|
||||
|
||||
// MARK: — ManifestIndex
|
||||
// Lightweight comparison structure — two Sets for O(1) lookups.
|
||||
// Built from BackupManifest and then the manifest is released.
|
||||
struct ManifestIndex {
|
||||
let localIdentifiers: Set<String> // PHAsset.localIdentifier (primary key)
|
||||
let lowercasedFilenames: Set<String> // filename fallback for legacy/bootstrap entries
|
||||
let totalCount: Int // non-hidden file count for NAS Archive stat
|
||||
|
||||
var isFilenameOnly: Bool { localIdentifiers.isEmpty }
|
||||
|
||||
func matches(localIdentifier id: String) -> Bool {
|
||||
!id.isEmpty && localIdentifiers.contains(id)
|
||||
}
|
||||
|
||||
func matches(filename: String) -> Bool {
|
||||
lowercasedFilenames.contains(filename.lowercased())
|
||||
}
|
||||
|
||||
// Build from decoded manifest — manifest released by caller after this returns
|
||||
init(manifest: BackupManifest) {
|
||||
var ids = Set<String>(minimumCapacity: manifest.entries.count)
|
||||
var names = Set<String>(minimumCapacity: manifest.entries.count)
|
||||
var count = 0
|
||||
for entry in manifest.entries {
|
||||
if !entry.filename.hasPrefix(".") { count += 1 }
|
||||
if !entry.localIdentifier.isEmpty { ids.insert(entry.localIdentifier) }
|
||||
names.insert(entry.filename.lowercased())
|
||||
}
|
||||
self.localIdentifiers = ids
|
||||
self.lowercasedFilenames = names
|
||||
self.totalCount = count
|
||||
}
|
||||
|
||||
// Build from NAS directory listing (bootstrap — no localIdentifiers known)
|
||||
init(nasListing items: [NASItem]) {
|
||||
var names = Set<String>()
|
||||
var count = 0
|
||||
for item in items where !item.isDirectory && item.name != BackupManifest.remoteFilename {
|
||||
if !item.name.hasPrefix(".") { count += 1 }
|
||||
names.insert(item.name.lowercased())
|
||||
}
|
||||
self.localIdentifiers = []
|
||||
self.lowercasedFilenames = names
|
||||
self.totalCount = count
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: — ManifestEntry
|
||||
|
||||
struct ManifestEntry: Codable {
|
||||
let localIdentifier: String // PHAsset.localIdentifier — primary key
|
||||
|
||||
Reference in New Issue
Block a user