Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DocumentFile.exists() on a path that doesn't yet exist always returns true due to DocumentFile().fromTreeUri()

I'm trying to check if a file exists prior to creating it, using DocumentFile (due to the Storage Access Framework). But DocumentFile().fromTreeUri() removes the non-existant portion of the would-be Uri, which then causes DocumentFile().exists() to always return true, regardless of whether it exists or not.

I've created a simple example to demonstrate my point. First we ask the user to select a directory:

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    
    // Ask the user for the source folder
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    startActivityForResult(intent, 100);
}

On response, we add /fictionalFile to the path (thus making it a non-existent file), and then check if it exists:

public void onActivityResult(int requestCode, int resultCode, Intent resultData)
{
    if (resultCode == RESULT_OK)
    {
        if(requestCode == 100)
        {
            Uri fictionalURI = Uri.parse(resultData.getData()+"/fictionalFile");
            DocumentFile fictionalFile = DocumentFile.fromTreeUri(this, fictionalURI);
            Log.i("STORAGE", "FICTIONAL URI: "+fictionalURI);
            Log.i("STORAGE", "FICTIONAL DOCUMENTFILE URI: "+fictionalFile.getUri());

            if(fictionalFile.exists())
            {
                Log.i("STORAGE", "Fictional file exists");
            }
        }
    }
}

However, when DocumentFile.fromTreeUri() is run on the fictional Uri, the fake "/fictionalfile" portion is lost, which then causes the DocumentFile.exists() function to return true, as shown by the below LogCat:

I/STORAGE: FICTIONAL URI: content://com.android.externalstorage.documents/tree/17FA-1C18%3AFileSync%2Ftarget/fictionalFile

I/STORAGE: FICTIONAL DOCUMENTFILE URI: content://com.android.externalstorage.documents/tree/17FA-1C18%3AFileSync%2Ftarget/document/17FA-1C18%3AFileSync%2Ftarget

I/STORAGE: Fictional file exists

(In the above example, I'm using the SD card, thus the long path names)

Is there another way to check if a not-yet created DocumentFile exists? The use case is that when copying a file from Directory A to Directory B, I want to check if said file already exists in Directory B prior to starting the transfer.

UPDATE: I've now realised that using DocumentFile.fromTreeUri() is wrong, and that I should be using DocumentFile.fromSingleUri(). This helps, but upon running .exists() on the new file I am getting W/DocumentFile: Failed query: java.lang.UnsupportedOperationException: Unsupported Uri content://com.android.externalstorage.documents/tree/17FA-1C18%3AFileSync%2Ftarget/fictionalFile. Any thoughts?

public void onActivityResult(int requestCode, int resultCode, Intent resultData)
{
    if (resultCode == RESULT_OK)
    {
        if(requestCode == 100)
        {
            Uri fictionalURI = Uri.parse(resultData.getData()+"/fictionalFile");
            DocumentFile fictionalFile = DocumentFile.fromSingleUri(this, fictionalURI);
            Log.i("STORAGE", "FICTIONAL URI: "+fictionalURI);
            Log.i("STORAGE", "FICTIONAL DOCUMENTFILE URI: "+fictionalFile.getUri());

            if(fictionalFile != null && fictionalFile.exists())
            {
                Log.i("STORAGE", "Fictional file exists");
            }
        }
    }
}
like image 212
Duncan McArdle Avatar asked Aug 02 '18 20:08

Duncan McArdle


2 Answers

Given treeUri as being the Uri returned by ACTION_OPEN_DOCUMENT_TREE, wrap treeUri in a DocumentFile using fromTreeUri(), then call findFile() on that DocumentFile supplying the display name that you are looking for (e.g., fictionalFile). If it returns null, there is no file matching that display name.

IOW:

if (DocumentFile.fromTreeUri(this, treeUri).findFile(whatevs) == null) {
  // TODO: something
}

Note, though, that "display name" is not necessarily a filename.

like image 68
CommonsWare Avatar answered Oct 11 '22 01:10

CommonsWare


In my case, when I checked DocumentFile.fromSingleUri(context, media.fileUri)?.exists() == true in CoroutineWorker with applicationContext, DocumentProvider always returned true, when file was deleted from outside application, for example, in file manager.

The problem was solved with uri.isFileExist(context)

import android.content.Context
import android.net.Uri
import androidx.documentfile.provider.DocumentFile

fun DocumentFile?.isFile() = this?.isFile ?: false

fun DocumentFile?.isExists() = this?.exists() ?: false

fun DocumentFile?.getLength() = this?.length() ?: 0

fun DocumentFile?.isFileExist() = isFile() && isExists() && getLength() > 0

fun Uri.isFileExist(context: Context) =
    DocumentFile.fromSingleUri(context, this).isFileExist()

UPDATE: The solution below works correctly on android 10+. I ended up replacing it to get it working properly on all versions of android.

fun Uri.isFileExist(context: Context): Boolean {
    var isExist = false
    runCatching {
        context.contentResolver.openInputStream(this)?.let {
             it.close()
             isExist = true
        }
    }
    return isExist
}
like image 20
Pavel Shirokov Avatar answered Oct 10 '22 23:10

Pavel Shirokov