Skip to content

Commit 68c53c6

Browse files
authored
Make creating DataStore files more resilient (#7440)
Prepare the DataStore files directly in the Sessions SDK, instead of relying on DataStore to do it. DataStore uses the old `File.mkdirs` api, which lacks any meaningful error messages. This will use the better `Files.createDirectories` api if available, which has proper error messages. Also renamed the parent directory from `aqs` to `firebaseSessions` to avoid any collision with any other libraries that might use DataStore and a 3 letter file name. If there is an unexpected file where the `firebaseSessions` directory should be, this will safely remove it. Tested manually by creating the conflicting file and running the Sessions Test App on both old and new api levels.
1 parent 1246b69 commit 68c53c6

File tree

2 files changed

+52
-2
lines changed

2 files changed

+52
-2
lines changed

firebase-crashlytics/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Unreleased
22

3+
- [fixed] Made creating DataStore files more resilient [#7440]
4+
35
# 20.0.2
46

57
- [changed] Bumped internal dependencies.

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.google.firebase.sessions
1818

1919
import android.content.Context
20+
import android.os.Build
2021
import android.util.Log
2122
import androidx.datastore.core.DataMigration
2223
import androidx.datastore.core.DataStore
@@ -48,6 +49,8 @@ import dagger.Component
4849
import dagger.Module
4950
import dagger.Provides
5051
import java.io.File
52+
import java.io.IOException
53+
import java.nio.file.Files
5154
import javax.inject.Qualifier
5255
import javax.inject.Singleton
5356
import kotlin.coroutines.CoroutineContext
@@ -146,7 +149,11 @@ internal interface FirebaseSessionsComponent {
146149
SessionConfigsSerializer.defaultValue
147150
},
148151
scope = CoroutineScope(blockingDispatcher),
149-
produceFile = { appContext.dataStoreFile("aqs/sessionConfigsDataStore.data") },
152+
produceFile = {
153+
appContext.dataStoreFile("firebaseSessions/sessionConfigsDataStore.data").also {
154+
prepDataStoreFile(it)
155+
}
156+
},
150157
)
151158

152159
@Provides
@@ -164,7 +171,11 @@ internal interface FirebaseSessionsComponent {
164171
sessionDataSerializer.defaultValue
165172
},
166173
scope = CoroutineScope(blockingDispatcher),
167-
produceFile = { appContext.dataStoreFile("aqs/sessionDataStore.data") },
174+
produceFile = {
175+
appContext.dataStoreFile("firebaseSessions/sessionDataStore.data").also {
176+
prepDataStoreFile(it)
177+
}
178+
},
168179
)
169180

170181
private fun <T> createDataStore(
@@ -197,6 +208,43 @@ internal interface FirebaseSessionsComponent {
197208
} catch (_: SecurityException) {
198209
false
199210
}
211+
212+
/**
213+
* Prepares the DataStore file by ensuring its parent directory exists. Throws [IOException]
214+
* if the directory could not be created, or if a conflicting file could not be removed.
215+
*/
216+
private fun prepDataStoreFile(dataStoreFile: File) {
217+
val parentDir = dataStoreFile.parentFile ?: return
218+
219+
// Check if something exists at the path, but isn't a directory
220+
if (parentDir.exists() && !parentDir.isDirectory) {
221+
// Only delete it if it's the specific file we know we can safely remove
222+
if (parentDir.name == "firebaseSessions") {
223+
if (!parentDir.delete()) {
224+
throw IOException("Failed to delete conflicting file: $parentDir")
225+
}
226+
}
227+
}
228+
229+
if (parentDir.isDirectory) {
230+
return
231+
}
232+
233+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
234+
try {
235+
Files.createDirectories(parentDir.toPath())
236+
} catch (ex: Exception) {
237+
throw IOException("Failed to create directory: $parentDir", ex)
238+
}
239+
} else {
240+
if (!parentDir.mkdirs()) {
241+
// It's possible another thread created it in the meantime, so we double-check
242+
if (!parentDir.isDirectory) {
243+
throw IOException("Failed to create directory: $parentDir")
244+
}
245+
}
246+
}
247+
}
200248
}
201249
}
202250
}

0 commit comments

Comments
 (0)