Common/FileSystem: Make file functions content URI-aware
This commit is contained in:
parent
03f3f0369c
commit
d6d8d21eff
|
@ -970,7 +970,8 @@ extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||||
|
|
||||||
jclass emulation_activity_class;
|
jclass emulation_activity_class;
|
||||||
if ((s_AndroidHostInterface_constructor =
|
if ((s_AndroidHostInterface_constructor =
|
||||||
env->GetMethodID(s_AndroidHostInterface_class, "<init>", "(Landroid/content/Context;)V")) == nullptr ||
|
env->GetMethodID(s_AndroidHostInterface_class, "<init>",
|
||||||
|
"(Landroid/content/Context;Lcom/github/stenzek/duckstation/FileHelper;)V")) == nullptr ||
|
||||||
(s_AndroidHostInterface_field_mNativePointer =
|
(s_AndroidHostInterface_field_mNativePointer =
|
||||||
env->GetFieldID(s_AndroidHostInterface_class, "mNativePointer", "J")) == nullptr ||
|
env->GetFieldID(s_AndroidHostInterface_class, "mNativePointer", "J")) == nullptr ||
|
||||||
(s_AndroidHostInterface_method_reportError =
|
(s_AndroidHostInterface_method_reportError =
|
||||||
|
@ -1067,12 +1068,13 @@ DEFINE_JNI_ARGS_METHOD(void, AndroidHostInterface_setThreadAffinity, jobject unu
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, jobject context_object,
|
DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, jobject context_object,
|
||||||
jstring user_directory)
|
jobject file_helper_object, jstring user_directory)
|
||||||
{
|
{
|
||||||
Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEBUG);
|
Log::SetDebugOutputParams(true, nullptr, LOGLEVEL_DEBUG);
|
||||||
|
|
||||||
// initialize the java side
|
// initialize the java side
|
||||||
jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor, context_object);
|
jobject java_obj = env->NewObject(s_AndroidHostInterface_class, s_AndroidHostInterface_constructor, context_object,
|
||||||
|
file_helper_object);
|
||||||
if (!java_obj)
|
if (!java_obj)
|
||||||
{
|
{
|
||||||
Log_ErrorPrint("Failed to create Java AndroidHostInterface");
|
Log_ErrorPrint("Failed to create Java AndroidHostInterface");
|
||||||
|
@ -1096,6 +1098,7 @@ DEFINE_JNI_ARGS_METHOD(jobject, AndroidHostInterface_create, jobject unused, job
|
||||||
env->SetLongField(java_obj, s_AndroidHostInterface_field_mNativePointer,
|
env->SetLongField(java_obj, s_AndroidHostInterface_field_mNativePointer,
|
||||||
static_cast<long>(reinterpret_cast<uintptr_t>(cpp_obj)));
|
static_cast<long>(reinterpret_cast<uintptr_t>(cpp_obj)));
|
||||||
|
|
||||||
|
FileSystem::SetAndroidFileHelper(s_jvm, env, file_helper_object);
|
||||||
return java_obj;
|
return java_obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1195,7 +1198,7 @@ DEFINE_JNI_ARGS_METHOD(jint, AndroidHostInterface_getControllerAxisType, jobject
|
||||||
jstring axis_name)
|
jstring axis_name)
|
||||||
{
|
{
|
||||||
std::optional<ControllerType> type =
|
std::optional<ControllerType> type =
|
||||||
Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str());
|
Settings::ParseControllerTypeName(AndroidHelpers::JStringToString(env, controller_type).c_str());
|
||||||
if (!type)
|
if (!type)
|
||||||
return -1;
|
return -1;
|
||||||
|
|
||||||
|
@ -1342,7 +1345,7 @@ static jobject CreateGameListEntry(JNIEnv* env, AndroidHostInterface* hi, const
|
||||||
{
|
{
|
||||||
const Timestamp modified_ts(
|
const Timestamp modified_ts(
|
||||||
Timestamp::FromUnixTimestamp(static_cast<Timestamp::UnixTimestampValue>(entry.last_modified_time)));
|
Timestamp::FromUnixTimestamp(static_cast<Timestamp::UnixTimestampValue>(entry.last_modified_time)));
|
||||||
const std::string file_title_str(System::GetTitleForPath(entry.path.c_str()));
|
const std::string file_title_str(FileSystem::GetFileTitleFromPath(entry.path));
|
||||||
const std::string cover_path_str(hi->GetGameList()->GetCoverImagePathForEntry(&entry));
|
const std::string cover_path_str(hi->GetGameList()->GetCoverImagePathForEntry(&entry));
|
||||||
|
|
||||||
jstring path = env->NewStringUTF(entry.path.c_str());
|
jstring path = env->NewStringUTF(entry.path.c_str());
|
||||||
|
|
|
@ -23,9 +23,11 @@ public class AndroidHostInterface {
|
||||||
|
|
||||||
private long mNativePointer;
|
private long mNativePointer;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
|
private FileHelper mFileHelper;
|
||||||
|
|
||||||
public AndroidHostInterface(Context context) {
|
public AndroidHostInterface(Context context, FileHelper fileHelper) {
|
||||||
this.mContext = context;
|
this.mContext = context;
|
||||||
|
this.mFileHelper = fileHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reportError(String message) {
|
public void reportError(String message) {
|
||||||
|
@ -54,7 +56,7 @@ public class AndroidHostInterface {
|
||||||
|
|
||||||
static public native boolean setThreadAffinity(int[] cpus);
|
static public native boolean setThreadAffinity(int[] cpus);
|
||||||
|
|
||||||
static public native AndroidHostInterface create(Context context, String userDirectory);
|
static public native AndroidHostInterface create(Context context, FileHelper fileHelper, String userDirectory);
|
||||||
|
|
||||||
public native boolean isEmulationThreadRunning();
|
public native boolean isEmulationThreadRunning();
|
||||||
|
|
||||||
|
@ -184,7 +186,7 @@ public class AndroidHostInterface {
|
||||||
|
|
||||||
mUserDirectory += "/duckstation";
|
mUserDirectory += "/duckstation";
|
||||||
Log.i("AndroidHostInterface", "User directory: " + mUserDirectory);
|
Log.i("AndroidHostInterface", "User directory: " + mUserDirectory);
|
||||||
mInstance = create(context, mUserDirectory);
|
mInstance = create(context, new FileHelper(context), mUserDirectory);
|
||||||
return mInstance != null;
|
return mInstance != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
package com.github.stenzek.duckstation;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File helper class - used to bridge native code to Java storage access framework APIs.
|
||||||
|
*/
|
||||||
|
public class FileHelper {
|
||||||
|
/**
|
||||||
|
* Native filesystem flags.
|
||||||
|
*/
|
||||||
|
public static final int FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY = 1;
|
||||||
|
public static final int FILESYSTEM_FILE_ATTRIBUTE_READ_ONLY = 2;
|
||||||
|
public static final int FILESYSTEM_FILE_ATTRIBUTE_COMPRESSED = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Native filesystem find result flags.
|
||||||
|
*/
|
||||||
|
public static final int FILESYSTEM_FIND_RECURSIVE = (1 << 0);
|
||||||
|
public static final int FILESYSTEM_FIND_RELATIVE_PATHS = (1 << 1);
|
||||||
|
public static final int FILESYSTEM_FIND_HIDDEN_FILES = (1 << 2);
|
||||||
|
public static final int FILESYSTEM_FIND_FOLDERS = (1 << 3);
|
||||||
|
public static final int FILESYSTEM_FIND_FILES = (1 << 4);
|
||||||
|
public static final int FILESYSTEM_FIND_KEEP_ARRAY = (1 << 5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Projection used when searching for files.
|
||||||
|
*/
|
||||||
|
private static final String[] findProjection = new String[]{
|
||||||
|
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||||
|
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
|
||||||
|
DocumentsContract.Document.COLUMN_MIME_TYPE,
|
||||||
|
DocumentsContract.Document.COLUMN_SIZE,
|
||||||
|
DocumentsContract.Document.COLUMN_LAST_MODIFIED
|
||||||
|
};
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private final ContentResolver contentResolver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* File helper class - used to bridge native code to Java storage access framework APIs.
|
||||||
|
* @param context Context in which to perform file actions as.
|
||||||
|
*/
|
||||||
|
public FileHelper(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
this.contentResolver = context.getContentResolver();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a file descriptor for a content URI string. Called by native code.
|
||||||
|
* @param uriString string of the URI to open
|
||||||
|
* @param mode Java open mode
|
||||||
|
* @return file descriptor for URI, or -1
|
||||||
|
*/
|
||||||
|
public int openURIAsFileDescriptor(String uriString, String mode) {
|
||||||
|
try {
|
||||||
|
final Uri uri = Uri.parse(uriString);
|
||||||
|
final ParcelFileDescriptor fd = contentResolver.openFileDescriptor(uri, mode);
|
||||||
|
if (fd == null)
|
||||||
|
return -1;
|
||||||
|
return fd.detachFd();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively iterates documents in the specified tree, searching for files.
|
||||||
|
* @param treeUri Root tree in which to search for documents.
|
||||||
|
* @param documentId Document ID representing the directory to start searching.
|
||||||
|
* @param flags Native search flags.
|
||||||
|
* @param results Cumulative result array.
|
||||||
|
*/
|
||||||
|
private void doFindFiles(Uri treeUri, String documentId, int flags, ArrayList<FindResult> results) {
|
||||||
|
try {
|
||||||
|
final Uri queryUri = DocumentsContract.buildChildDocumentsUriUsingTree(treeUri, documentId);
|
||||||
|
final Cursor cursor = contentResolver.query(queryUri, findProjection, null, null, null);
|
||||||
|
final int count = cursor.getCount();
|
||||||
|
|
||||||
|
while (cursor.moveToNext()) {
|
||||||
|
try {
|
||||||
|
final String mimeType = cursor.getString(2);
|
||||||
|
final String childDocumentId = cursor.getString(0);
|
||||||
|
final Uri uri = DocumentsContract.buildDocumentUriUsingTree(treeUri, childDocumentId);
|
||||||
|
final long size = cursor.getLong(3);
|
||||||
|
final long lastModified = cursor.getLong(4);
|
||||||
|
|
||||||
|
if (DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType)) {
|
||||||
|
if ((flags & FILESYSTEM_FIND_FOLDERS) != 0) {
|
||||||
|
results.add(new FindResult(childDocumentId, uri.toString(), size, lastModified, FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((flags & FILESYSTEM_FIND_RECURSIVE) != 0)
|
||||||
|
doFindFiles(treeUri, childDocumentId, flags, results);
|
||||||
|
} else {
|
||||||
|
if ((flags & FILESYSTEM_FIND_FILES) != 0) {
|
||||||
|
results.add(new FindResult(childDocumentId, uri.toString(), size, lastModified, 0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively iterates documents in the specified URI, searching for files.
|
||||||
|
* @param uriString URI containing directory to search.
|
||||||
|
* @param flags Native filter flags.
|
||||||
|
* @return Array of find results.
|
||||||
|
*/
|
||||||
|
public FindResult[] findFiles(String uriString, int flags) {
|
||||||
|
try {
|
||||||
|
final Uri fullUri = Uri.parse(uriString);
|
||||||
|
final String documentId = DocumentsContract.getTreeDocumentId(fullUri);
|
||||||
|
final ArrayList<FindResult> results = new ArrayList<>();
|
||||||
|
doFindFiles(fullUri, documentId, flags, results);
|
||||||
|
if (results.isEmpty())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
final FindResult[] resultsArray = new FindResult[results.size()];
|
||||||
|
results.toArray(resultsArray);
|
||||||
|
return resultsArray;
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Java class containing the data for a file in a find operation.
|
||||||
|
*/
|
||||||
|
public static class FindResult {
|
||||||
|
public String name;
|
||||||
|
public String relativeName;
|
||||||
|
public long size;
|
||||||
|
public long modifiedTime;
|
||||||
|
public int flags;
|
||||||
|
|
||||||
|
public FindResult(String relativeName, String name, long size, long modifiedTime, int flags) {
|
||||||
|
this.relativeName = relativeName;
|
||||||
|
this.name = name;
|
||||||
|
this.size = size;
|
||||||
|
this.modifiedTime = modifiedTime;
|
||||||
|
this.flags = flags;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -30,10 +30,257 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
#include <jni.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
Log_SetChannel(FileSystem);
|
Log_SetChannel(FileSystem);
|
||||||
|
|
||||||
namespace FileSystem {
|
namespace FileSystem {
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
|
||||||
|
static JavaVM* s_android_jvm;
|
||||||
|
static jobject s_android_FileHelper_object;
|
||||||
|
static jclass s_android_FileHelper_class;
|
||||||
|
static jmethodID s_android_FileHelper_openURIAsFileDescriptor;
|
||||||
|
static jmethodID s_android_FileHelper_FindFiles;
|
||||||
|
static jclass s_android_FileHelper_FindResult_class;
|
||||||
|
static jfieldID s_android_FileHelper_FindResult_name;
|
||||||
|
static jfieldID s_android_FileHelper_FindResult_relativeName;
|
||||||
|
static jfieldID s_android_FileHelper_FindResult_size;
|
||||||
|
static jfieldID s_android_FileHelper_FindResult_modifiedTime;
|
||||||
|
static jfieldID s_android_FileHelper_FindResult_flags;
|
||||||
|
|
||||||
|
// helper for retrieving the current per-thread jni environment
|
||||||
|
static JNIEnv* GetJNIEnv()
|
||||||
|
{
|
||||||
|
JNIEnv* env;
|
||||||
|
if (s_android_jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK)
|
||||||
|
return nullptr;
|
||||||
|
else
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsUriPath(const std::string_view& path)
|
||||||
|
{
|
||||||
|
return StringUtil::StartsWith(path, "content:/") || StringUtil::StartsWith(path, "file:/");
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool UriHelpersAreAvailable()
|
||||||
|
{
|
||||||
|
return (s_android_FileHelper_object != nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetAndroidFileHelper(void* jvm, void* env, void* object)
|
||||||
|
{
|
||||||
|
Assert(!jvm || !s_android_jvm || s_android_jvm == jvm);
|
||||||
|
|
||||||
|
if (s_android_FileHelper_object)
|
||||||
|
{
|
||||||
|
JNIEnv* jenv = GetJNIEnv();
|
||||||
|
jenv->DeleteGlobalRef(s_android_FileHelper_FindResult_class);
|
||||||
|
s_android_FileHelper_FindResult_name = {};
|
||||||
|
s_android_FileHelper_FindResult_relativeName = {};
|
||||||
|
s_android_FileHelper_FindResult_size = {};
|
||||||
|
s_android_FileHelper_FindResult_modifiedTime = {};
|
||||||
|
s_android_FileHelper_FindResult_flags = {};
|
||||||
|
s_android_FileHelper_FindResult_class = {};
|
||||||
|
|
||||||
|
jenv->DeleteGlobalRef(s_android_FileHelper_object);
|
||||||
|
jenv->DeleteGlobalRef(s_android_FileHelper_class);
|
||||||
|
s_android_FileHelper_openURIAsFileDescriptor = {};
|
||||||
|
s_android_FileHelper_FindFiles = {};
|
||||||
|
s_android_FileHelper_object = {};
|
||||||
|
s_android_FileHelper_class = {};
|
||||||
|
s_android_jvm = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!object)
|
||||||
|
return;
|
||||||
|
|
||||||
|
JNIEnv* jenv = static_cast<JNIEnv*>(env);
|
||||||
|
s_android_jvm = static_cast<JavaVM*>(jvm);
|
||||||
|
s_android_FileHelper_object = jenv->NewGlobalRef(static_cast<jobject>(object));
|
||||||
|
Assert(s_android_FileHelper_object);
|
||||||
|
|
||||||
|
jclass fh_class = jenv->GetObjectClass(static_cast<jobject>(object));
|
||||||
|
s_android_FileHelper_class = static_cast<jclass>(jenv->NewGlobalRef(fh_class));
|
||||||
|
Assert(s_android_FileHelper_class);
|
||||||
|
jenv->DeleteLocalRef(fh_class);
|
||||||
|
|
||||||
|
s_android_FileHelper_openURIAsFileDescriptor =
|
||||||
|
jenv->GetMethodID(s_android_FileHelper_class, "openURIAsFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I");
|
||||||
|
s_android_FileHelper_FindFiles =
|
||||||
|
jenv->GetMethodID(s_android_FileHelper_class, "findFiles",
|
||||||
|
"(Ljava/lang/String;I)[Lcom/github/stenzek/duckstation/FileHelper$FindResult;");
|
||||||
|
Assert(s_android_FileHelper_openURIAsFileDescriptor && s_android_FileHelper_FindFiles);
|
||||||
|
|
||||||
|
jclass fr_class = jenv->FindClass("com/github/stenzek/duckstation/FileHelper$FindResult");
|
||||||
|
Assert(fr_class);
|
||||||
|
s_android_FileHelper_FindResult_class = static_cast<jclass>(jenv->NewGlobalRef(fr_class));
|
||||||
|
Assert(s_android_FileHelper_FindResult_class);
|
||||||
|
jenv->DeleteLocalRef(fr_class);
|
||||||
|
|
||||||
|
s_android_FileHelper_FindResult_relativeName =
|
||||||
|
jenv->GetFieldID(s_android_FileHelper_FindResult_class, "relativeName", "Ljava/lang/String;");
|
||||||
|
s_android_FileHelper_FindResult_name =
|
||||||
|
jenv->GetFieldID(s_android_FileHelper_FindResult_class, "name", "Ljava/lang/String;");
|
||||||
|
s_android_FileHelper_FindResult_size = jenv->GetFieldID(s_android_FileHelper_FindResult_class, "size", "J");
|
||||||
|
s_android_FileHelper_FindResult_modifiedTime =
|
||||||
|
jenv->GetFieldID(s_android_FileHelper_FindResult_class, "modifiedTime", "J");
|
||||||
|
s_android_FileHelper_FindResult_flags = jenv->GetFieldID(s_android_FileHelper_FindResult_class, "flags", "I");
|
||||||
|
Assert(s_android_FileHelper_FindResult_relativeName && s_android_FileHelper_FindResult_name &&
|
||||||
|
s_android_FileHelper_FindResult_size && s_android_FileHelper_FindResult_modifiedTime &&
|
||||||
|
s_android_FileHelper_FindResult_flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::FILE* OpenUriFile(const char* path, const char* mode)
|
||||||
|
{
|
||||||
|
// translate C modes to Java modes
|
||||||
|
TinyString mode_trimmed;
|
||||||
|
std::size_t mode_len = std::strlen(mode);
|
||||||
|
for (size_t i = 0; i < mode_len; i++)
|
||||||
|
{
|
||||||
|
if (mode[i] == 'r' || mode[i] == 'w' || mode[i] == '+')
|
||||||
|
mode_trimmed.AppendCharacter(mode[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle append mode by seeking to end.
|
||||||
|
const char* java_mode = nullptr;
|
||||||
|
if (mode_trimmed == "r")
|
||||||
|
java_mode = "r";
|
||||||
|
else if (mode_trimmed == "r+")
|
||||||
|
java_mode = "rw";
|
||||||
|
else if (mode_trimmed == "w")
|
||||||
|
java_mode = "w";
|
||||||
|
else if (mode_trimmed == "w+")
|
||||||
|
java_mode = "rwt";
|
||||||
|
|
||||||
|
if (!java_mode)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Could not translate file mode '%s' ('%s')", mode, mode_trimmed.GetCharArray());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hand off to Java...
|
||||||
|
JNIEnv* env = GetJNIEnv();
|
||||||
|
jstring path_jstr = env->NewStringUTF(path);
|
||||||
|
jstring mode_jstr = env->NewStringUTF(java_mode);
|
||||||
|
int fd =
|
||||||
|
env->CallIntMethod(s_android_FileHelper_object, s_android_FileHelper_openURIAsFileDescriptor, path_jstr, mode_jstr);
|
||||||
|
env->DeleteLocalRef(mode_jstr);
|
||||||
|
env->DeleteLocalRef(path_jstr);
|
||||||
|
|
||||||
|
// Just in case...
|
||||||
|
if (env->ExceptionCheck())
|
||||||
|
{
|
||||||
|
env->ExceptionClear();
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fd < 0)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// Convert to a C file object.
|
||||||
|
std::FILE* fp = fdopen(fd, mode);
|
||||||
|
if (!fp)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("Failed to convert FD %d to C FILE for '%s'.", fd, path);
|
||||||
|
close(fd);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fp;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool FindUriFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* pVector)
|
||||||
|
{
|
||||||
|
if (!s_android_FileHelper_object)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
JNIEnv* env = GetJNIEnv();
|
||||||
|
|
||||||
|
jstring path_jstr = env->NewStringUTF(path);
|
||||||
|
jobjectArray arr = static_cast<jobjectArray>(env->CallObjectMethod(
|
||||||
|
s_android_FileHelper_object, s_android_FileHelper_FindFiles, path_jstr, static_cast<int>(flags)));
|
||||||
|
env->DeleteLocalRef(path_jstr);
|
||||||
|
if (!arr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// small speed optimization for '*' case
|
||||||
|
bool hasWildCards = false;
|
||||||
|
bool wildCardMatchAll = false;
|
||||||
|
u32 nFiles = 0;
|
||||||
|
if (std::strpbrk(pattern, "*?") != nullptr)
|
||||||
|
{
|
||||||
|
hasWildCards = true;
|
||||||
|
wildCardMatchAll = !(std::strcmp(pattern, "*"));
|
||||||
|
}
|
||||||
|
|
||||||
|
jsize count = env->GetArrayLength(arr);
|
||||||
|
for (jsize i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
jobject result = env->GetObjectArrayElement(arr, i);
|
||||||
|
if (!result)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
jstring result_name_obj = static_cast<jstring>(env->GetObjectField(result, s_android_FileHelper_FindResult_name));
|
||||||
|
jstring result_relative_name_obj =
|
||||||
|
static_cast<jstring>(env->GetObjectField(result, s_android_FileHelper_FindResult_relativeName));
|
||||||
|
const u64 result_size = static_cast<u64>(env->GetLongField(result, s_android_FileHelper_FindResult_size));
|
||||||
|
const u64 result_modified_time =
|
||||||
|
static_cast<u64>(env->GetLongField(result, s_android_FileHelper_FindResult_modifiedTime));
|
||||||
|
const u32 result_flags = static_cast<u32>(env->GetIntField(result, s_android_FileHelper_FindResult_flags));
|
||||||
|
|
||||||
|
if (result_name_obj && result_relative_name_obj)
|
||||||
|
{
|
||||||
|
const char* result_name = env->GetStringUTFChars(result_name_obj, nullptr);
|
||||||
|
const char* result_relative_name = env->GetStringUTFChars(result_relative_name_obj, nullptr);
|
||||||
|
if (result_relative_name)
|
||||||
|
{
|
||||||
|
// match the filename
|
||||||
|
bool matched;
|
||||||
|
if (hasWildCards)
|
||||||
|
{
|
||||||
|
matched = wildCardMatchAll || StringUtil::WildcardMatch(result_relative_name, pattern);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
matched = std::strcmp(result_relative_name, pattern) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matched)
|
||||||
|
{
|
||||||
|
FILESYSTEM_FIND_DATA ffd;
|
||||||
|
ffd.FileName = ((flags & FILESYSTEM_FIND_RELATIVE_PATHS) != 0) ? result_relative_name : result_name;
|
||||||
|
ffd.Attributes = result_flags;
|
||||||
|
ffd.ModificationTime.SetUnixTimestamp(result_modified_time);
|
||||||
|
ffd.Size = result_size;
|
||||||
|
pVector->push_back(std::move(ffd));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result_name)
|
||||||
|
env->ReleaseStringUTFChars(result_name_obj, result_name);
|
||||||
|
if (result_relative_name)
|
||||||
|
env->ReleaseStringUTFChars(result_relative_name_obj, result_relative_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result_name_obj)
|
||||||
|
env->DeleteLocalRef(result_name_obj);
|
||||||
|
if (result_relative_name_obj)
|
||||||
|
env->DeleteLocalRef(result_relative_name_obj);
|
||||||
|
|
||||||
|
env->DeleteLocalRef(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
env->DeleteLocalRef(arr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __ANDROID__
|
||||||
|
|
||||||
ChangeNotifier::ChangeNotifier(const String& directoryPath, bool recursiveWatch)
|
ChangeNotifier::ChangeNotifier(const String& directoryPath, bool recursiveWatch)
|
||||||
: m_directoryPath(directoryPath), m_recursiveWatch(recursiveWatch)
|
: m_directoryPath(directoryPath), m_recursiveWatch(recursiveWatch)
|
||||||
{
|
{
|
||||||
|
@ -298,6 +545,28 @@ static std::string_view::size_type GetLastSeperatorPosition(const std::string_vi
|
||||||
if (last_separator == std::string_view::npos || other_last_separator > last_separator)
|
if (last_separator == std::string_view::npos || other_last_separator > last_separator)
|
||||||
last_separator = other_last_separator;
|
last_separator = other_last_separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#elif defined(__ANDROID__)
|
||||||
|
if (IsUriPath(filename))
|
||||||
|
{
|
||||||
|
// scoped storage rubbish
|
||||||
|
std::string_view::size_type other_last_separator = filename.rfind("%2F");
|
||||||
|
if (other_last_separator != std::string_view::npos)
|
||||||
|
{
|
||||||
|
if (include_separator)
|
||||||
|
other_last_separator += 3;
|
||||||
|
if (last_separator == std::string_view::npos || other_last_separator > last_separator)
|
||||||
|
last_separator = other_last_separator;
|
||||||
|
}
|
||||||
|
std::string_view::size_type lower_other_last_separator = filename.rfind("%2f");
|
||||||
|
if (lower_other_last_separator != std::string_view::npos)
|
||||||
|
{
|
||||||
|
if (include_separator)
|
||||||
|
lower_other_last_separator += 3;
|
||||||
|
if (last_separator == std::string_view::npos || lower_other_last_separator > last_separator)
|
||||||
|
last_separator = lower_other_last_separator;
|
||||||
|
}
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return last_separator;
|
return last_separator;
|
||||||
|
@ -374,6 +643,8 @@ std::unique_ptr<ByteStream> OpenFile(const char* FileName, u32 Flags)
|
||||||
if (FileName[0] == '\0')
|
if (FileName[0] == '\0')
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
|
// TODO: Handle Android content URIs here.
|
||||||
|
|
||||||
// forward to local file wrapper
|
// forward to local file wrapper
|
||||||
return ByteStream_OpenFileStream(FileName, Flags);
|
return ByteStream_OpenFileStream(FileName, Flags);
|
||||||
}
|
}
|
||||||
|
@ -414,6 +685,11 @@ std::FILE* OpenCFile(const char* filename, const char* mode)
|
||||||
|
|
||||||
return fp;
|
return fp;
|
||||||
#else
|
#else
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
if (IsUriPath(filename) && UriHelpersAreAvailable())
|
||||||
|
return OpenUriFile(filename, mode);
|
||||||
|
#endif
|
||||||
|
|
||||||
return std::fopen(filename, mode);
|
return std::fopen(filename, mode);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
@ -1450,6 +1726,11 @@ bool FindFiles(const char* Path, const char* Pattern, u32 Flags, FindResultsArra
|
||||||
if (!(Flags & FILESYSTEM_FIND_KEEP_ARRAY))
|
if (!(Flags & FILESYSTEM_FIND_KEEP_ARRAY))
|
||||||
pResults->clear();
|
pResults->clear();
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
if (IsUriPath(Path) && UriHelpersAreAvailable())
|
||||||
|
return FindUriFiles(Path, Pattern, Flags, pResults);
|
||||||
|
#endif
|
||||||
|
|
||||||
// enter the recursive function
|
// enter the recursive function
|
||||||
return (RecursiveFindFiles(Path, nullptr, nullptr, Pattern, Flags, pResults) > 0);
|
return (RecursiveFindFiles(Path, nullptr, nullptr, Pattern, Flags, pResults) > 0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,12 @@ namespace FileSystem {
|
||||||
|
|
||||||
using FindResultsArray = std::vector<FILESYSTEM_FIND_DATA>;
|
using FindResultsArray = std::vector<FILESYSTEM_FIND_DATA>;
|
||||||
|
|
||||||
|
#ifdef __ANDROID__
|
||||||
|
/// Sets the instance for the FileHelpers Java class, used for storage access framework
|
||||||
|
/// file access on Android.
|
||||||
|
void SetAndroidFileHelper(void* jvm, void* env, void* object);
|
||||||
|
#endif
|
||||||
|
|
||||||
class ChangeNotifier
|
class ChangeNotifier
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
Loading…
Reference in New Issue