1 /*
2  * Copyright 2022 The Android Open Source Project
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  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package androidx.datastore.core
18 
19 import androidx.datastore.core.handlers.NoOpCorruptionHandler
20 import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
21 import java.io.File
22 import kotlinx.coroutines.CoroutineScope
23 import kotlinx.coroutines.Dispatchers
24 import kotlinx.coroutines.SupervisorJob
25 
26 public object MultiProcessDataStoreFactory {
27     /**
28      * Create an instance of MultiProcessDataStore, which provides cross-process eventual
29      * consistency. Never create more than one instance of DataStore for a given file in the same
30      * process; doing so can break all DataStore functionality. You should consider managing your
31      * DataStore instance for each file as a singleton. If there are multiple DataStores active for
32      * a given file in the same process, DataStore will throw IllegalStateException when reading or
33      * updating data. A DataStore is considered active as long as its scope is active. Having
34      * multiple instances, each for a different file, in the same process is OK.
35      *
36      * T is the type DataStore acts on. The type T must be immutable. Mutating a type used in
37      * DataStore invalidates any guarantees that DataStore provides and will result in potentially
38      * serious, hard-to-catch bugs. We strongly recommend using protocol buffers:
39      * https://developers.google.com/protocol-buffers/docs/javatutorial - which provides
40      * immutability guarantees, a simple API and efficient serialization.
41      *
42      * @param storage Storage to handle file reads and writes used with DataStore. The type T must
43      *   be immutable. The storage must operate on the same file as the one passed in {@link
44      *   produceFile}.
45      * @param corruptionHandler The [ReplaceFileCorruptionHandler] is invoked if DataStore
46      *   encounters a [CorruptionException] when attempting to read data. CorruptionExceptions are
47      *   thrown by serializers when data can not be de-serialized.
48      * @param migrations Migrations are run before any access to data can occur. Migrations must be
49      *   idempotent.
50      * @param scope The scope in which IO operations and transform functions will execute.
51      * @return a new DataStore instance with the provided configuration
52      */
53     @JvmOverloads // Generate constructors for default params for java users.
createnull54     public fun <T> create(
55         storage: Storage<T>,
56         corruptionHandler: ReplaceFileCorruptionHandler<T>? = null,
57         migrations: List<DataMigration<T>> = listOf(),
58         scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
59     ): DataStore<T> =
60         DataStoreImpl<T>(
61             storage = storage,
62             initTasksList = listOf(DataMigrationInitializer.getInitializer(migrations)),
63             corruptionHandler = corruptionHandler ?: NoOpCorruptionHandler(),
64             scope = scope
65         )
66 
67     /**
68      * Create an instance of MultiProcessDataStore, which provides cross-process eventual
69      * consistency. Never create more than one instance of DataStore for a given file in the same
70      * process; doing so can break all DataStore functionality. You should consider managing your
71      * DataStore instance for each file as a singleton. If there are multiple DataStores active for
72      * a given file in the same process, DataStore will throw IllegalStateException when reading or
73      * updating data. A DataStore is considered active as long as its scope is active. Having
74      * multiple instances, each for a different file, in the same process is OK.
75      *
76      * T is the type DataStore acts on. The type T must be immutable. Mutating a type used in
77      * DataStore invalidates any guarantees that DataStore provides and will result in potentially
78      * serious, hard-to-catch bugs. We strongly recommend using protocol buffers:
79      * https://developers.google.com/protocol-buffers/docs/javatutorial - which provides
80      * immutability guarantees, a simple API and efficient serialization.
81      *
82      * @param serializer Serializer for the type T used with DataStore. The type T must be
83      *   immutable.
84      * @param corruptionHandler The {@link
85      *   androidx.datastore.core.handlers.ReplaceFileCorruptionHandler} is invoked if DataStore
86      *   encounters a [CorruptionException] when attempting to read data. CorruptionExceptions are
87      *   thrown by serializers when data can not be de-serialized.
88      * @param migrations Migrations are run before any access to data can occur. Migrations must be
89      *   idempotent.
90      * @param scope The scope in which IO operations and transform functions will execute.
91      * @param produceFile Function which returns the file that the new DataStore will act on. The
92      *   function must return the same path every time. No two instances of DataStore should act on
93      *   the same file at the same time in the same process.
94      * @return a new DataStore instance with the provided configuration
95      */
96     @JvmOverloads // Generate constructors for default params for java users.
97     public fun <T> create(
98         serializer: Serializer<T>,
99         corruptionHandler: ReplaceFileCorruptionHandler<T>? = null,
100         migrations: List<DataMigration<T>> = listOf(),
101         scope: CoroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob()),
102         produceFile: () -> File
103     ): DataStore<T> =
104         DataStoreImpl<T>(
105             storage =
106                 FileStorage(
107                     serializer,
108                     { MultiProcessCoordinator(scope.coroutineContext, it) },
109                     produceFile
110                 ),
111             initTasksList = listOf(DataMigrationInitializer.getInitializer(migrations)),
112             corruptionHandler = corruptionHandler ?: NoOpCorruptionHandler(),
113             scope = scope
114         )
115 }
116