@@ -45,6 +45,7 @@ import kotlinx.coroutines.CoroutineExceptionHandler
45
45
import kotlinx.coroutines.Dispatchers
46
46
import kotlinx.coroutines.sync.Mutex
47
47
import kotlinx.coroutines.sync.withLock
48
+ import java.util.concurrent.ConcurrentHashMap
48
49
import java.util.concurrent.CopyOnWriteArrayList
49
50
import java.util.concurrent.atomic.AtomicBoolean
50
51
import java.util.concurrent.atomic.AtomicReference
@@ -77,7 +78,9 @@ public class SafeBox private constructor(
77
78
private val stateManager : SafeBoxStateManager ,
78
79
) : SharedPreferences {
79
80
80
- private val castFailureStrategy = AtomicReference <ValueFallbackStrategy >(WARN )
81
+ private val entries: MutableMap <Bytes , ByteArray > = ConcurrentHashMap ()
82
+
83
+ private val castFailureStrategy = AtomicReference (WARN )
81
84
82
85
private val byteDecoder = ByteDecoder (castFailureStrategy::get)
83
86
@@ -92,34 +95,79 @@ public class SafeBox private constructor(
92
95
93
96
private val delegate = object : Delegate {
94
97
98
+ private val updateLock = Any ()
99
+
95
100
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
96
101
Log .e(" SafeBox" , " Failed to apply changes." , throwable)
97
102
applyCompleted.complete(Unit )
98
103
}
99
104
100
- override fun commit (entries : LinkedHashMap <String , Action >, cleared : Boolean ): Boolean =
101
- stateManager.launchCommitWithWritingState {
105
+ override fun commit (entries : LinkedHashMap <String , Action >, cleared : Boolean ): Boolean {
106
+ val entriesToWrite = LinkedHashMap (entries)
107
+ synchronized(updateLock) {
108
+ entries.clear() // Prevents stale mutations on reused editor instance
109
+ updateEntries(entriesToWrite, cleared)
110
+ }
111
+ return stateManager.launchCommitWithWritingState {
102
112
try {
103
113
applyCompleted.await()
104
114
commitMutex.withLock {
105
- applyChanges(entries , cleared)
115
+ applyChanges(entriesToWrite , cleared)
106
116
}
107
117
true
108
118
} catch (e: Exception ) {
109
119
Log .e(" SafeBox" , " Failed to commit changes." , e)
110
120
false
111
121
}
112
122
}
123
+ }
113
124
114
125
override fun apply (entries : LinkedHashMap <String , Action >, cleared : Boolean ) {
126
+ val entriesToWrite = LinkedHashMap (entries)
127
+ synchronized(updateLock) {
128
+ entries.clear() // Prevents stale mutations on reused editor instance
129
+ updateEntries(entriesToWrite, cleared)
130
+ }
115
131
stateManager.launchApplyWithWritingState(exceptionHandler) {
116
132
applyCompleted = CompletableDeferred ()
117
133
applyMutex.withLock {
118
- applyChanges(entries , cleared)
134
+ applyChanges(entriesToWrite , cleared)
119
135
}
120
136
applyCompleted.complete(Unit )
121
137
}
122
138
}
139
+
140
+ private fun updateEntries (entries : LinkedHashMap <String , Action >, cleared : Boolean ) {
141
+ if (cleared) {
142
+ val keys = this @SafeBox.entries.keys.toHashSet()
143
+ this @SafeBox.entries.clear()
144
+ for (encryptedKey in keys) {
145
+ val key = keyCipherProvider.decrypt(encryptedKey.value).toString(Charsets .UTF_8 )
146
+ listeners.forEach { it.onSharedPreferenceChanged(this @SafeBox, key) }
147
+ }
148
+ }
149
+ for ((key, action) in entries) {
150
+ when (action) {
151
+ is Put -> {
152
+ val encryptedKey = key.toEncryptedKey()
153
+ val encryptedValue = action.encodedValue.value
154
+ .let (valueCipherProvider::encrypt)
155
+ this @SafeBox.entries[encryptedKey] = encryptedValue
156
+ }
157
+ is Remove -> {
158
+ val encryptedKey = key.toEncryptedKey()
159
+ this @SafeBox.entries.remove(encryptedKey)
160
+ }
161
+ }
162
+ listeners.forEach { it.onSharedPreferenceChanged(this @SafeBox, key) }
163
+ }
164
+ }
165
+ }
166
+
167
+ init {
168
+ stateManager.launchWithStartingState {
169
+ entries + = blobStore.loadPersistedEntries()
170
+ }
123
171
}
124
172
125
173
/* *
@@ -185,9 +233,8 @@ public class SafeBox private constructor(
185
233
}
186
234
187
235
override fun getAll (): Map <String , Any ?> {
188
- val encryptedEntries = blobStore.getAll()
189
- val decryptedEntries = HashMap <String , Any ?>(encryptedEntries.size, 1f )
190
- for (entry in encryptedEntries) {
236
+ val decryptedEntries = HashMap <String , Any ?>(entries.size, 1f )
237
+ for (entry in entries) {
191
238
val key = keyCipherProvider.decrypt(entry.key.value).toString(Charsets .UTF_8 )
192
239
val value = valueCipherProvider.decrypt(entry.value)
193
240
decryptedEntries[key] = byteDecoder.decodeAny(value)
@@ -226,7 +273,7 @@ public class SafeBox private constructor(
226
273
? : defValue
227
274
228
275
override fun contains (key : String ): Boolean =
229
- blobStore.contains (key.toEncryptedKey())
276
+ entries.containsKey (key.toEncryptedKey())
230
277
231
278
override fun edit (): SharedPreferences .Editor = Editor (delegate)
232
279
@@ -243,15 +290,12 @@ public class SafeBox private constructor(
243
290
}
244
291
245
292
private fun getDecryptedValue (key : String ): ByteArray? =
246
- blobStore.get( key.toEncryptedKey())
293
+ entries[ key.toEncryptedKey()]
247
294
?.let (valueCipherProvider::decrypt)
248
295
249
296
private suspend fun applyChanges (entries : LinkedHashMap <String , Action >, cleared : Boolean ) {
250
297
if (cleared) {
251
- blobStore.deleteAll().forEach { encryptedKey ->
252
- val key = keyCipherProvider.decrypt(encryptedKey.value).toString(Charsets .UTF_8 )
253
- listeners.forEach { it.onSharedPreferenceChanged(this , key) }
254
- }
298
+ blobStore.deleteAll()
255
299
}
256
300
for ((key, action) in entries) {
257
301
when (action) {
@@ -267,9 +311,7 @@ public class SafeBox private constructor(
267
311
}
268
312
}
269
313
}
270
- listeners.forEach { it.onSharedPreferenceChanged(this , key) }
271
314
}
272
- entries.clear()
273
315
}
274
316
275
317
private fun String.toEncryptedKey (): Bytes =
@@ -384,7 +426,7 @@ public class SafeBox private constructor(
384
426
val keyCipherProvider = ChaCha20CipherProvider (keyProvider, deterministic = true )
385
427
val valueCipherProvider = ChaCha20CipherProvider (keyProvider, deterministic = false )
386
428
val stateManager = SafeBoxStateManager (fileName, stateListener, ioDispatcher)
387
- val blobStore = SafeBoxBlobStore .create(context, fileName, stateManager )
429
+ val blobStore = SafeBoxBlobStore .create(context, fileName)
388
430
return SafeBox (blobStore, keyCipherProvider, valueCipherProvider, stateManager)
389
431
}
390
432
@@ -421,7 +463,7 @@ public class SafeBox private constructor(
421
463
): SafeBox {
422
464
SafeBoxBlobFileRegistry .register(fileName)
423
465
val stateManager = SafeBoxStateManager (fileName, stateListener, ioDispatcher)
424
- val blobStore = SafeBoxBlobStore .create(context, fileName, stateManager )
466
+ val blobStore = SafeBoxBlobStore .create(context, fileName)
425
467
return SafeBox (blobStore, keyCipherProvider, valueCipherProvider, stateManager)
426
468
}
427
469
}
0 commit comments