google / modernstorage

ModernStorage is a group of libraries that provide an abstraction layer over storage on Android to simplify its interactions
https://google.github.io/modernstorage/
Apache License 2.0
1.24k stars 60 forks source link

SQLiteConstraintException in createMediaStoreUri #82

Open SteinerOk opened 2 years ago

SteinerOk commented 2 years ago

Describe the bug

I'm using modernstorage to save a file from the web to my Downloads folder. In certain situations, the following error occurs:

java.util.concurrent.ExecutionException: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: files._data (code 2067 SQLITE_CONSTRAINT_UNIQUE)
        at androidx.work.impl.utils.futures.AbstractFuture.getDoneValue(AbstractFuture.java:516)
        at androidx.work.impl.utils.futures.AbstractFuture.get(AbstractFuture.java:475)
        at androidx.work.impl.WorkerWrapper$2.run(WorkerWrapper.java:321)
        at androidx.work.impl.utils.SerialExecutor$Task.run(SerialExecutor.java:91)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:923)
     Caused by: android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: files._data (code 2067 SQLITE_CONSTRAINT_UNIQUE)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:178)
        at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:142)
        at android.content.ContentProviderProxy.insert(ContentProviderNative.java:549)
        at android.content.ContentResolver.insert(ContentResolver.java:2149)
        at android.content.ContentResolver.insert(ContentResolver.java:2111)
        at com.google.modernstorage.storage.SharedFileSystem.createMediaStoreUri(SharedFileSystem.kt:266)

To Reproduce

As I managed to find out, this happens if you download a file in the application, then clear the application data and try to download the same file again.

Environment:

Additional context

Some peaces of code, witch I use:

        val destPath = fileSystem.createMediaStoreUri(
            filename = depFileName,
            directory = getExternalStorageDir().absolutePath
        )?.toPath()
    private fun getExternalStorageDir(): File {
        return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
            applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)!!
        } else {
            @Suppress("DEPRECATION")
            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
        }
    }
yrezgui commented 2 years ago

Let me give a try to reproduce the issue. I might escalate the problem with the Android storage engineers if it's not related to ModernStorage

yrezgui commented 2 years ago

So I investigated a bit but wasn't able to reproduce the same issue you're getting (at least on an emulator).

Two things to note though, your getExternalStorageDir is pointing to two different folders. The first one is the app's own Download external folder (which isn't visible by other apps since Android 11, was never supposed to be public to be honest) and the second one is the shared Download folder (visible with a file manager):

# applicationContext.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
/storage/emulated/0/Android/data/com.google.modernstorage.storage.test/files/Download

# Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
/storage/emulated/0/Download

On Android 11 (I haven't tried yet on previous versions), it doesn't let me write to the app's own Download external folder. It doesn't trigger an exception but change the target folder by putting it in the device shared Download folder. I've tried setting the same file name but end up having MediaStore adding a suffix automatically:

# First try
uri = content://media/external/file/65
path = /storage/emulated/0/Download/samefile.pdf

# Second try
uri = content://media/external/file/66
path = /storage/emulated/0/Download/samefile (1).pdf

Could you share me a snippet adding the same file twice from your project just to make sure I'm reproducing it in the same way? Thanks!