Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add handling of content URIs in Filesystem.stat() and Filesystem.readdir() #2078

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,13 +1,20 @@
package com.capacitorjs.plugins.filesystem;

import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.provider.DocumentsContract;
import android.util.Base64;
import com.capacitorjs.plugins.filesystem.exceptions.CopyFailedException;
import com.capacitorjs.plugins.filesystem.exceptions.DirectoryExistsException;
import com.capacitorjs.plugins.filesystem.exceptions.DirectoryNotFoundException;
import com.getcapacitor.Bridge;
import com.getcapacitor.JSArray;
import com.getcapacitor.JSObject;
import com.getcapacitor.PluginCall;
import com.getcapacitor.plugin.util.CapacitorHttpUrlConnection;
Expand Down Expand Up @@ -100,6 +107,61 @@ public File[] readdir(String path, String directory) throws DirectoryNotFoundExc
return files;
}

public void readdir_content_uri(final PluginCall call, final Uri uri, boolean callback) {
ContentResolver resolver = context.getContentResolver();

Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri));
String[] projections = {
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE,
DocumentsContract.Document.COLUMN_SIZE,
DocumentsContract.Document.COLUMN_LAST_MODIFIED,
DocumentsContract.Document.COLUMN_DOCUMENT_ID
};
Cursor c = resolver.query(childrenUri, projections, null, null);

if (c == null) {
call.reject("Document does not exist");
return;
}

Looper looper = Looper.myLooper();
if (looper == null) {
looper = Looper.getMainLooper();
}

ContentObserver observer = new ContentObserver(new Handler(looper)) {
@Override
public void onChange(boolean selfChange) {
if (!selfChange) {
c.unregisterContentObserver(this);
}

JSArray filesArray = new JSArray();
while (c.moveToNext()) {
JSObject data = new JSObject();
data.put("name", c.getString(0));
data.put("type", DocumentsContract.Document.MIME_TYPE_DIR.equals(c.getString(1)) ? "directory" : "file");
data.put("size", c.getLong(2));
data.put("mtime", c.getLong(3));
data.put("ctime", null);
data.put("uri", DocumentsContract.buildDocumentUriUsingTree(uri, c.getString(4)));
filesArray.put(data);
}

JSObject ret = new JSObject();
ret.put("files", filesArray);
call.resolve(ret);
}
};

if (!callback && c.getExtras().getBoolean(DocumentsContract.EXTRA_LOADING, false)) {
c.registerContentObserver(observer);
} else {
observer.onChange(true);
}
}

public File copy(String from, String directory, String to, String toDirectory, boolean doRename)
throws IOException, CopyFailedException {
if (toDirectory == null) {
Expand Down Expand Up @@ -222,6 +284,7 @@ public File getFileObject(String path, String directory) {
if (u.getScheme() == null || u.getScheme().equals("file")) {
return new File(u.getPath());
}
return null;
}

File androidDirectory = this.getDirectory(directory);
Expand Down
@@ -1,10 +1,12 @@
package com.capacitorjs.plugins.filesystem;

import android.Manifest;
import android.database.Cursor;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.provider.DocumentsContract.Document;
import com.capacitorjs.plugins.filesystem.exceptions.CopyFailedException;
import com.capacitorjs.plugins.filesystem.exceptions.DirectoryExistsException;
import com.capacitorjs.plugins.filesystem.exceptions.DirectoryNotFoundException;
Expand Down Expand Up @@ -270,7 +272,10 @@ public void readdir(PluginCall call) {
String path = call.getString("path");
String directory = getDirectoryParameter(call);

if (isPublicDirectory(directory) && !isStoragePermissionGranted()) {
if (directory == null && isContentURL(path)) {
Uri uri = Uri.parse(path);
implementation.readdir_content_uri(call, uri, false);
} else if (isPublicDirectory(directory) && !isStoragePermissionGranted()) {
requestAllPermissions(call, "permissionCallback");
} else {
try {
Expand Down Expand Up @@ -336,11 +341,31 @@ public void stat(PluginCall call) {
String path = call.getString("path");
String directory = getDirectoryParameter(call);

File fileObject = implementation.getFileObject(path, directory);
if (directory == null && isContentURL(path)) {
String[] projection = {Document.COLUMN_MIME_TYPE, Document.COLUMN_SIZE, Document.COLUMN_LAST_MODIFIED};
Cursor c = getContext().getContentResolver().query(Uri.parse(path), projection, null, null);

if (isPublicDirectory(directory) && !isStoragePermissionGranted()) {
if (c == null) {
call.reject("Document does not exist");
return;
} else if (c.getCount() != 1) {
call.reject("Provider returned unexpected data (file might not exist)");
return;
}

JSObject data = new JSObject();
c.moveToNext();
data.put("type", Document.MIME_TYPE_DIR.equals(c.getString(0)) ? "directory" : "file");
data.put("size", c.getLong(1));
data.put("mtime", c.getLong(2));
data.put("ctime", null);
data.put("uri", path);

call.resolve(data);
} else if (isPublicDirectory(directory) && !isStoragePermissionGranted()) {
requestAllPermissions(call, "permissionCallback");
} else {
File fileObject = implementation.getFileObject(path, directory);
if (!fileObject.exists()) {
call.reject("File does not exist");
return;
Expand Down Expand Up @@ -510,6 +535,11 @@ private void permissionCallback(PluginCall call) {
}
}

private boolean isContentURL(String path) {
Uri u = Uri.parse(path);
return "content".equals(u.getScheme());
}

/**
* Checks the the given permission is granted or not
* @return Returns true if the app is running on Android 30 or newer or if the permission is already granted
Expand Down