Sync pairing files from Documents

This commit is contained in:
CelloSerenity 2026-08-01 08:41:16 -06:00
parent 49b7c7aef4
commit ea42d847d1

View file

@ -7,14 +7,14 @@ import Foundation
import UniformTypeIdentifiers import UniformTypeIdentifiers
enum PairingFileStore { enum PairingFileStore {
static let fileName = "rp_pairing_file.plist" static let fileName = "pairingFile.plist"
static let supportedContentTypes: [UTType] = [ static let supportedContentTypes: [UTType] = [
UTType(filenameExtension: "mobiledevicepairing", conformingTo: .data)!, UTType(filenameExtension: "mobiledevicepairing", conformingTo: .data)!,
UTType(filenameExtension: "mobiledevicepair", conformingTo: .data)!, UTType(filenameExtension: "mobiledevicepair", conformingTo: .data)!,
.propertyList .propertyList
] ]
private static let legacyFileName = "pairingFile.plist" private static let legacyFileName = "rp_pairing_file.plist"
static var url: URL { static var url: URL {
directoryURL.appendingPathComponent(fileName) directoryURL.appendingPathComponent(fileName)
@ -25,31 +25,31 @@ enum PairingFileStore {
let destination = url let destination = url
try? fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true) try? fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)
guard !fileManager.fileExists(atPath: destination.path) else { if let documentURL = documentSourceURL(fileManager: fileManager) {
removeLegacyCopies(fileManager: fileManager) if !fileManager.fileExists(atPath: destination.path) ||
!fileManager.contentsEqual(atPath: documentURL.path, andPath: destination.path) {
try? replaceItem(at: destination, with: documentURL, fileManager: fileManager)
protectPairingFile(at: destination, fileManager: fileManager)
}
return destination return destination
} }
if !fileManager.fileExists(atPath: destination.path) {
migrateLegacyCopy(to: destination, fileManager: fileManager) migrateLegacyCopy(to: destination, fileManager: fileManager)
}
return destination return destination
} }
static func replace(with sourceURL: URL, fileManager: FileManager = .default) throws { static func replace(with sourceURL: URL, fileManager: FileManager = .default) throws {
let destination = prepareURL(fileManager: fileManager) try fileManager.createDirectory(at: directoryURL, withIntermediateDirectories: true)
let tempURL = directoryURL.appendingPathComponent(UUID().uuidString + ".tmp") let documentsDestination = documentsURL
try? fileManager.removeItem(at: tempURL) if sourceURL.standardizedFileURL != documentsDestination.standardizedFileURL {
try fileManager.copyItem(at: sourceURL, to: tempURL) try replaceItem(at: documentsDestination, with: sourceURL, fileManager: fileManager)
defer { try? fileManager.removeItem(at: tempURL) }
if fileManager.fileExists(atPath: destination.path) {
_ = try fileManager.replaceItemAt(destination, withItemAt: tempURL)
} else {
try fileManager.moveItem(at: tempURL, to: destination)
} }
removeLegacyCopies(fileManager: fileManager) try replaceItem(at: url, with: documentsDestination, fileManager: fileManager)
protectPairingFile(at: destination, fileManager: fileManager) protectPairingFile(at: url, fileManager: fileManager)
} }
static func importFromPicker(_ sourceURL: URL, fileManager: FileManager = .default) throws { static func importFromPicker(_ sourceURL: URL, fileManager: FileManager = .default) throws {
@ -83,19 +83,33 @@ enum PairingFileStore {
private static var legacyURLs: [URL] { private static var legacyURLs: [URL] {
[ [
URL.documentsDirectory.appendingPathComponent(fileName), directoryURL.appendingPathComponent(legacyFileName),
documentsURL,
URL.documentsDirectory.appendingPathComponent(legacyFileName) URL.documentsDirectory.appendingPathComponent(legacyFileName)
] ]
} }
private static var documentsURL: URL {
URL.documentsDirectory.appendingPathComponent(fileName)
}
private static func documentSourceURL(fileManager: FileManager) -> URL? {
[documentsURL, URL.documentsDirectory.appendingPathComponent(legacyFileName)]
.filter { fileManager.fileExists(atPath: $0.path) }
.max {
let firstDate = (try? $0.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate ?? .distantPast
let secondDate = (try? $1.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate ?? .distantPast
return firstDate < secondDate
}
}
private static func migrateLegacyCopy(to destination: URL, fileManager: FileManager) { private static func migrateLegacyCopy(to destination: URL, fileManager: FileManager) {
for legacyURL in legacyURLs where fileManager.fileExists(atPath: legacyURL.path) { for legacyURL in legacyURLs where fileManager.fileExists(atPath: legacyURL.path) {
do { do {
try fileManager.moveItem(at: legacyURL, to: destination) try replaceItem(at: destination, with: legacyURL, fileManager: fileManager)
} catch { } catch {
if let data = try? Data(contentsOf: legacyURL) { if let data = try? Data(contentsOf: legacyURL) {
try? data.write(to: destination, options: .atomic) try? data.write(to: destination, options: .atomic)
try? fileManager.removeItem(at: legacyURL)
} }
} }
@ -104,6 +118,21 @@ enum PairingFileStore {
} }
} }
private static func replaceItem(at destination: URL, with source: URL, fileManager: FileManager) throws {
let temporary = destination
.deletingLastPathComponent()
.appendingPathComponent(UUID().uuidString + ".tmp")
try? fileManager.removeItem(at: temporary)
try fileManager.copyItem(at: source, to: temporary)
defer { try? fileManager.removeItem(at: temporary) }
if fileManager.fileExists(atPath: destination.path) {
_ = try fileManager.replaceItemAt(destination, withItemAt: temporary)
} else {
try fileManager.moveItem(at: temporary, to: destination)
}
}
private static func removeLegacyCopies(fileManager: FileManager) { private static func removeLegacyCopies(fileManager: FileManager) {
for legacyURL in legacyURLs where fileManager.fileExists(atPath: legacyURL.path) { for legacyURL in legacyURLs where fileManager.fileExists(atPath: legacyURL.path) {
try? fileManager.removeItem(at: legacyURL) try? fileManager.removeItem(at: legacyURL)