mirror of
https://github.com/lux-tel/session-ios.git
synced 2026-10-30 20:38:32 +00:00
257 lines
10 KiB
Swift
257 lines
10 KiB
Swift
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
|
|
|
|
import Combine
|
|
import GRDB
|
|
|
|
@testable import SessionUtilitiesKit
|
|
|
|
class SynchronousStorage: Storage, DependenciesSettable, InitialSetupable {
|
|
public var dependencies: Dependencies
|
|
private let initialData: ((ObservingDatabase) throws -> ())?
|
|
|
|
public init(
|
|
customWriter: DatabaseWriter? = nil,
|
|
migrations: [Migration.Type]? = nil,
|
|
using dependencies: Dependencies,
|
|
initialData: ((ObservingDatabase) throws -> ())? = nil
|
|
) {
|
|
self.dependencies = dependencies
|
|
self.initialData = initialData
|
|
|
|
super.init(customWriter: customWriter, using: dependencies)
|
|
|
|
if let migrations: [Migration.Type] = migrations {
|
|
perform(
|
|
migrations: migrations,
|
|
async: false,
|
|
onProgressUpdate: nil,
|
|
onComplete: { _ in }
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - DependenciesSettable
|
|
|
|
func setDependencies(_ dependencies: Dependencies?) {
|
|
guard let dependencies: Dependencies = dependencies else { return }
|
|
|
|
self.dependencies = dependencies
|
|
}
|
|
|
|
// MARK: - InitialSetupable
|
|
|
|
func performInitialSetup() {
|
|
guard let closure: ((ObservingDatabase) throws -> ()) = initialData else { return }
|
|
|
|
write { db in try closure(db) }
|
|
}
|
|
|
|
// MARK: - Overwritten Functions
|
|
|
|
@discardableResult override func write<T>(
|
|
fileName: String = #fileID,
|
|
functionName: String = #function,
|
|
lineNumber: Int = #line,
|
|
updates: @escaping (ObservingDatabase) throws -> T?
|
|
) -> T? {
|
|
guard isValid, let dbWriter: DatabaseWriter = testDbWriter else { return nil }
|
|
|
|
// If 'forceSynchronous' is true then it's likely that we will access the database in
|
|
// a reentrant way, the 'unsafeReentrant...' functions allow us to interact with the
|
|
// database without worrying about reentrant access during tests because we can be
|
|
// confident that the tests are running on the correct thread
|
|
guard !dependencies.forceSynchronous else {
|
|
var result: T?
|
|
var events: [ObservedEvent] = []
|
|
var actions: [String: () -> Void] = [:]
|
|
|
|
do {
|
|
try dbWriter.unsafeReentrantWrite { [dependencies] db in
|
|
let observingDatabase: ObservingDatabase = ObservingDatabase.create(db, using: dependencies)
|
|
result = try ObservationContext.$observingDb.withValue(observingDatabase) {
|
|
try updates(observingDatabase)
|
|
}
|
|
|
|
events = observingDatabase.events
|
|
actions = observingDatabase.postCommitActions
|
|
}
|
|
|
|
/// Forcibly trigger `ObservableEvent` and `postCommitActions` when forcing synchronous logic
|
|
dependencies.notifyAsync(events: events)
|
|
|
|
actions.values.forEach { $0() }
|
|
}
|
|
catch {}
|
|
|
|
return result
|
|
}
|
|
|
|
return super.write(
|
|
fileName: fileName,
|
|
functionName: functionName,
|
|
lineNumber: lineNumber,
|
|
updates: updates
|
|
)
|
|
}
|
|
|
|
@discardableResult override func read<T>(
|
|
fileName: String = #fileID,
|
|
functionName: String = #function,
|
|
lineNumber: Int = #line,
|
|
_ value: @escaping (ObservingDatabase) throws -> T?
|
|
) -> T? {
|
|
guard isValid, let dbWriter: DatabaseWriter = testDbWriter else { return nil }
|
|
|
|
// If 'forceSynchronous' is true then it's likely that we will access the database in
|
|
// a reentrant way, the 'unsafeReentrant...' functions allow us to interact with the
|
|
// database without worrying about reentrant access during tests because we can be
|
|
// confident that the tests are running on the correct thread
|
|
guard !dependencies.forceSynchronous else {
|
|
var result: T?
|
|
var events: [ObservedEvent] = []
|
|
var actions: [String: () -> Void] = [:]
|
|
|
|
do {
|
|
try dbWriter.unsafeReentrantRead { [dependencies] db in
|
|
let observingDatabase: ObservingDatabase = ObservingDatabase.create(db, using: dependencies)
|
|
result = try ObservationContext.$observingDb.withValue(observingDatabase) {
|
|
try value(observingDatabase)
|
|
}
|
|
|
|
events = observingDatabase.events
|
|
actions = observingDatabase.postCommitActions
|
|
}
|
|
|
|
/// Forcibly trigger `ObservableEvent` and `postCommitActions` when forcing synchronous logic
|
|
dependencies.notifyAsync(events: events)
|
|
|
|
actions.values.forEach { $0() }
|
|
}
|
|
catch {}
|
|
|
|
return result
|
|
}
|
|
|
|
return super.read(
|
|
fileName: fileName,
|
|
functionName: functionName,
|
|
lineNumber: lineNumber,
|
|
value
|
|
)
|
|
}
|
|
|
|
// MARK: - Async Methods
|
|
|
|
override func readPublisher<T>(
|
|
fileName: String = #fileID,
|
|
functionName: String = #function,
|
|
lineNumber: Int = #line,
|
|
value: @escaping (ObservingDatabase) throws -> T
|
|
) -> AnyPublisher<T, Error> {
|
|
guard isValid, let dbWriter: DatabaseWriter = testDbWriter else {
|
|
return Fail(error: StorageError.generic)
|
|
.eraseToAnyPublisher()
|
|
}
|
|
|
|
// If 'forceSynchronous' is true then it's likely that we will access the database in
|
|
// a reentrant way, the 'unsafeReentrant...' functions allow us to interact with the
|
|
// database without worrying about reentrant access during tests because we can be
|
|
// confident that the tests are running on the correct thread
|
|
guard !dependencies.forceSynchronous else {
|
|
var events: [ObservedEvent] = []
|
|
var actions: [String: () -> Void] = [:]
|
|
|
|
return Just(())
|
|
.setFailureType(to: Error.self)
|
|
.tryMap { [dependencies] _ in
|
|
try dbWriter.unsafeReentrantRead { [dependencies] db in
|
|
let observingDatabase: ObservingDatabase = ObservingDatabase.create(db, using: dependencies)
|
|
let result: T = try ObservationContext.$observingDb.withValue(observingDatabase) {
|
|
try value(observingDatabase)
|
|
}
|
|
|
|
events = observingDatabase.events
|
|
actions = observingDatabase.postCommitActions
|
|
|
|
return result
|
|
}
|
|
}
|
|
.handleEvents(
|
|
receiveCompletion: { [dependencies] result in
|
|
/// Forcibly trigger `ObservableEvent` and `postCommitActions` when forcing synchronous logic
|
|
dependencies.notifyAsync(events: events)
|
|
|
|
actions.values.forEach { $0() }
|
|
}
|
|
)
|
|
.eraseToAnyPublisher()
|
|
}
|
|
|
|
return super.readPublisher(fileName: fileName, functionName: functionName, lineNumber: lineNumber, value: value)
|
|
}
|
|
|
|
override func writeAsync<T>(
|
|
fileName: String = #fileID,
|
|
functionName: String = #function,
|
|
lineNumber: Int = #line,
|
|
updates: @escaping (ObservingDatabase) throws -> T,
|
|
completion: @escaping (Result<T, Error>) -> Void
|
|
) {
|
|
do {
|
|
let result: T = try write(updates: updates) ?? { throw StorageError.failedToSave }()
|
|
completion(Result.success(result))
|
|
}
|
|
catch {
|
|
completion(Result.failure(error))
|
|
}
|
|
}
|
|
|
|
override func writePublisher<T>(
|
|
fileName: String = #fileID,
|
|
functionName: String = #function,
|
|
lineNumber: Int = #line,
|
|
updates: @escaping (ObservingDatabase) throws -> T
|
|
) -> AnyPublisher<T, Error> {
|
|
guard isValid, let dbWriter: DatabaseWriter = testDbWriter else {
|
|
return Fail(error: StorageError.generic)
|
|
.eraseToAnyPublisher()
|
|
}
|
|
|
|
// If 'forceSynchronous' is true then it's likely that we will access the database in
|
|
// a reentrant way, the 'unsafeReentrant...' functions allow us to interact with the
|
|
// database without worrying about reentrant access during tests because we can be
|
|
// confident that the tests are running on the correct thread
|
|
guard !dependencies.forceSynchronous else {
|
|
var events: [ObservedEvent] = []
|
|
var actions: [String: () -> Void] = [:]
|
|
|
|
return Just(())
|
|
.setFailureType(to: Error.self)
|
|
.tryMap { [dependencies] _ in
|
|
try dbWriter.unsafeReentrantWrite { [dependencies] db in
|
|
let observingDatabase: ObservingDatabase = ObservingDatabase.create(db, using: dependencies)
|
|
let result: T = try ObservationContext.$observingDb.withValue(observingDatabase) {
|
|
try updates(observingDatabase)
|
|
}
|
|
|
|
events = observingDatabase.events
|
|
actions = observingDatabase.postCommitActions
|
|
|
|
return result
|
|
}
|
|
}
|
|
.handleEvents(
|
|
receiveCompletion: { [dependencies] result in
|
|
/// Forcibly trigger `ObservableEvent` and `postCommitActions` when forcing synchronous logic
|
|
dependencies.notifyAsync(events: events)
|
|
|
|
actions.values.forEach { $0() }
|
|
}
|
|
)
|
|
.eraseToAnyPublisher()
|
|
}
|
|
|
|
return super.writePublisher(fileName: fileName, functionName: functionName, lineNumber: lineNumber, updates: updates)
|
|
}
|
|
}
|