mirror of https://github.com/oxen-io/session-ios
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
152 lines
5.2 KiB
Swift
152 lines
5.2 KiB
Swift
// Copyright © 2022 Rangeproof Pty Ltd. All rights reserved.
|
|
//
|
|
// stringlint:disable
|
|
|
|
import Foundation
|
|
import GRDB
|
|
|
|
// MARK: - Cache
|
|
|
|
internal extension Cache {
|
|
static let transactionObserver: CacheConfig<TransactionObserverCacheType, TransactionObserverImmutableCacheType> = Dependencies.create(
|
|
identifier: "transactionObserver",
|
|
createInstance: { dependencies in Storage.TransactionObserverCache(using: dependencies) },
|
|
mutableInstance: { $0 },
|
|
immutableInstance: { $0 }
|
|
)
|
|
}
|
|
|
|
public extension Database {
|
|
func makeFTS5Pattern<T>(rawPattern: String, forTable table: T.Type) throws -> FTS5Pattern where T: TableRecord, T: ColumnExpressible {
|
|
return try makeFTS5Pattern(rawPattern: rawPattern, forTable: table.databaseTableName)
|
|
}
|
|
|
|
/// This is a custom implementation of the `afterNextTransaction` method which executes the closures within their own
|
|
/// transactions to allow for nesting of 'afterNextTransaction' actions
|
|
///
|
|
/// **Note:** GRDB doesn't notify read-only transactions to transaction observers
|
|
func afterNextTransactionNested(
|
|
using dependencies: Dependencies,
|
|
onCommit: @escaping (Database) -> Void,
|
|
onRollback: @escaping (Database) -> Void = { _ in }
|
|
) {
|
|
dependencies.mutate(cache: .transactionObserver) {
|
|
$0.add(self, dedupeId: UUID().uuidString, onCommit: onCommit, onRollback: onRollback)
|
|
}
|
|
}
|
|
|
|
func afterNextTransactionNestedOnce(
|
|
dedupeId: String,
|
|
using dependencies: Dependencies,
|
|
onCommit: @escaping (Database) -> Void,
|
|
onRollback: @escaping (Database) -> Void = { _ in }
|
|
) {
|
|
dependencies.mutate(cache: .transactionObserver) {
|
|
$0.add(self, dedupeId: dedupeId, onCommit: onCommit, onRollback: onRollback)
|
|
}
|
|
}
|
|
}
|
|
|
|
internal class TransactionHandler: TransactionObserver {
|
|
private let dependencies: Dependencies
|
|
private let identifier: String
|
|
private let onCommit: (Database) -> Void
|
|
private let onRollback: (Database) -> Void
|
|
|
|
init(
|
|
identifier: String,
|
|
onCommit: @escaping (Database) -> Void,
|
|
onRollback: @escaping (Database) -> Void,
|
|
using dependencies: Dependencies
|
|
) {
|
|
self.dependencies = dependencies
|
|
self.identifier = identifier
|
|
self.onCommit = onCommit
|
|
self.onRollback = onRollback
|
|
}
|
|
|
|
// Ignore changes
|
|
func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { false }
|
|
func databaseDidChange(with event: DatabaseEvent) { }
|
|
|
|
func databaseDidCommit(_ db: Database) {
|
|
dependencies.mutate(cache: .transactionObserver) { $0.remove(for: identifier) }
|
|
|
|
do {
|
|
try db.inTransaction {
|
|
onCommit(db)
|
|
return .commit
|
|
}
|
|
}
|
|
catch {
|
|
Log.warn(.storage, "afterNextTransactionNested onCommit failed")
|
|
}
|
|
}
|
|
|
|
func databaseDidRollback(_ db: Database) {
|
|
dependencies.mutate(cache: .transactionObserver) { $0.remove(for: identifier) }
|
|
onRollback(db)
|
|
}
|
|
}
|
|
|
|
// MARK: - TransactionObserver Cache
|
|
|
|
internal extension Storage {
|
|
class TransactionObserverCache: TransactionObserverCacheType {
|
|
private let dependencies: Dependencies
|
|
public var registeredHandlers: [String: TransactionHandler] = [:]
|
|
|
|
// MARK: - Initialization
|
|
|
|
public init(using dependencies: Dependencies) {
|
|
self.dependencies = dependencies
|
|
}
|
|
|
|
// MARK: - Functions
|
|
|
|
public func add(
|
|
_ db: Database,
|
|
dedupeId: String,
|
|
onCommit: @escaping (Database) -> Void,
|
|
onRollback: @escaping (Database) -> Void
|
|
) {
|
|
// Only allow a single observer per `dedupeId` per transaction, this allows us to
|
|
// schedule an action to run at most once per transaction (eg. auto-scheduling a ConfigSyncJob
|
|
// when receiving messages)
|
|
guard registeredHandlers[dedupeId] == nil else { return }
|
|
|
|
let observer: TransactionHandler = TransactionHandler(
|
|
identifier: dedupeId,
|
|
onCommit: onCommit,
|
|
onRollback: onRollback,
|
|
using: dependencies
|
|
)
|
|
db.add(transactionObserver: observer, extent: .nextTransaction)
|
|
registeredHandlers[dedupeId] = observer
|
|
}
|
|
|
|
public func remove(for identifier: String) {
|
|
registeredHandlers.removeValue(forKey: identifier)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - TransactionObserverCacheType
|
|
|
|
/// This is a read-only version of the Cache designed to avoid unintentionally mutating the instance in a non-thread-safe way
|
|
internal protocol TransactionObserverImmutableCacheType: ImmutableCacheType {
|
|
var registeredHandlers: [String: TransactionHandler] { get }
|
|
}
|
|
|
|
internal protocol TransactionObserverCacheType: TransactionObserverImmutableCacheType, MutableCacheType {
|
|
var registeredHandlers: [String: TransactionHandler] { get }
|
|
|
|
func add(
|
|
_ db: Database,
|
|
dedupeId: String,
|
|
onCommit: @escaping (Database) -> Void,
|
|
onRollback: @escaping (Database) -> Void
|
|
)
|
|
func remove(for identifier: String)
|
|
}
|