diff --git a/Localizable.xcstrings b/Localizable.xcstrings index 66552059..edbdda71 100644 --- a/Localizable.xcstrings +++ b/Localizable.xcstrings @@ -2204,6 +2204,7 @@ } }, "Backup Database" : { + "extractionState" : "stale", "localizations" : { "sr" : { "stringUnit" : { @@ -24694,6 +24695,7 @@ } }, "restore" : { + "extractionState" : "stale", "localizations" : { "de" : { "stringUnit" : { diff --git a/Meshtastic/Helpers/BLEManager.swift b/Meshtastic/Helpers/BLEManager.swift index 89f57aa8..7077a26d 100644 --- a/Meshtastic/Helpers/BLEManager.swift +++ b/Meshtastic/Helpers/BLEManager.swift @@ -690,24 +690,7 @@ class BLEManager: NSObject, CBPeripheralDelegate, MqttClientProxyManagerDelegate connectedPeripheral.longName = myInfo?.bleName ?? "unknown".localized let newConnection = Int64(UserDefaults.preferredPeripheralNum) != Int64(decodedInfo.myInfo.myNodeNum) if newConnection { - let container = NSPersistentContainer(name: "Meshtastic") - if let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { - let databasePath = url.appendingPathComponent("backup") - .appendingPathComponent("\(UserDefaults.preferredPeripheralNum)") - .appendingPathComponent("Meshtastic.sqlite") - if FileManager.default.fileExists(atPath: databasePath.path) { - do { - disconnectPeripheral(reconnect: false) - try container.restorePersistentStore(from: databasePath) - UserDefaults.preferredPeripheralNum = Int(myInfo?.myNodeNum ?? 0) - context.refreshAllObjects() - Logger.data.notice("🗂️ Restored Core data for /\(UserDefaults.preferredPeripheralNum, privacy: .public)") - connectTo(peripheral: peripheral) - } catch { - Logger.data.error("🗂️ Restore Core data copy error: \(error, privacy: .public)") - } - } - } + // Onboard a new device connection here } } tryClearExistingChannels() diff --git a/Meshtastic/Persistence/Persistence.swift b/Meshtastic/Persistence/Persistence.swift index 70e9f898..e86278e3 100644 --- a/Meshtastic/Persistence/Persistence.swift +++ b/Meshtastic/Persistence/Persistence.swift @@ -103,123 +103,4 @@ extension NSPersistentContainer { case invalidSource(String) } - /// Restore backup persistent stores located in the directory referenced by `backupURL`. - /// **Be very careful with this**. To restore a persistent store, the current persistent store must be removed from the container. When that happens, **all currently loaded Core Data objects** will become invalid. Using them after restoring will cause your app to crash. - /// When calling this method you **must** ensure that you do not continue to use any previously fetched managed objects or existing fetched results controllers. **If this method does not throw, that does not mean your app is safe.** You need to take extra steps to prevent crashes. The details vary depending on the nature of your app. - /// - Parameter backupURL: A file URL containing backup copies of all currently loaded persistent stores. - /// - Throws: `CopyPersistentStoreError` in various situations. - /// - Returns: Nothing. If no errors are thrown, the restore is complete. - func restorePersistentStore(from backupURL: URL) throws { - guard backupURL.isFileURL else { - throw CopyPersistentStoreErrors.invalidSource("Backup URL must be a file URL") - } - - var isDirectory: ObjCBool = false - if FileManager.default.fileExists(atPath: backupURL.path, isDirectory: &isDirectory) { - if !isDirectory.boolValue { - throw CopyPersistentStoreErrors.invalidSource("Source URL must be a directory") - } - } else { - throw CopyPersistentStoreErrors.invalidSource("Source URL must exist") - } - - for persistentStoreDescription in persistentStoreDescriptions { - guard let loadedStoreURL = persistentStoreDescription.url else { - continue - } - let backupStoreURL = backupURL.appendingPathComponent(loadedStoreURL.lastPathComponent) - guard FileManager.default.fileExists(atPath: backupStoreURL.path) else { - throw CopyPersistentStoreErrors.invalidSource("Missing backup store for \(backupStoreURL)") - } - do { - let storeOptions = persistentStoreDescription.options - let configurationName = persistentStoreDescription.configuration - let storeType = persistentStoreDescription.type - // Replace the current store with the backup copy. This has a side effect of removing the current store from the Core Data stack. - // When restoring, it's necessary to use the current persistent store coordinator. - try persistentStoreCoordinator.replacePersistentStore(at: loadedStoreURL, destinationOptions: storeOptions, withPersistentStoreFrom: backupStoreURL, sourceOptions: storeOptions, ofType: storeType) - // Add the persistent store at the same location we've been using, because it was removed in the previous step. - try persistentStoreCoordinator.addPersistentStore(ofType: storeType, configurationName: configurationName, at: loadedStoreURL, options: storeOptions) - } catch { - throw CopyPersistentStoreErrors.copyStoreError("Could not restore: \(error.localizedDescription)") - } - } - } - - /// Copy all loaded persistent stores to a new directory. Each currently loaded file-based persistent store will be copied (including journal files, external binary storage, and anything else Core Data needs) into the destination directory to a persistent store with the same name and type as the existing store. In-memory stores, if any, are skipped. - /// - Parameters: - /// - destinationURL: Destination for new persistent store files. Must be a file URL. If `overwriting` is `false` and `destinationURL` exists, it must be a directory. - /// - overwriting: If `true`, any existing copies of the persistent store will be replaced or updated. If `false`, existing copies will not be changed or remoted. When this is `false`, the destination persistent store file must not already exist. - /// - Throws: `CopyPersistentStoreError` - /// - Returns: Nothing. If no errors are thrown, all loaded persistent stores will be copied to the destination directory. - func copyPersistentStores(to destinationURL: URL, overwriting: Bool = false) throws { - - guard !destinationURL.relativeString.contains("/0/") else { - throw CopyPersistentStoreErrors.invalidDestination("Invalid 0 Node Id") - } - - guard destinationURL.isFileURL else { - throw CopyPersistentStoreErrors.invalidDestination("Destination URL must be a file URL") - } - // If the destination exists and we aren't overwriting it, then it must be a directory. (If we are overwriting, we'll remove it anyway, so it doesn't matter whether it's a directory). - var isDirectory: ObjCBool = false - if !overwriting && FileManager.default.fileExists(atPath: destinationURL.path, isDirectory: &isDirectory) { - if !isDirectory.boolValue { - throw CopyPersistentStoreErrors.invalidDestination("Destination URL must be a directory") - } - // Don't check if destination stores exist in the destination dir, that comes later on a per-store basis. - } - // If we're overwriting, remove the destination. - if overwriting && FileManager.default.fileExists(atPath: destinationURL.path) { - do { - try FileManager.default.removeItem(at: destinationURL) - } catch { - throw CopyPersistentStoreErrors.destinationNotRemoved("Can't overwrite destination at \(destinationURL)") - } - } - // Create the destination directory - do { - try FileManager.default.createDirectory(at: destinationURL, withIntermediateDirectories: true, attributes: nil) - } catch { - throw CopyPersistentStoreErrors.destinationError("Could not create destination directory at \(destinationURL)") - } - - for persistentStoreDescription in persistentStoreDescriptions { - guard let storeURL = persistentStoreDescription.url else { - continue - } - guard persistentStoreDescription.type != NSInMemoryStoreType else { - continue - } - let destinationStoreURL = destinationURL.appendingPathComponent(storeURL.lastPathComponent) - - if !overwriting && FileManager.default.fileExists(atPath: destinationStoreURL.path) { - // If the destination exists, the replacePersistentStore call will update it in place. That's fine unless we're not overwriting. - throw CopyPersistentStoreErrors.destinationError("Destination already exists at \(destinationStoreURL)") - } - do { - // Replace an existing backup, if any, with a new one with the same options and type. This doesn't affect the current Core Data stack. - // The function name says "replace", but it works if there's nothing at the destination yet. In that case it creates a new persistent store. - // Note that for backup, it doesn't matter if the persistent store coordinator is the one currently in use or a different one. It could be a class function, for this use. - try persistentStoreCoordinator.replacePersistentStore(at: destinationStoreURL, destinationOptions: persistentStoreDescription.options, withPersistentStoreFrom: storeURL, sourceOptions: persistentStoreDescription.options, ofType: persistentStoreDescription.type) - /// Cleanup extra files - let directory = destinationStoreURL.deletingLastPathComponent() - /// Delete -wal file - do { - try FileManager.default.removeItem(at: directory.appendingPathComponent("Meshtastic.sqlite-wal")) - /// Delete -shm file - do { - try FileManager.default.removeItem(at: directory.appendingPathComponent("Meshtastic.sqlite-shm")) - } catch { - Logger.services.error("🗄 Error Deleting Meshtastic.sqlite-shm file \(error, privacy: .public)") - } - } catch { - Logger.services.error("🗄 Error Deleting Meshtastic.sqlite-wal file \(error, privacy: .public)") - } - } catch { - Logger.services.error("🗄 Error Deleting Meshtastic.sqlite file \(error, privacy: .public)") - throw CopyPersistentStoreErrors.copyStoreError("\(error.localizedDescription)") - } - } - } } diff --git a/Meshtastic/Views/Bluetooth/Connect.swift b/Meshtastic/Views/Bluetooth/Connect.swift index 09af4418..72b1a6ae 100644 --- a/Meshtastic/Views/Bluetooth/Connect.swift +++ b/Meshtastic/Views/Bluetooth/Connect.swift @@ -214,18 +214,6 @@ struct Connect: View { if let connectedPeripheral = bleManager.connectedPeripheral, connectedPeripheral.peripheral.state == CBPeripheralState.connected { bleManager.disconnectPeripheral() } - let container = NSPersistentContainer(name: "Meshtastic") - guard let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { - Logger.data.error("nil File path for back") - return - } - do { - try container.copyPersistentStores(to: url.appendingPathComponent("backup").appendingPathComponent("\(UserDefaults.preferredPeripheralNum)"), overwriting: true) - Logger.data.notice("🗂️ Made a core data backup to backup/\(UserDefaults.preferredPeripheralNum)") - - } catch { - Logger.data.error("🗂️ Core data backup copy error: \(error, privacy: .public)") - } clearCoreDataDatabase(context: context, includeRoutes: false) } UserDefaults.preferredPeripheralId = selectedPeripherialId diff --git a/Meshtastic/Views/Settings/AppData.swift b/Meshtastic/Views/Settings/AppData.swift index 3f1ebf0e..f1777989 100644 --- a/Meshtastic/Views/Settings/AppData.swift +++ b/Meshtastic/Views/Settings/AppData.swift @@ -25,34 +25,6 @@ struct AppData: View { GPSStatus() } Divider() - Button(action: { - let container = NSPersistentContainer(name: "Meshtastic") - guard let url = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { - Logger.data.error("nil File path for back") - return - } - do { - try container.copyPersistentStores(to: url.appendingPathComponent("backup").appendingPathComponent("\(UserDefaults.preferredPeripheralNum)"), overwriting: true) - loadFiles() - Logger.data.notice("🗂️ Made a core data backup to backup/\(UserDefaults.preferredPeripheralNum)") - } catch { - Logger.data.error("🗂️ Core data backup copy error: \(error, privacy: .public)") - } - }) { - Label { - Text("Backup Database") - .font(idiom == .phone ? .callout : .title) - } icon: { - Image(systemName: "cylinder.split.1x2") - .symbolRenderingMode(.hierarchical) - .font(idiom == .phone ? .callout : .title) - .frame(width: 35) - } - } - .buttonStyle(.bordered) - .buttonBorderShape(.capsule) - .controlSize(.large) - Divider() } List(files, id: \.self) { file in @@ -62,20 +34,6 @@ struct AppData: View { Label { Text("Node Core Data Backup \(file.pathComponents[(idiom == .phone || idiom == .pad) ? 9 : 10])/\(file.lastPathComponent) - \(file.creationDate?.formatted() ?? "") - \(file.fileSizeString)") .swipeActions { - Button(role: .none) { - bleManager.disconnectPeripheral(reconnect: false) - let container = NSPersistentContainer(name: "Meshtastic") - do { - try container.restorePersistentStore(from: file.absoluteURL) - UserDefaults.preferredPeripheralId = "" - UserDefaults.preferredPeripheralNum = Int(file.pathComponents[(idiom == .phone || idiom == .pad) ? 9 : 10]) ?? 0 - Logger.data.notice("🗂️ Restored a core data backup to backup/\(UserDefaults.preferredPeripheralNum, privacy: .public)") - } catch { - Logger.data.error("🗂️ Core data restore copy error: \(error, privacy: .public)") - } - } label: { - Label("restore", systemImage: "arrow.counterclockwise") - } Button(role: .destructive) { do { try FileManager.default.removeItem(at: file)