mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-03 16:10:00 +00:00
Merge pull request #7344 from hayaksi1/fix/6371-save-file-name-conflicts
Keep saving files to Downloads once 32 files share the same name
This commit is contained in:
+13
-11
@@ -32,6 +32,7 @@ import androidx.core.content.PermissionChecker
|
||||
import androidx.core.net.toFile
|
||||
import dev.zacsweers.metro.AppScope
|
||||
import dev.zacsweers.metro.ContributesBinding
|
||||
import io.element.android.libraries.androidutils.file.saveWithUniqueFileName
|
||||
import io.element.android.libraries.androidutils.system.startInstallFromSourceIntent
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||
@@ -44,6 +45,7 @@ import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
@@ -172,18 +174,18 @@ class AndroidLocalMediaActions(
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.Q)
|
||||
private fun saveOnDiskUsingMediaStore(localMedia: LocalMedia) {
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, localMedia.info.filename)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, localMedia.info.mimeType)
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||
}
|
||||
val resolver = context.contentResolver
|
||||
val outputUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
||||
if (outputUri != null) {
|
||||
localMedia.openStream()?.use { input ->
|
||||
resolver.openOutputStream(outputUri).use { output ->
|
||||
input.copyTo(output!!, DEFAULT_BUFFER_SIZE)
|
||||
}
|
||||
val outputUri = saveWithUniqueFileName(localMedia.info.filename) { displayName ->
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, displayName)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, localMedia.info.mimeType)
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
||||
}
|
||||
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
||||
} ?: throw IOException("Unable to create the destination file")
|
||||
localMedia.openStream()?.use { input ->
|
||||
resolver.openOutputStream(outputUri).use { output ->
|
||||
input.copyTo(output!!, DEFAULT_BUFFER_SIZE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+41
@@ -12,18 +12,22 @@ import android.net.Uri
|
||||
import androidx.activity.compose.LocalActivityResultRegistryOwner
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.net.toUri
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia
|
||||
import io.element.android.tests.testutils.fake.registerFakeMediaStoreContentProvider
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class AndroidLocalMediaActionsTest : RobolectricTest() {
|
||||
@Test
|
||||
@@ -62,6 +66,43 @@ class AndroidLocalMediaActionsTest : RobolectricTest() {
|
||||
assertThat(result.exceptionOrNull()).isNotNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test AndroidLocalMediaAction save on disk inserts the media under its own name`() = runTest {
|
||||
val mediaStore = registerFakeMediaStoreContentProvider()
|
||||
val sut = createAndroidLocalMediaActions()
|
||||
val result = sut.saveOnDisk(aLocalMedia(aMediaFile().toUri()))
|
||||
assertThat(result.exceptionOrNull()).isNull()
|
||||
assertThat(mediaStore.insertedDisplayNames).containsExactly("an image file.jpg")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test AndroidLocalMediaAction save on disk retries with a suffixed name when the name cannot be made unique`() = runTest {
|
||||
val mediaStore = registerFakeMediaStoreContentProvider().apply {
|
||||
conflictingDisplayNames = setOf("an image file.jpg")
|
||||
}
|
||||
val sut = createAndroidLocalMediaActions()
|
||||
val result = sut.saveOnDisk(aLocalMedia(aMediaFile().toUri()))
|
||||
assertThat(result.exceptionOrNull()).isNull()
|
||||
assertThat(mediaStore.insertedDisplayNames).hasSize(2)
|
||||
assertThat(mediaStore.insertedDisplayNames.last()).matches("an image file_\\d+\\.jpg")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test AndroidLocalMediaAction save on disk fails when the destination cannot be created`() = runTest {
|
||||
registerFakeMediaStoreContentProvider().apply {
|
||||
returnsNoUriOnInsert = true
|
||||
}
|
||||
val sut = createAndroidLocalMediaActions()
|
||||
val result = sut.saveOnDisk(aLocalMedia(aMediaFile().toUri()))
|
||||
assertThat(result.exceptionOrNull()).isInstanceOf(IOException::class.java)
|
||||
}
|
||||
|
||||
private fun aMediaFile(): File {
|
||||
return File(RuntimeEnvironment.getApplication().cacheDir, "an image file.jpg").apply {
|
||||
writeText("Some content")
|
||||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createAndroidLocalMediaActions() = AndroidLocalMediaActions(
|
||||
RuntimeEnvironment.getApplication(),
|
||||
testCoroutineDispatchers(),
|
||||
|
||||
Reference in New Issue
Block a user