65 lines
2.7 KiB
Swift
65 lines
2.7 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import Foundation
|
|
import SwiftData
|
|
|
|
#if os(macOS)
|
|
import ObjectiveC
|
|
#endif
|
|
|
|
/// The complete, versioned store shared by both apps. Platform entry points
|
|
/// choose storage and own the container lifetime, never the model set.
|
|
@MainActor
|
|
public enum TermindPersistence {
|
|
#if os(macOS)
|
|
private final class WeakStoreLock {
|
|
weak var value: PersistentStoreLock?
|
|
init(_ value: PersistentStoreLock) { self.value = value }
|
|
}
|
|
|
|
private static var storeLocks: [URL: WeakStoreLock] = [:]
|
|
private static var storeLockAssociation: UInt8 = 0
|
|
#endif
|
|
|
|
// Local schema migrations do not change the independently versioned CloudKit envelopes.
|
|
public static let schema = Schema(versionedSchema: TermindSchemaV59.self)
|
|
|
|
public static func makeContainer(inMemory: Bool = false, url: URL? = nil) throws -> ModelContainer {
|
|
precondition(!inMemory || url == nil, "An in-memory store cannot also have a file URL")
|
|
let configuration: ModelConfiguration
|
|
if let url {
|
|
configuration = ModelConfiguration("Termind", schema: schema, url: url, cloudKitDatabase: .none)
|
|
} else {
|
|
configuration = ModelConfiguration(
|
|
"Termind", schema: schema, isStoredInMemoryOnly: inMemory, cloudKitDatabase: .none)
|
|
}
|
|
// Acquire before SwiftData can open or migrate the database. Containers in
|
|
// this process share a lease; another process must wait for all of them.
|
|
#if os(macOS)
|
|
let lock = try inMemory ? nil : storeLock(for: configuration.url)
|
|
#endif
|
|
let container = try ModelContainer(
|
|
for: schema, migrationPlan: TermindMigrationPlan.self, configurations: [configuration])
|
|
#if os(macOS)
|
|
if let lock {
|
|
objc_setAssociatedObject(container, &storeLockAssociation, lock, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
}
|
|
#endif
|
|
return container
|
|
}
|
|
|
|
#if os(macOS)
|
|
private static func storeLock(for url: URL) throws -> PersistentStoreLock {
|
|
try FileManager.default.createDirectory(
|
|
at: url.deletingLastPathComponent(), withIntermediateDirectories: true)
|
|
let canonicalURL = url.resolvingSymlinksInPath().standardizedFileURL
|
|
storeLocks = storeLocks.filter { $0.value.value != nil }
|
|
if let existing = storeLocks[canonicalURL]?.value { return existing }
|
|
let lock = try PersistentStoreLock(storeURL: canonicalURL)
|
|
storeLocks[canonicalURL] = WeakStoreLock(lock)
|
|
return lock
|
|
}
|
|
#endif
|
|
}
|