57 lines
2.0 KiB
Swift
57 lines
2.0 KiB
Swift
|
|
import Foundation
|
||
|
|
|
||
|
|
struct ManifestEntry: Codable {
|
||
|
|
let localIdentifier: String // PHAsset.localIdentifier — primary key
|
||
|
|
let filename: String
|
||
|
|
let creationDate: Date?
|
||
|
|
let fileSize: Int64
|
||
|
|
let remotePath: String
|
||
|
|
let uploadedAt: Date
|
||
|
|
}
|
||
|
|
|
||
|
|
struct BackupManifest: Codable {
|
||
|
|
static let remoteFilename = ".kisani.json"
|
||
|
|
|
||
|
|
var version: Int = 1
|
||
|
|
var lastUpdated: Date = Date()
|
||
|
|
var entries: [ManifestEntry] = []
|
||
|
|
|
||
|
|
// Primary match: stable PHAsset localIdentifier
|
||
|
|
func contains(localIdentifier id: String) -> Bool {
|
||
|
|
guard !id.isEmpty else { return false }
|
||
|
|
return entries.contains { $0.localIdentifier == id }
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fallback: case-insensitive filename (for assets backed up before manifest existed)
|
||
|
|
func containsByFilename(_ name: String) -> Bool {
|
||
|
|
let lower = name.lowercased()
|
||
|
|
return entries.contains { $0.filename.lowercased() == lower }
|
||
|
|
}
|
||
|
|
|
||
|
|
// Merge new entries, replacing any existing record for the same localIdentifier
|
||
|
|
mutating func merge(entries newEntries: [ManifestEntry]) {
|
||
|
|
let newIds = Set(newEntries.map { $0.localIdentifier }.filter { !$0.isEmpty })
|
||
|
|
entries.removeAll { !$0.localIdentifier.isEmpty && newIds.contains($0.localIdentifier) }
|
||
|
|
entries.append(contentsOf: newEntries)
|
||
|
|
lastUpdated = Date()
|
||
|
|
}
|
||
|
|
|
||
|
|
// Bootstrap from a raw NAS directory listing (no localIdentifiers known)
|
||
|
|
static func buildFromNASListing(_ items: [NASItem]) -> BackupManifest {
|
||
|
|
var manifest = BackupManifest()
|
||
|
|
manifest.entries = items
|
||
|
|
.filter { !$0.isDirectory && $0.name != remoteFilename }
|
||
|
|
.map { item in
|
||
|
|
ManifestEntry(
|
||
|
|
localIdentifier: "",
|
||
|
|
filename: item.name,
|
||
|
|
creationDate: item.modifiedDate,
|
||
|
|
fileSize: item.size,
|
||
|
|
remotePath: item.path,
|
||
|
|
uploadedAt: item.modifiedDate ?? Date()
|
||
|
|
)
|
||
|
|
}
|
||
|
|
return manifest
|
||
|
|
}
|
||
|
|
}
|