Log fatal crashes and present alert on next launch

This commit is contained in:
Angelo Stavrow 2022-06-18 08:53:25 -04:00
parent a43bd801a8
commit 5a1b400333
No known key found for this signature in database
GPG Key ID: 1A49C7064E060EEE
4 changed files with 72 additions and 8 deletions

View File

@ -13,6 +13,8 @@ enum WFDefaults {
static let automaticallyChecksForUpdates = "automaticallyChecksForUpdates"
static let subscribeToBetaUpdates = "subscribeToBetaUpdates"
#endif
static let didHaveFatalError = "didHaveFatalError"
static let fatalErrorDescription = "fatalErrorDescription"
}
extension UserDefaults {

View File

@ -1,4 +1,5 @@
import CoreData
import os
#if os(iOS)
import UIKit
@ -21,9 +22,11 @@ final class LocalStorageManager {
func saveContext() {
if container.viewContext.hasChanges {
do {
Self.logger.info("Saving context to local store started...")
try container.viewContext.save()
Self.logger.notice("Context saved to local store.")
} catch {
fatalError(LocalStoreError.couldNotSaveContext.localizedDescription)
logCrashAndSetFlag(error: LocalStoreError.couldNotSaveContext)
}
}
}
@ -33,8 +36,11 @@ final class LocalStorageManager {
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
Self.logger.info("Purging user collections from local store...")
try container.viewContext.executeAndMergeChanges(using: deleteRequest)
Self.logger.notice("User collections purged from local store.")
} catch {
Self.logger.error("\(LocalStoreError.couldNotPurgeCollections.localizedDescription)")
throw LocalStoreError.couldNotPurgeCollections
}
}
@ -60,9 +66,11 @@ private extension LocalStorageManager {
}
container.loadPersistentStores { _, error in
Self.logger.info("Loading local store...")
if let error = error {
fatalError(LocalStoreError.couldNotLoadStore(error.localizedDescription).localizedDescription)
self.logCrashAndSetFlag(error: LocalStoreError.couldNotLoadStore(error.localizedDescription))
}
Self.logger.notice("Loaded local store.")
}
migrateStore(for: container)
container.viewContext.automaticallyMergesChangesFromParent = true
@ -83,21 +91,23 @@ private extension LocalStorageManager {
// Attempt to migrate the old store over to the shared store URL.
do {
Self.logger.info("Migrating local store to shared store...")
try coordinator.migratePersistentStore(oldStore,
to: sharedStoreURL,
options: nil,
withType: NSSQLiteStoreType)
Self.logger.notice("Migrated local store to shared store.")
} catch {
fatalError(LocalStoreError.couldNotMigrateStore(error.localizedDescription).localizedDescription)
logCrashAndSetFlag(error: LocalStoreError.couldNotMigrateStore(error.localizedDescription))
}
// Attempt to delete the old store.
do {
Self.logger.info("Deleting migrated local store...")
try FileManager.default.removeItem(at: oldStoreURL)
Self.logger.notice("Deleted migrated local store.")
} catch {
fatalError(
LocalStoreError.couldNotDeleteStoreAfterMigration(error.localizedDescription).localizedDescription
)
logCrashAndSetFlag(error: LocalStoreError.couldNotDeleteStoreAfterMigration(error.localizedDescription))
}
}
@ -123,3 +133,20 @@ private extension LocalStorageManager {
}
}
private extension LocalStorageManager {
private static let logger = Logger(
subsystem: Bundle.main.bundleIdentifier!,
category: String(describing: LocalStorageManager.self)
)
private func logCrashAndSetFlag(error: Error) {
let errorDescription = error.localizedDescription
UserDefaults.shared.set(true, forKey: WFDefaults.didHaveFatalError)
UserDefaults.shared.set(errorDescription, forKey: WFDefaults.fatalErrorDescription)
Self.logger.critical("\(errorDescription)")
fatalError(errorDescription)
}
}

View File

@ -1,5 +1,6 @@
import SwiftUI
import CoreData
import os
class CollectionListModel: NSObject, ObservableObject {
@Published var list: [WFACollection] = []
@ -16,11 +17,12 @@ class CollectionListModel: NSObject, ObservableObject {
collectionsController.delegate = self
do {
Self.logger.info("Fetching collections from local store...")
try collectionsController.performFetch()
list = collectionsController.fetchedObjects ?? []
Self.logger.notice("Fetched collections from local store.")
} catch {
// FIXME: Errors cannot be thrown out of the CollectionListView property initializer
fatalError(LocalStoreError.couldNotFetchCollections.localizedDescription)
logCrashAndSetFlag(error: LocalStoreError.couldNotFetchCollections)
}
}
}
@ -32,6 +34,21 @@ extension CollectionListModel: NSFetchedResultsControllerDelegate {
}
}
extension CollectionListModel {
private static let logger = Logger(
subsystem: Bundle.main.bundleIdentifier!,
category: String(describing: CollectionListModel.self)
)
private func logCrashAndSetFlag(error: Error) {
let errorDescription = error.localizedDescription
UserDefaults.shared.set(true, forKey: WFDefaults.didHaveFatalError)
UserDefaults.shared.set(errorDescription, forKey: WFDefaults.fatalErrorDescription)
Self.logger.critical("\(errorDescription)")
fatalError(errorDescription)
}
}
extension WFACollection {
static var collectionsFetchRequest: NSFetchRequest<WFACollection> {
let request: NSFetchRequest<WFACollection> = WFACollection.createFetchRequest()

View File

@ -30,6 +30,8 @@ struct WriteFreely_MultiPlatformApp: App {
@State private var selectedTab = 0
#endif
@State private var didCrash = UserDefaults.shared.bool(forKey: WFDefaults.didHaveFatalError)
var body: some Scene {
WindowGroup {
ContentView()
@ -48,6 +50,22 @@ struct WriteFreely_MultiPlatformApp: App {
}
}
})
.alert(isPresented: $didCrash) {
// TODO: - Confirm copy for this alert
Alert(
title: Text("Crash Detected"),
message: Text(
UserDefaults.shared.object(forKey: WFDefaults.fatalErrorDescription) as? String ??
"Something went horribly wrong!"
),
dismissButton: .default(
Text("Dismiss"), action: {
UserDefaults.shared.set(false, forKey: WFDefaults.didHaveFatalError)
UserDefaults.shared.removeObject(forKey: WFDefaults.fatalErrorDescription)
}
)
)
}
.withErrorHandling()
.environmentObject(model)
.environment(\.managedObjectContext, LocalStorageManager.standard.container.viewContext)