Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ extension DataStoreCategory: DataStoreBaseBehavior {
plugin.delete(modelType, withId: id, completion: completion)
}

public func start(completion: @escaping DataStoreCallback<Void>) {
plugin.start(completion: completion)
}

public func stop(completion: @escaping DataStoreCallback<Void>) {
plugin.stop(completion: completion)
}

public func clear(completion: @escaping DataStoreCallback<Void>) {
plugin.clear(completion: completion)
}
Expand Down
22 changes: 22 additions & 0 deletions Amplify/Categories/DataStore/DataStoreCategoryBehavior.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,29 @@ public protocol DataStoreBaseBehavior {
func delete<M: Model>(_ modelType: M.Type,
withId id: String,
completion: @escaping DataStoreCallback<Void>)
/**
Synchronization starts automatically whenever you run any DataStore operation (query(), save(), delete())
however, you can explicitly begin the process with DatasStore.start()

- parameter completion: callback to be invoked on success or failure
*/
func start(completion: @escaping DataStoreCallback<Void>)

/**
To stop the DataStore sync process, you can use DataStore.stop(). This ensures the real time subscription
connection is closed when your app is no longer interested in updates, such as when you application is closed.
This can also be used to modify DataStore sync expressions at runtime by calling stop(), then start()
to force your sync expressions to be re-evaluated.

- parameter completion: callback to be invoked on success or failure
*/
func stop(completion: @escaping DataStoreCallback<Void>)

/**
To clear local data from DataStore, use the clear method.

- parameter completion: callback to be invoked on success or failure
*/
func clear(completion: @escaping DataStoreCallback<Void>)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,11 +169,36 @@ extension AWSDataStorePlugin: DataStoreBaseBehavior {
completion: onCompletion)
}

public func start(completion: @escaping DataStoreCallback<Void>) {
reinitStorageEngineIfNeeded(completion: completion)
}

public func stop(completion: @escaping DataStoreCallback<Void>) {
storageEngineInitSemaphore.wait()
if storageEngine == nil {
storageEngineInitSemaphore.signal()
completion(.successfulVoid)
return
}
storageEngineInitSemaphore.signal()
storageEngine.stopSync { result in
self.storageEngine = nil
if #available(iOS 13.0, *) {
self.dataStorePublisher?.sendFinished()
}
self.dataStorePublisher = nil
completion(result)
}
}

public func clear(completion: @escaping DataStoreCallback<Void>) {
storageEngineInitSemaphore.wait()
if storageEngine == nil {
storageEngineInitSemaphore.signal()
completion(.successfulVoid)
return
}
storageEngineInitSemaphore.signal()
storageEngine.clear { result in
self.storageEngine = nil
if #available(iOS 13.0, *) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,9 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin {

let validAuthPluginKey: String

/// The local storage provider. Resolved during configuration phase
var storageEngine: StorageEngineBehavior!
var storageEngineInitSemaphore: DispatchSemaphore
var storageEngineBehaviorFactory: StorageEngineBehaviorFactory

var iStorageEngineSink: Any?
@available(iOS 13.0, *)
Expand All @@ -52,28 +53,32 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin {
self.isSyncEnabled = false
self.validAPIPluginKey = "awsAPIPlugin"
self.validAuthPluginKey = "awsCognitoAuthPlugin"

self.storageEngineBehaviorFactory =
StorageEngine.init(isSyncEnabled:dataStoreConfiguration:validAPIPluginKey:validAuthPluginKey:modelRegistryVersion:userDefault:)
if #available(iOS 13.0, *) {
self.dataStorePublisher = DataStorePublisher()
} else {
self.dataStorePublisher = nil
}
self.storageEngineInitSemaphore = DispatchSemaphore(value: 1)
}

/// Internal initializer for testing
init(modelRegistration: AmplifyModelRegistration,
configuration dataStoreConfiguration: DataStoreConfiguration = .default,
storageEngine: StorageEngineBehavior,
storageEngineBehaviorFactory: StorageEngineBehaviorFactory? = nil,
dataStorePublisher: ModelSubcriptionBehavior,
validAPIPluginKey: String,
validAuthPluginKey: String) {
self.modelRegistration = modelRegistration
self.dataStoreConfiguration = dataStoreConfiguration
self.isSyncEnabled = false
self.storageEngine = storageEngine
self.storageEngineBehaviorFactory = storageEngineBehaviorFactory ??
StorageEngine.init(isSyncEnabled:dataStoreConfiguration:validAPIPluginKey:validAuthPluginKey:modelRegistryVersion:userDefault:)
self.dataStorePublisher = dataStorePublisher
self.validAPIPluginKey = validAPIPluginKey
self.validAuthPluginKey = validAuthPluginKey
self.storageEngineInitSemaphore = DispatchSemaphore(value: 1)
}

/// By the time this method gets called, DataStore will already have invoked
Expand All @@ -82,33 +87,28 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin {
public func configure(using amplifyConfiguration: Any?) throws {
modelRegistration.registerModels(registry: ModelRegistry.self)
resolveSyncEnabled()

try resolveStorageEngine(dataStoreConfiguration: dataStoreConfiguration)

try storageEngine.setUp(modelSchemas: ModelRegistry.modelSchemas)

let filter = HubFilters.forEventName(HubPayload.EventName.Amplify.configured)
var token: UnsubscribeToken?
token = Amplify.Hub.listen(to: .dataStore, isIncluded: filter) { _ in
self.storageEngine.startSync()
if let token = token {
Amplify.Hub.removeListener(token)
}
}
}

func reinitStorageEngineIfNeeded() {
func reinitStorageEngineIfNeeded(completion: @escaping DataStoreCallback<Void> = {_ in}) {
storageEngineInitSemaphore.wait()
if storageEngine != nil {
storageEngineInitSemaphore.signal()
completion(.successfulVoid)
return
}
do {
if #available(iOS 13.0, *) {
self.dataStorePublisher = DataStorePublisher()
if self.dataStorePublisher == nil {
self.dataStorePublisher = DataStorePublisher()
}
}
try resolveStorageEngine(dataStoreConfiguration: dataStoreConfiguration)
try storageEngine.setUp(modelSchemas: ModelRegistry.modelSchemas)
storageEngine.startSync()
storageEngineInitSemaphore.signal()
storageEngine.startSync(completion: completion)
} catch {
storageEngineInitSemaphore.signal()
completion(.failure(causedBy: error))
log.error(error: error)
}
}
Expand All @@ -118,11 +118,13 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin {
return
}

storageEngine = try StorageEngine(isSyncEnabled: isSyncEnabled,
dataStoreConfiguration: dataStoreConfiguration,
validAPIPluginKey: validAPIPluginKey,
validAuthPluginKey: validAuthPluginKey,
modelRegistryVersion: modelRegistration.version)
storageEngine = try storageEngineBehaviorFactory(isSyncEnabled,
dataStoreConfiguration,
validAPIPluginKey,
validAuthPluginKey,
modelRegistration.version,
UserDefaults.standard)

if #available(iOS 13.0, *) {
setupStorageSink()
}
Expand Down Expand Up @@ -184,4 +186,3 @@ final public class AWSDataStorePlugin: DataStoreCategoryPlugin {
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,30 @@ import AWSPluginsCore

extension StorageEngine {

func startSync() {
func startSync(completion: @escaping DataStoreCallback<Void>) {
guard let api = tryGetAPIPlugin() else {
log.info("Unable to find suitable API plugin for syncEngine. syncEngine will not be started")
completion(.failure(.configuration("Unable to find suitable API plugin for syncEngine. syncEngine will not be started",
"Ensure the API category has been setup and configured for your project", nil)))
return
}

let authPluginRequired = requiresAuthPlugin(api: api)

guard authPluginRequired else {
syncEngine?.start(api: api, auth: nil)
completion(.successfulVoid)
return
}

guard let auth = tryGetAuthPlugin() else {
log.warn("Unable to find suitable Auth plugin for syncEngine. Models require auth")
completion(.failure(.configuration("Unable to find suitable Auth plugin for syncEngine. Models require auth",
"Ensure the Auth category has been setup and configured for your project", nil)))
return
}

syncEngine?.start(api: api, auth: auth)
completion(.successfulVoid)
}

private func tryGetAPIPlugin() -> APICategoryGraphQLBehavior? {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import Combine
import Foundation
import AWSPluginsCore

typealias StorageEngineBehaviorFactory =
(Bool,
DataStoreConfiguration,
String,
String,
String,
UserDefaults) throws -> StorageEngineBehavior

// swiftlint:disable type_body_length
final class StorageEngine: StorageEngineBehavior {

Expand Down Expand Up @@ -386,6 +394,16 @@ final class StorageEngine: StorageEngineBehavior {
}
}

func stopSync(completion: @escaping DataStoreCallback<Void>) {
if let syncEngine = syncEngine {
syncEngine.stop { _ in
completion(.successfulVoid)
}
} else {
completion(.successfulVoid)
}
}

func reset(onComplete: () -> Void) {
// TOOD: Perform cleanup on StorageAdapter, including releasing its `Connection` if needed
let group = DispatchGroup()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ protocol StorageEngineBehavior: class, ModelStorageBehavior {
var publisher: AnyPublisher<StorageEngineEvent, DataStoreError> { get }

/// start remote sync, based on if sync is enabled and/or authentication is required
func startSync()

func startSync(completion: @escaping DataStoreCallback<Void>)
func stopSync(completion: @escaping DataStoreCallback<Void>)
func clear(completion: @escaping DataStoreCallback<Void>)
}
Loading