From d1a44e7d2333464dfde9c917a5d1fc33885152b7 Mon Sep 17 00:00:00 2001 From: Matthew Robertson Date: Mon, 6 Oct 2025 14:37:54 -0400 Subject: [PATCH 1/3] Prepare the DataStore files early --- .../sessions/FirebaseSessionsComponent.kt | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) 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..80c04513194 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 = { + prepDataStoreFile( + appContext.dataStoreFile("firebaseSessions/sessionConfigsDataStore.data") + ) + }, ) @Provides @@ -164,7 +171,9 @@ internal interface FirebaseSessionsComponent { sessionDataSerializer.defaultValue }, scope = CoroutineScope(blockingDispatcher), - produceFile = { appContext.dataStoreFile("aqs/sessionDataStore.data") }, + produceFile = { + prepDataStoreFile(appContext.dataStoreFile("firebaseSessions/sessionDataStore.data")) + }, ) private fun createDataStore( @@ -197,6 +206,45 @@ 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): File { + val parentDir = dataStoreFile.parentFile ?: return dataStoreFile + + // 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 dataStoreFile + } + + 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") + } + } + } + + return dataStoreFile + } } } } From 40b32e8f8f3cc24402d6625532b472c67eac8709 Mon Sep 17 00:00:00 2001 From: Matthew Robertson Date: Mon, 6 Oct 2025 14:48:37 -0400 Subject: [PATCH 2/3] Add changelog --- firebase-crashlytics/CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) 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. From 201dfc71801586edf239e4f6a298047489ce3049 Mon Sep 17 00:00:00 2001 From: Matthew Robertson Date: Mon, 6 Oct 2025 17:26:31 -0400 Subject: [PATCH 3/3] Address feedback --- .../sessions/FirebaseSessionsComponent.kt | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) 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 80c04513194..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 @@ -150,9 +150,9 @@ internal interface FirebaseSessionsComponent { }, scope = CoroutineScope(blockingDispatcher), produceFile = { - prepDataStoreFile( - appContext.dataStoreFile("firebaseSessions/sessionConfigsDataStore.data") - ) + appContext.dataStoreFile("firebaseSessions/sessionConfigsDataStore.data").also { + prepDataStoreFile(it) + } }, ) @@ -172,7 +172,9 @@ internal interface FirebaseSessionsComponent { }, scope = CoroutineScope(blockingDispatcher), produceFile = { - prepDataStoreFile(appContext.dataStoreFile("firebaseSessions/sessionDataStore.data")) + appContext.dataStoreFile("firebaseSessions/sessionDataStore.data").also { + prepDataStoreFile(it) + } }, ) @@ -211,8 +213,8 @@ internal interface FirebaseSessionsComponent { * 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): File { - val parentDir = dataStoreFile.parentFile ?: return dataStoreFile + 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) { @@ -225,7 +227,7 @@ internal interface FirebaseSessionsComponent { } if (parentDir.isDirectory) { - return dataStoreFile + return } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -242,8 +244,6 @@ internal interface FirebaseSessionsComponent { } } } - - return dataStoreFile } } }