Skip to content

Commit 64bd6f2

Browse files
authored
Palpatim/crash resuming from background (#171)
* Begin migrating AWSMutationCache into promises (#155) * Refactored AWSSQLLiteNormalizedCache * Some Promise wrapping around AWSSQLLiteNormalizedCache * Added AWSAppSyncCacheConfiguration, deprecated old databaseURL-style configs - Renamed `AWSSQLLiteNormalizedCacheError` to `AWSAppSyncQueriesCacheError` - Deprecated `AWSSQLLiteNormalizedCache` - Deprecated `databaseURL` option to `AWSAppSyncClientConfiguration` - Deprecated `MutationCache` protocol * Fixed license notices
1 parent 0d164bc commit 64bd6f2

File tree

77 files changed

+1450
-1215
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1450
-1215
lines changed

AWSAppSyncClient.xcodeproj/project.pbxproj

+64-16
Large diffs are not rendered by default.

AWSAppSyncClient.xcworkspace/xcshareddata/IDETemplateMacros.plist

+3-12
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,9 @@
44
<dict>
55
<key>FILEHEADER</key>
66
<string>
7-
// Copyright 2010-___YEAR___ Amazon.com, Inc. or its affiliates. All Rights Reserved.
8-
//
9-
// Licensed under the Apache License, Version 2.0 (the "License").
10-
// You may not use this file except in compliance with the License.
11-
// A copy of the License is located at
12-
//
13-
// http://aws.amazon.com/apache2.0
14-
//
15-
// or in the "license" file accompanying this file. This file is distributed
16-
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
17-
// express or implied. See the License for the specific language governing
18-
// permissions and limitations under the License.
7+
// Copyright ___YEAR___ Amazon.com, Inc. or its affiliates. All Rights Reserved.
8+
// Licensed under the Amazon Software License
9+
// http://aws.amazon.com/asl/
1910
//</string>
2011
</dict>
2112
</plist>

AWSAppSyncClient/AWSAppSyncAuthType.swift

+3-12
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
11
//
2-
// Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3-
//
4-
// Licensed under the Apache License, Version 2.0 (the "License").
5-
// You may not use this file except in compliance with the License.
6-
// A copy of the License is located at
7-
//
8-
// http://aws.amazon.com/apache2.0
9-
//
10-
// or in the "license" file accompanying this file. This file is distributed
11-
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12-
// express or implied. See the License for the specific language governing
13-
// permissions and limitations under the License.
2+
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
// Licensed under the Amazon Software License
4+
// http://aws.amazon.com/asl/
145
//
156

167
/// Supported authentication types for the AppSyncClient
+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
//
2+
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
// Licensed under the Amazon Software License
4+
// http://aws.amazon.com/asl/
5+
//
6+
7+
import Foundation
8+
9+
/// Errors thrown from the Queries Cache
10+
public enum AWSAppSyncQueriesCacheError: Error {
11+
case invalidRecordEncoding(record: String)
12+
case invalidRecordShape(object: Any)
13+
case invalidRecordValue(value: Any)
14+
}
15+
16+
/// Errors thrown during creation or migration of AppSync caches
17+
public enum AWSCacheConfigurationError: Error, LocalizedError {
18+
/// Could not resolve the default Caches directory
19+
case couldNotResolveCachesDirectory
20+
21+
public var errorDescription: String? {
22+
return String(describing: self)
23+
}
24+
25+
public var localizedDescription: String {
26+
return String(describing: self)
27+
}
28+
}
29+
30+
/// Defines the working directories of different caches in use by AWSAppSync. If the value is non-nil, then the cache
31+
/// is persisted at that URL. If the value is nil, the cache will be created in-memory, and lost when the app is
32+
/// restarted.
33+
public struct AWSAppSyncCacheConfiguration {
34+
/// A cache configuration with all caches created as in-memory.
35+
public static let inMemory = AWSAppSyncCacheConfiguration(offlineMutations: nil, queries: nil, subscriptionMetadataCache: nil)
36+
37+
/// A cache to store mutations created while the app is offline, to be delivered when the app regains network
38+
public let offlineMutations: URL?
39+
40+
/// An instance of Apollo's NormalizedCache to locally cache query data
41+
public let queries: URL?
42+
43+
/// A local cache to store information about active subscriptions, used to reconnect to subscriptions when the
44+
/// app is relaunched
45+
public let subscriptionMetadataCache: URL?
46+
47+
/// Creates a cache configuration with individually-specified cache paths. If a path is nil, the cache will
48+
/// be created in-memory. If specified, the directory portion of the file path must exist, and be writable by the
49+
/// hosting app. No attempt is made to validate the specified file paths until AppSync initializes the related
50+
/// cache at the specified path.
51+
///
52+
/// - Parameters:
53+
/// - offlineMutations: The file path to create or connect to the cache for the offline mutation queue.
54+
/// - queries: The file path to create or connect to the cache for the local query cache.
55+
/// - subscriptionMetadataCache: The file path to create or connect to the cache for subscription metadata.
56+
public init(offlineMutations: URL?, queries: URL?, subscriptionMetadataCache: URL?) {
57+
self.offlineMutations = offlineMutations
58+
self.queries = queries
59+
self.subscriptionMetadataCache = subscriptionMetadataCache
60+
}
61+
62+
/// Creates a cache configuration for caches at the specified workingDirectory. Attempts to create the directory
63+
/// if it does not already exist.
64+
///
65+
/// - Parameter url: The directory path at which to store persistent caches. Defaults to `<appCacheDirectory>/appsync`
66+
/// - Throws: Throws an error if `workingDirectory` is not a directory, or if it cannot be created.
67+
public init(withRootDirectory url: URL? = nil) throws {
68+
let resolvedRootDirectory: URL
69+
if let rootDirectory = url {
70+
resolvedRootDirectory = rootDirectory
71+
} else {
72+
guard let cachesDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else {
73+
throw AWSCacheConfigurationError.couldNotResolveCachesDirectory
74+
}
75+
resolvedRootDirectory = cachesDirectory.appendingPathComponent("appsync")
76+
}
77+
78+
try FileManager.default.createDirectory(at: resolvedRootDirectory, withIntermediateDirectories: true)
79+
80+
offlineMutations = resolvedRootDirectory.appendingPathComponent("offlineMutations.db")
81+
queries = resolvedRootDirectory.appendingPathComponent("queries.db")
82+
subscriptionMetadataCache = resolvedRootDirectory.appendingPathComponent("subscriptionMetadataCache.db")
83+
}
84+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
//
2+
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
// Licensed under the Amazon Software License
4+
// http://aws.amazon.com/asl/
5+
//
6+
7+
import Foundation
8+
import SQLite
9+
10+
@available(*, deprecated, message: "This utility will be removed when the databaseURL parameter is removed from AWSAppSyncClientConfiguration")
11+
public struct AWSAppSyncCacheConfigurationMigration {
12+
public enum Error: Swift.Error {
13+
/// The destination file specified by the cache configuration already exists
14+
case destinationFileExists
15+
}
16+
17+
/// A UserDefaults key that will be set once any successful migration has been completed
18+
public static let userDefaultsKey = "AWSAppSyncCacheConfigurationMigration.success"
19+
20+
private enum TableName: String, CaseIterable {
21+
case offlineMutations = "mutation_records"
22+
case queries = "records"
23+
case subscriptionMetadata = "subscription_metadata"
24+
}
25+
26+
/// A convenience method to migrate data from caches created prior to AWSAppSync 2.10.0. Once this migration
27+
/// completes, the old cache should be deleted. This method is safe to call multiple times: it stores a key in
28+
/// UserDefaults to indicate that the migration was successfully completed, and will only migrate if that flag is
29+
/// not set. That makes this method safe to call whenever you need to configure a new AppSyncClient (e.g., app
30+
/// startup or user login).
31+
///
32+
/// **Usage**
33+
/// Invoke `migrate` before setting up the AWSAppSyncClientConfiguration:
34+
///
35+
/// // Given `databaseURL` is the old consolidated databaseURL...
36+
///
37+
/// // Create a default cacheConfiguration with cache files stored in the app's Caches directory
38+
/// let cacheConfiguration = try AWSAppSyncCacheConfiguration()
39+
/// try? AWSAppSyncCacheConfigurationMigration.migrate(from: databaseURL, using: cacheConfiguration)
40+
/// let clientConfig = try AWSAppSyncClientConfiguration(appSyncServiceConfig: serviceConfig,
41+
/// cacheConfiguration: cacheConfiguration)
42+
/// let appSyncClient = AWSAppSyncClient(appSyncConfig: clientConfig)
43+
///
44+
/// **How it works**
45+
/// Internally, this method copies the database file from the source URL to the destination, and then drops
46+
/// unneeded tables. This results in higher disk usage on device, but is ultimately faster and safer than
47+
/// performing queries or data exports.creates a new connection to both the source and destination databases.
48+
///
49+
/// **Multiple calls**
50+
/// A successful migration to a cache configuration with at least one persistent store will write a flag to
51+
/// UserDefaults to prevent any future migrations from occurring. Migrating to an in-memory cache (such as is
52+
/// configured by passing no `AWSAppSyncCacheConfiguration` to the `AWSAppSyncClientConfiguration` constructor,
53+
/// or by passing `AWSAppSyncCacheConfiguration.inMemory`) will __not__ set the flag. That also means this method
54+
/// will not populate an in-memory copy of a previously existing on-disk database.
55+
///
56+
/// **Warning**
57+
/// This migration operates by copying the file from `databaseURL` to the URL of the individual cache.
58+
/// This would destroy any data at the destination cache, so the destination URL must not have a file present at
59+
/// the time the migration begins.
60+
///
61+
/// - Parameters:
62+
/// - databaseURL: The URL of the consolidated cache
63+
/// - cacheConfiguration: The AWSAppSyncCacheConfiguration specifying the individual destination cache
64+
/// locations to migrate to
65+
/// - Throws: If the migration encounters an file system error, or if any of the cache files in
66+
/// `cacheConfiguration` already exists.
67+
public static func migrate(from databaseURL: URL, using cacheConfiguration: AWSAppSyncCacheConfiguration) throws {
68+
guard !hasSuccessfullyMigrated() else {
69+
AppSyncLog.info("Migration has already been completed, aborting")
70+
return
71+
}
72+
73+
guard cacheConfiguration.offlineMutations != nil ||
74+
cacheConfiguration.queries != nil ||
75+
cacheConfiguration.subscriptionMetadataCache != nil else {
76+
AppSyncLog.info("At least one cacheConfiguration must be non-nil")
77+
return
78+
}
79+
80+
try migrate(.offlineMutations,
81+
from: databaseURL,
82+
to: cacheConfiguration.offlineMutations)
83+
84+
try migrate(.queries,
85+
from: databaseURL,
86+
to: cacheConfiguration.queries)
87+
88+
try migrate(.subscriptionMetadata,
89+
from: databaseURL,
90+
to: cacheConfiguration.subscriptionMetadataCache)
91+
92+
recordSuccessfulMigration()
93+
}
94+
95+
private static func hasSuccessfullyMigrated() -> Bool {
96+
let hasMigrated = UserDefaults.standard.bool(forKey: userDefaultsKey)
97+
return hasMigrated
98+
}
99+
100+
private static func migrate(_ tableName: TableName,
101+
from databaseURL: URL,
102+
to destinationURL: URL?) throws {
103+
guard let destinationURL = destinationURL else {
104+
return
105+
}
106+
107+
try copyIfDestinationNotExists(from: databaseURL, to: destinationURL)
108+
109+
let destinationDB = try Connection(.uri(destinationURL.absoluteString))
110+
111+
try deleteOtherTables(than: tableName, in: destinationDB)
112+
}
113+
114+
private static func copyIfDestinationNotExists(from sourceURL: URL, to destinationURL: URL) throws {
115+
let fileManager = FileManager.default
116+
117+
guard !fileManager.fileExists(atPath: destinationURL.path) else {
118+
throw Error.destinationFileExists
119+
}
120+
121+
try fileManager.copyItem(at: sourceURL, to: destinationURL)
122+
}
123+
124+
private static func deleteOtherTables(than tableName: TableName, in db: Connection) throws {
125+
for tableToDelete in TableName.allCases where tableToDelete != tableName {
126+
try db.run("DROP TABLE IF EXISTS \(tableToDelete.rawValue)")
127+
}
128+
129+
try db.run("VACUUM")
130+
}
131+
132+
private static func recordSuccessfulMigration() {
133+
UserDefaults.standard.set(true, forKey: userDefaultsKey)
134+
}
135+
136+
}

AWSAppSyncClient/AWSAppSyncClient.swift

+8-14
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,7 @@
11
//
2-
// Copyright 2010-2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3-
//
4-
// Licensed under the Apache License, Version 2.0 (the "License").
5-
// You may not use this file except in compliance with the License.
6-
// A copy of the License is located at
7-
//
8-
// http://aws.amazon.com/apache2.0
9-
//
10-
// or in the "license" file accompanying this file. This file is distributed
11-
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12-
// express or implied. See the License for the specific language governing
13-
// permissions and limitations under the License.
2+
// Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
// Licensed under the Amazon Software License
4+
// http://aws.amazon.com/asl/
145
//
156

167
import Foundation
@@ -49,11 +40,14 @@ public struct AWSAppSyncSubscriptionError: Error, LocalizedError {
4940
}
5041
}
5142

43+
/// Delegates will be notified when a mutation is performed from the `mutationCallback`. This pattern is necessary
44+
/// in order to provide notifications of mutations which are performed after an app restart and the initial callback
45+
/// context has been lost.
5246
public protocol AWSAppSyncOfflineMutationDelegate {
5347
func mutationCallback(recordIdentifier: String, operationString: String, snapshot: Snapshot?, error: Error?)
5448
}
5549

56-
// The client for making `Mutation`, `Query` and `Subscription` requests.
50+
/// The client for making `Mutation`, `Query` and `Subscription` requests.
5751
public class AWSAppSyncClient {
5852

5953
public let apolloClient: ApolloClient?
@@ -105,7 +99,7 @@ public class AWSAppSyncClient {
10599
networkClient: httpTransport!,
106100
handlerQueue: .main,
107101
reachabiltyChangeNotifier: NetworkReachabilityNotifier.shared,
108-
cacheFileURL: appSyncConfig.databaseURL)
102+
cacheFileURL: appSyncConfig.cacheConfiguration?.offlineMutations)
109103

110104
NotificationCenter.default.addObserver(
111105
self,

0 commit comments

Comments
 (0)