passepartout-apple/Library/Sources/CommonUtils/Business/CoreDataPersistentStore.swift

150 lines
5.1 KiB
Swift
Raw Normal View History

2022-04-12 13:09:14 +00:00
//
2023-05-24 16:19:47 +00:00
// CoreDataPersistentStore.swift
2022-04-12 13:09:14 +00:00
// Passepartout
//
// Created by Davide De Rosa on 3/14/22.
2024-01-14 13:34:21 +00:00
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
2022-04-12 13:09:14 +00:00
//
// https://github.com/passepartoutvpn
//
// This file is part of Passepartout.
//
// Passepartout is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Passepartout is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Passepartout. If not, see <http://www.gnu.org/licenses/>.
//
import CloudKit
2022-04-12 13:09:14 +00:00
import Combine
import CoreData
import Foundation
2022-04-12 13:09:14 +00:00
public protocol CoreDataPersistentStoreLogger: Sendable {
2024-09-23 13:02:26 +00:00
func debug(_ msg: String)
func warning(_ msg: String)
}
public final class CoreDataPersistentStore: Sendable {
private let logger: CoreDataPersistentStoreLogger?
2024-09-23 13:02:26 +00:00
2022-04-12 13:09:14 +00:00
private let container: NSPersistentContainer
2023-03-17 20:55:47 +00:00
2024-09-23 13:02:26 +00:00
public convenience init(
logger: CoreDataPersistentStoreLogger? = nil,
2024-09-23 13:02:26 +00:00
containerName: String,
baseURL: URL? = nil,
2024-09-23 13:02:26 +00:00
model: NSManagedObjectModel,
cloudKitIdentifier: String?,
author: String?
) {
2023-05-24 16:19:47 +00:00
let container: NSPersistentContainer
if let cloudKitIdentifier {
2023-05-24 16:19:47 +00:00
container = NSPersistentCloudKitContainer(name: containerName, managedObjectModel: model)
logger?.debug("Set up CloudKit container (\(cloudKitIdentifier)): \(containerName)")
2023-05-24 16:19:47 +00:00
} else {
container = NSPersistentContainer(name: containerName, managedObjectModel: model)
logger?.debug("Set up local container: \(containerName)")
}
if let baseURL {
let url = baseURL.appending(component: "\(containerName).sqlite")
container.persistentStoreDescriptions = [.init(url: url)]
2023-05-24 16:19:47 +00:00
}
2024-09-23 13:02:26 +00:00
self.init(
logger: logger,
container: container,
cloudKitIdentifier: cloudKitIdentifier,
author: author
)
2022-04-12 13:09:14 +00:00
}
2024-09-23 13:02:26 +00:00
private init(
logger: CoreDataPersistentStoreLogger?,
2024-09-23 13:02:26 +00:00
container: NSPersistentContainer,
cloudKitIdentifier: String?,
author: String?
) {
self.logger = logger
2022-04-12 13:09:14 +00:00
self.container = container
guard let desc = container.persistentStoreDescriptions.first else {
2024-09-23 13:02:26 +00:00
fatalError("Unable to read persistent store description")
2022-04-12 13:09:14 +00:00
}
logger?.debug("Container description: \(desc)")
2022-04-12 13:09:14 +00:00
2023-12-16 19:58:54 +00:00
// optional container identifier for CloudKit, first in entitlements otherwise
if let cloudKitIdentifier {
desc.cloudKitContainerOptions = .init(containerIdentifier: cloudKitIdentifier)
}
// set this even for local container, to avoid readonly mode in case
// container was formerly created with CloudKit option
desc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
2022-04-12 13:09:14 +00:00
// report remote notifications (do this BEFORE loadPersistentStores)
//
// https://stackoverflow.com/a/69507329/784615
// desc.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
container.loadPersistentStores {
if let error = $1 {
2024-09-23 13:02:26 +00:00
fatalError("Unable to load persistent store: \(error)")
2022-04-12 13:09:14 +00:00
}
}
container.viewContext.mergePolicy = NSMergePolicy.mergeByPropertyObjectTrump
container.viewContext.automaticallyMergesChangesFromParent = true
if let author {
logger?.debug("Setting transaction author: \(author)")
2022-04-12 13:09:14 +00:00
container.viewContext.transactionAuthor = author
}
}
}
extension CoreDataPersistentStore {
public var context: NSManagedObjectContext {
container.viewContext
}
public func backgroundContext() -> NSManagedObjectContext {
container.newBackgroundContext()
}
}
2023-03-17 20:55:47 +00:00
// MARK: Development
extension CoreDataPersistentStore {
2022-05-05 07:18:53 +00:00
public var containerURLs: [URL]? {
guard let url = container.persistentStoreDescriptions.first?.url else {
return nil
}
return [
url,
url.deletingPathExtension().appendingPathExtension("sqlite-shm"),
url.deletingPathExtension().appendingPathExtension("sqlite-wal")
]
}
2023-03-17 20:55:47 +00:00
2022-04-12 13:09:14 +00:00
public func truncate() {
let coordinator = container.persistentStoreCoordinator
container.persistentStoreDescriptions.forEach {
do {
try $0.url.map {
try coordinator.destroyPersistentStore(at: $0, ofType: NSSQLiteStoreType)
try coordinator.addPersistentStore(ofType: NSSQLiteStoreType, configurationName: nil, at: $0, options: nil)
}
} catch {
logger?.warning("Unable to truncate persistent store: \(error)")
2022-04-12 13:09:14 +00:00
}
}
}
}