2020-09-01 19:55:11 +00:00
|
|
|
|
import CoreData
|
|
|
|
|
|
2020-09-02 17:17:56 +00:00
|
|
|
|
#if os(iOS)
|
|
|
|
|
import UIKit
|
|
|
|
|
#elseif os(macOS)
|
2020-09-01 19:55:11 +00:00
|
|
|
|
import AppKit
|
|
|
|
|
#endif
|
|
|
|
|
|
2021-10-08 21:07:06 +00:00
|
|
|
|
final class LocalStorageManager {
|
2021-10-15 19:06:35 +00:00
|
|
|
|
|
2022-07-28 11:47:39 +00:00
|
|
|
|
private let logger = Logging(for: String(describing: LocalStorageManager.self))
|
|
|
|
|
|
2021-10-08 21:07:06 +00:00
|
|
|
|
public static var standard = LocalStorageManager()
|
2021-10-08 21:15:38 +00:00
|
|
|
|
public let container: NSPersistentContainer
|
2021-10-15 19:06:35 +00:00
|
|
|
|
private let containerName = "LocalStorageModel"
|
2021-10-08 21:07:06 +00:00
|
|
|
|
|
2021-10-15 19:06:35 +00:00
|
|
|
|
private init() {
|
|
|
|
|
container = NSPersistentContainer(name: containerName)
|
|
|
|
|
setupStore(in: container)
|
|
|
|
|
registerObservers()
|
2020-09-02 14:36:28 +00:00
|
|
|
|
}
|
2020-09-01 19:55:11 +00:00
|
|
|
|
|
2020-09-02 14:36:28 +00:00
|
|
|
|
func saveContext() {
|
2021-10-08 21:15:38 +00:00
|
|
|
|
if container.viewContext.hasChanges {
|
2020-09-02 14:36:28 +00:00
|
|
|
|
do {
|
2022-07-28 11:47:39 +00:00
|
|
|
|
logger.log("Saving context to local store started...")
|
2021-10-08 21:15:38 +00:00
|
|
|
|
try container.viewContext.save()
|
2022-07-28 11:47:39 +00:00
|
|
|
|
logger.log("Context saved to local store.")
|
2020-09-02 14:36:28 +00:00
|
|
|
|
} catch {
|
2022-07-28 11:47:39 +00:00
|
|
|
|
logger.logCrashAndSetFlag(error: LocalStoreError.couldNotSaveContext)
|
2020-09-01 19:55:11 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-10 14:07:59 +00:00
|
|
|
|
|
2022-07-27 13:56:32 +00:00
|
|
|
|
func purgeUserCollections() throws {
|
2020-09-10 14:07:59 +00:00
|
|
|
|
let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "WFACollection")
|
|
|
|
|
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
|
|
|
|
|
|
|
|
|
|
do {
|
2022-07-28 11:47:39 +00:00
|
|
|
|
logger.log("Purging user collections from local store...")
|
2021-10-08 21:15:38 +00:00
|
|
|
|
try container.viewContext.executeAndMergeChanges(using: deleteRequest)
|
2022-07-28 11:47:39 +00:00
|
|
|
|
logger.log("User collections purged from local store.")
|
2020-09-10 14:07:59 +00:00
|
|
|
|
} catch {
|
2022-07-28 11:47:39 +00:00
|
|
|
|
logger.log("\(LocalStoreError.couldNotPurgeCollections.localizedDescription)", level: .error)
|
2022-07-27 13:56:32 +00:00
|
|
|
|
throw LocalStoreError.couldNotPurgeCollections
|
2020-09-10 14:07:59 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-10-08 21:16:01 +00:00
|
|
|
|
|
2021-10-15 19:06:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private extension LocalStorageManager {
|
|
|
|
|
|
|
|
|
|
var oldStoreURL: URL {
|
|
|
|
|
let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
|
|
|
|
|
return appSupport.appendingPathComponent("LocalStorageModel.sqlite")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var sharedStoreURL: URL {
|
|
|
|
|
let id = "group.com.abunchtell.writefreely"
|
|
|
|
|
let groupContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: id)!
|
|
|
|
|
return groupContainer.appendingPathComponent("LocalStorageModel.sqlite")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func setupStore(in container: NSPersistentContainer) {
|
|
|
|
|
if !FileManager.default.fileExists(atPath: oldStoreURL.path) {
|
|
|
|
|
container.persistentStoreDescriptions.first!.url = sharedStoreURL
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-02 12:04:50 +00:00
|
|
|
|
container.loadPersistentStores { _, error in
|
2022-07-28 11:47:39 +00:00
|
|
|
|
self.logger.log("Loading local store...")
|
2021-10-15 19:06:35 +00:00
|
|
|
|
if let error = error {
|
2022-07-28 11:47:39 +00:00
|
|
|
|
self.logger.logCrashAndSetFlag(error: LocalStoreError.couldNotLoadStore(error.localizedDescription))
|
2021-10-15 19:06:35 +00:00
|
|
|
|
}
|
2022-07-28 11:47:39 +00:00
|
|
|
|
self.logger.log("Loaded local store.")
|
2021-10-15 19:06:35 +00:00
|
|
|
|
}
|
|
|
|
|
migrateStore(for: container)
|
|
|
|
|
container.viewContext.automaticallyMergesChangesFromParent = true
|
|
|
|
|
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-08 21:16:01 +00:00
|
|
|
|
func migrateStore(for container: NSPersistentContainer) {
|
2021-10-15 19:06:35 +00:00
|
|
|
|
// Check if the shared store exists before attempting a migration — for example, in case we've already attempted
|
|
|
|
|
// and successfully completed a migration, but the deletion of the old store failed for some reason.
|
|
|
|
|
guard !FileManager.default.fileExists(atPath: sharedStoreURL.path) else { return }
|
|
|
|
|
|
2021-10-08 21:16:01 +00:00
|
|
|
|
let coordinator = container.persistentStoreCoordinator
|
|
|
|
|
|
2021-10-15 19:06:35 +00:00
|
|
|
|
// Get a reference to the old store.
|
2021-10-08 21:16:01 +00:00
|
|
|
|
guard let oldStore = coordinator.persistentStore(for: oldStoreURL) else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-15 19:06:35 +00:00
|
|
|
|
// Attempt to migrate the old store over to the shared store URL.
|
2021-10-08 21:16:01 +00:00
|
|
|
|
do {
|
2022-07-28 11:47:39 +00:00
|
|
|
|
self.logger.log("Migrating local store to shared store...")
|
2021-10-08 21:16:01 +00:00
|
|
|
|
try coordinator.migratePersistentStore(oldStore,
|
|
|
|
|
to: sharedStoreURL,
|
|
|
|
|
options: nil,
|
|
|
|
|
withType: NSSQLiteStoreType)
|
2022-07-28 11:47:39 +00:00
|
|
|
|
self.logger.log("Migrated local store to shared store.")
|
2021-10-08 21:16:01 +00:00
|
|
|
|
} catch {
|
2022-07-28 11:47:39 +00:00
|
|
|
|
logger.logCrashAndSetFlag(error: LocalStoreError.couldNotMigrateStore(error.localizedDescription))
|
2021-10-08 21:16:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-10-15 19:06:35 +00:00
|
|
|
|
// Attempt to delete the old store.
|
2021-10-08 21:16:01 +00:00
|
|
|
|
do {
|
2022-07-28 11:47:39 +00:00
|
|
|
|
logger.log("Deleting migrated local store...")
|
2021-10-08 21:16:01 +00:00
|
|
|
|
try FileManager.default.removeItem(at: oldStoreURL)
|
2022-07-28 11:47:39 +00:00
|
|
|
|
logger.log("Deleted migrated local store.")
|
2021-10-08 21:16:01 +00:00
|
|
|
|
} catch {
|
2022-07-28 11:47:39 +00:00
|
|
|
|
logger.logCrashAndSetFlag(
|
|
|
|
|
error: LocalStoreError.couldNotDeleteStoreAfterMigration(error.localizedDescription)
|
2022-07-27 13:56:32 +00:00
|
|
|
|
)
|
2021-10-08 21:16:01 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-09 16:39:23 +00:00
|
|
|
|
|
2021-10-15 19:06:35 +00:00
|
|
|
|
func registerObservers() {
|
|
|
|
|
let center = NotificationCenter.default
|
|
|
|
|
|
|
|
|
|
#if os(iOS)
|
|
|
|
|
let notification = UIApplication.willResignActiveNotification
|
|
|
|
|
#elseif os(macOS)
|
|
|
|
|
let notification = NSApplication.willResignActiveNotification
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// We don't need to worry about removing this observer because we're targeting iOS 9+ / macOS 10.11+; the
|
|
|
|
|
// system will clean this up the next time it would be posted to.
|
|
|
|
|
// See: https://developer.apple.com/documentation/foundation/notificationcenter/1413994-removeobserver
|
|
|
|
|
// And: https://developer.apple.com/documentation/foundation/notificationcenter/1407263-removeobserver
|
|
|
|
|
// swiftlint:disable:next discarded_notification_center_observer
|
|
|
|
|
center.addObserver(forName: notification, object: nil, queue: nil, using: self.saveContextOnResignActive)
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-09 16:39:23 +00:00
|
|
|
|
func saveContextOnResignActive(_ notification: Notification) {
|
|
|
|
|
saveContext()
|
|
|
|
|
}
|
2021-10-15 19:06:35 +00:00
|
|
|
|
|
2020-09-01 19:55:11 +00:00
|
|
|
|
}
|