diff --git a/firebase-crashlytics/CHANGELOG.md b/firebase-crashlytics/CHANGELOG.md index fd4d227c468..c545530b38e 100644 --- a/firebase-crashlytics/CHANGELOG.md +++ b/firebase-crashlytics/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- [fixed] Made creating DataStore files more resilient [#7440] + # 20.0.2 - [changed] Bumped internal dependencies. diff --git a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt index 70f818c570f..68315e0558d 100644 --- a/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt +++ b/firebase-sessions/src/main/kotlin/com/google/firebase/sessions/FirebaseSessionsComponent.kt @@ -17,6 +17,7 @@ package com.google.firebase.sessions import android.content.Context +import android.os.Build import android.util.Log import androidx.datastore.core.DataMigration import androidx.datastore.core.DataStore @@ -48,6 +49,8 @@ import dagger.Component import dagger.Module import dagger.Provides import java.io.File +import java.io.IOException +import java.nio.file.Files import javax.inject.Qualifier import javax.inject.Singleton import kotlin.coroutines.CoroutineContext @@ -146,7 +149,11 @@ internal interface FirebaseSessionsComponent { SessionConfigsSerializer.defaultValue }, scope = CoroutineScope(blockingDispatcher), - produceFile = { appContext.dataStoreFile("aqs/sessionConfigsDataStore.data") }, + produceFile = { + appContext.dataStoreFile("firebaseSessions/sessionConfigsDataStore.data").also { + prepDataStoreFile(it) + } + }, ) @Provides @@ -164,7 +171,11 @@ internal interface FirebaseSessionsComponent { sessionDataSerializer.defaultValue }, scope = CoroutineScope(blockingDispatcher), - produceFile = { appContext.dataStoreFile("aqs/sessionDataStore.data") }, + produceFile = { + appContext.dataStoreFile("firebaseSessions/sessionDataStore.data").also { + prepDataStoreFile(it) + } + }, ) private fun createDataStore( @@ -197,6 +208,43 @@ internal interface FirebaseSessionsComponent { } catch (_: SecurityException) { false } + + /** + * Prepares the DataStore file by ensuring its parent directory exists. Throws [IOException] + * if the directory could not be created, or if a conflicting file could not be removed. + */ + private fun prepDataStoreFile(dataStoreFile: File) { + val parentDir = dataStoreFile.parentFile ?: return + + // Check if something exists at the path, but isn't a directory + if (parentDir.exists() && !parentDir.isDirectory) { + // Only delete it if it's the specific file we know we can safely remove + if (parentDir.name == "firebaseSessions") { + if (!parentDir.delete()) { + throw IOException("Failed to delete conflicting file: $parentDir") + } + } + } + + if (parentDir.isDirectory) { + return + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + try { + Files.createDirectories(parentDir.toPath()) + } catch (ex: Exception) { + throw IOException("Failed to create directory: $parentDir", ex) + } + } else { + if (!parentDir.mkdirs()) { + // It's possible another thread created it in the meantime, so we double-check + if (!parentDir.isDirectory) { + throw IOException("Failed to create directory: $parentDir") + } + } + } + } } } }