perf: eliminate full rescans with local caches; add queue tracking, notifications, SyncView redesign
Cached indexes: - NASManifestCache: actor-based disk cache for the NAS manifest (kisani_manifest_cache.json) Avoids NAS network round-trip when cache < 5 min old. Updated after every manifest download. - LocalPhotoIndex: actor-based disk cache of phone asset metadata (kisani_local_index.json) Built once on first launch, then updated incrementally via PHPhotoLibraryChangeObserver. O(1) countSafe via in-memory dict (vs PHFetchResult batch enumeration + CoreData calls). Fast path reconcile: When both caches are warm, reconcile() skips NAS connection + PHFetchResult enumeration entirely and returns in microseconds. Falls through to full NAS path only when stale. AutoBackupCoordinator.onActive(): Changed force: true → force: false so the 30-second debounce prevents expensive rescans on rapid app foreground/background cycles. Dashboard appears instantly from cached snapshot. BackupQueueItem + queue tracking: BackupEngine publishes queueItems: [BackupQueueItem] with per-file status, progress, speed, error details. Queue is persisted to disk and reloaded on next launch so SyncView always has data to show even after restart. NotificationService: Centralised local notification sender replacing the inline UNMutableNotificationContent blocks. Sends for: backup started, completed, failed, paused, NAS offline. Uses category identifiers so same-type notifications replace each other. SyncView redesign: - Removed pill/chip around NAS IP — replaced with plain "Online/Offline/Checking" dot+text - No live NAS directory listing (was expensive NAS connection on every tab open) - Shows engine.queueItems grouped into Active / Failed / Completed sections - Per-file rows with inline progress bar + bytes/speed for uploading items, error text for failed - Idle state shows archive count from statusService snapshot GalleryViewModel: NAS tab and All tab now use NASManifestCache as fallback when statusService.lastManifest is nil (offline launch). Gallery shows NAS items even when NAS is unreachable. AppDelegate: Bootstraps LocalPhotoIndex.loadOrBuild() in background on launch (non-blocking). Co-Authored-By: Kutesir <kutesir@provoc.ug> Co-Authored-By: Sentry <sentry@provoc.ug>
This commit is contained in:
34
Core/Models/BackupQueueItem.swift
Normal file
34
Core/Models/BackupQueueItem.swift
Normal file
@@ -0,0 +1,34 @@
|
||||
import Foundation
|
||||
|
||||
struct BackupQueueItem: Identifiable, Codable, Sendable {
|
||||
let id: String // = asset localIdentifier
|
||||
var filename: String
|
||||
var nasPath: String
|
||||
var status: Status
|
||||
var bytesUploaded: Int64
|
||||
var totalBytes: Int64
|
||||
var speedBytesPerSec: Double
|
||||
var error: UploadError?
|
||||
var retryCount: Int
|
||||
var queuedAt: Date
|
||||
var completedAt: Date?
|
||||
|
||||
enum Status: String, Codable, Sendable {
|
||||
case queued, uploading, uploaded, skipped, failed
|
||||
}
|
||||
|
||||
struct UploadError: Codable, Sendable {
|
||||
var code: String
|
||||
var message: String
|
||||
var timestamp: Date
|
||||
}
|
||||
|
||||
var progress: Double {
|
||||
guard totalBytes > 0 else {
|
||||
return (status == .uploaded || status == .skipped) ? 1.0 : 0.0
|
||||
}
|
||||
return min(1.0, Double(bytesUploaded) / Double(totalBytes))
|
||||
}
|
||||
|
||||
var isFinished: Bool { status == .uploaded || status == .skipped || status == .failed }
|
||||
}
|
||||
Reference in New Issue
Block a user