Source code for the WriteFreely SwiftUI app for iOS, iPadOS, and macOS
Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 

139 linhas
5.4 KiB

  1. import CoreData
  2. #if os(iOS)
  3. import UIKit
  4. #elseif os(macOS)
  5. import AppKit
  6. #endif
  7. final class LocalStorageManager {
  8. private let logger = Logging(for: String(describing: LocalStorageManager.self))
  9. public static var standard = LocalStorageManager()
  10. public let container: NSPersistentContainer
  11. private let containerName = "LocalStorageModel"
  12. private init() {
  13. container = NSPersistentContainer(name: containerName)
  14. setupStore(in: container)
  15. registerObservers()
  16. }
  17. func saveContext() {
  18. if container.viewContext.hasChanges {
  19. do {
  20. logger.log("Saving context to local store started...")
  21. try container.viewContext.save()
  22. logger.log("Context saved to local store.")
  23. } catch {
  24. logger.logCrashAndSetFlag(error: LocalStoreError.couldNotSaveContext)
  25. }
  26. }
  27. }
  28. func purgeUserCollections() throws {
  29. let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "WFACollection")
  30. let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
  31. do {
  32. logger.log("Purging user collections from local store...")
  33. try container.viewContext.executeAndMergeChanges(using: deleteRequest)
  34. logger.log("User collections purged from local store.")
  35. } catch {
  36. logger.log("\(LocalStoreError.couldNotPurgeCollections.localizedDescription)", level: .error)
  37. throw LocalStoreError.couldNotPurgeCollections
  38. }
  39. }
  40. }
  41. private extension LocalStorageManager {
  42. var oldStoreURL: URL {
  43. let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
  44. return appSupport.appendingPathComponent("LocalStorageModel.sqlite")
  45. }
  46. var sharedStoreURL: URL {
  47. let id = "group.com.abunchtell.writefreely"
  48. let groupContainer = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: id)!
  49. return groupContainer.appendingPathComponent("LocalStorageModel.sqlite")
  50. }
  51. func setupStore(in container: NSPersistentContainer) {
  52. if !FileManager.default.fileExists(atPath: oldStoreURL.path) {
  53. container.persistentStoreDescriptions.first!.url = sharedStoreURL
  54. }
  55. container.loadPersistentStores { _, error in
  56. self.logger.log("Loading local store...")
  57. if let error = error {
  58. self.logger.logCrashAndSetFlag(error: LocalStoreError.couldNotLoadStore(error.localizedDescription))
  59. }
  60. self.logger.log("Loaded local store.")
  61. }
  62. migrateStore(for: container)
  63. container.viewContext.automaticallyMergesChangesFromParent = true
  64. container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
  65. }
  66. func migrateStore(for container: NSPersistentContainer) {
  67. // Check if the shared store exists before attempting a migration — for example, in case we've already attempted
  68. // and successfully completed a migration, but the deletion of the old store failed for some reason.
  69. guard !FileManager.default.fileExists(atPath: sharedStoreURL.path) else { return }
  70. let coordinator = container.persistentStoreCoordinator
  71. // Get a reference to the old store.
  72. guard let oldStore = coordinator.persistentStore(for: oldStoreURL) else {
  73. return
  74. }
  75. // Attempt to migrate the old store over to the shared store URL.
  76. do {
  77. self.logger.log("Migrating local store to shared store...")
  78. try coordinator.migratePersistentStore(oldStore,
  79. to: sharedStoreURL,
  80. options: nil,
  81. withType: NSSQLiteStoreType)
  82. self.logger.log("Migrated local store to shared store.")
  83. } catch {
  84. logger.logCrashAndSetFlag(error: LocalStoreError.couldNotMigrateStore(error.localizedDescription))
  85. }
  86. // Attempt to delete the old store.
  87. do {
  88. logger.log("Deleting migrated local store...")
  89. try FileManager.default.removeItem(at: oldStoreURL)
  90. logger.log("Deleted migrated local store.")
  91. } catch {
  92. logger.logCrashAndSetFlag(
  93. error: LocalStoreError.couldNotDeleteStoreAfterMigration(error.localizedDescription)
  94. )
  95. }
  96. }
  97. func registerObservers() {
  98. let center = NotificationCenter.default
  99. #if os(iOS)
  100. let notification = UIApplication.willResignActiveNotification
  101. #elseif os(macOS)
  102. let notification = NSApplication.willResignActiveNotification
  103. #endif
  104. // We don't need to worry about removing this observer because we're targeting iOS 9+ / macOS 10.11+; the
  105. // system will clean this up the next time it would be posted to.
  106. // See: https://developer.apple.com/documentation/foundation/notificationcenter/1413994-removeobserver
  107. // And: https://developer.apple.com/documentation/foundation/notificationcenter/1407263-removeobserver
  108. // swiftlint:disable:next discarded_notification_center_observer
  109. center.addObserver(forName: notification, object: nil, queue: nil, using: self.saveContextOnResignActive)
  110. }
  111. func saveContextOnResignActive(_ notification: Notification) {
  112. saveContext()
  113. }
  114. }