mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2024-11-14 21:37:52 -07:00
Android: Add specialized content provider implementation of DoFileSearch
This commit is contained in:
parent
01b964b01a
commit
d78277c063
@ -130,8 +130,8 @@ public final class MainPresenter
|
||||
|
||||
boolean recursive = BooleanSetting.MAIN_RECURSIVE_ISO_PATHS.getBooleanGlobal();
|
||||
String[] childNames = ContentHandler.getChildNames(uri, recursive);
|
||||
if (Arrays.stream(childNames).noneMatch((name) ->
|
||||
FileBrowserHelper.GAME_EXTENSIONS.contains(FileBrowserHelper.getExtension(name))))
|
||||
if (Arrays.stream(childNames).noneMatch((name) -> FileBrowserHelper.GAME_EXTENSIONS.contains(
|
||||
FileBrowserHelper.getExtension(name, false))))
|
||||
{
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(mContext, R.style.DolphinDialogBase);
|
||||
builder.setMessage(mContext.getString(R.string.wrong_file_extension_in_directory,
|
||||
|
@ -15,7 +15,9 @@ import org.dolphinemu.dolphinemu.DolphinApplication;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/*
|
||||
We use a lot of "catch (Exception e)" in this class. This is for two reasons:
|
||||
@ -184,34 +186,94 @@ public class ContentHandler
|
||||
public static String[] getChildNames(@NonNull Uri uri, boolean recursive)
|
||||
{
|
||||
ArrayList<String> result = new ArrayList<>();
|
||||
getChildNames(uri, DocumentsContract.getDocumentId(treeToDocument(uri)), recursive, result);
|
||||
|
||||
ForEachChildCallback callback = new ForEachChildCallback()
|
||||
{
|
||||
@Override
|
||||
public void run(String displayName, String documentId, boolean isDirectory)
|
||||
{
|
||||
if (recursive && isDirectory)
|
||||
{
|
||||
forEachChild(uri, documentId, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.add(displayName);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
forEachChild(uri, DocumentsContract.getDocumentId(treeToDocument(uri)), callback);
|
||||
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static void getChildNames(@NonNull Uri uri, @NonNull String documentId, boolean recursive,
|
||||
List<String> resultOut)
|
||||
@NonNull @Keep
|
||||
public static String[] doFileSearch(@NonNull String directory, @NonNull String[] extensions,
|
||||
boolean recursive)
|
||||
{
|
||||
ArrayList<String> result = new ArrayList<>();
|
||||
|
||||
try
|
||||
{
|
||||
Uri uri = unmangle(directory);
|
||||
String documentId = DocumentsContract.getDocumentId(treeToDocument(uri));
|
||||
boolean acceptAll = extensions.length == 0;
|
||||
Predicate<String> extensionCheck = (displayName) ->
|
||||
{
|
||||
String extension = FileBrowserHelper.getExtension(displayName, true);
|
||||
return extension != null && Arrays.stream(extensions).anyMatch(extension::equalsIgnoreCase);
|
||||
};
|
||||
doFileSearch(uri, directory, documentId, recursive, result, acceptAll, extensionCheck);
|
||||
}
|
||||
catch (Exception ignored)
|
||||
{
|
||||
}
|
||||
|
||||
return result.toArray(new String[0]);
|
||||
}
|
||||
|
||||
private static void doFileSearch(@NonNull Uri baseUri, @NonNull String path,
|
||||
@NonNull String documentId, boolean recursive, @NonNull List<String> resultOut,
|
||||
boolean acceptAll, @NonNull Predicate<String> extensionCheck)
|
||||
{
|
||||
forEachChild(baseUri, documentId, (displayName, childDocumentId, isDirectory) ->
|
||||
{
|
||||
String childPath = path + '/' + displayName;
|
||||
if (acceptAll || (!isDirectory && extensionCheck.test(displayName)))
|
||||
{
|
||||
resultOut.add(childPath);
|
||||
}
|
||||
if (recursive && isDirectory)
|
||||
{
|
||||
doFileSearch(baseUri, childPath, childDocumentId, recursive, resultOut, acceptAll,
|
||||
extensionCheck);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private interface ForEachChildCallback
|
||||
{
|
||||
void run(String displayName, String documentId, boolean isDirectory);
|
||||
}
|
||||
|
||||
private static void forEachChild(@NonNull Uri uri, @NonNull String documentId,
|
||||
@NonNull ForEachChildCallback callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri, documentId);
|
||||
|
||||
final String[] projection = recursive ? new String[]{Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_MIME_TYPE, Document.COLUMN_DOCUMENT_ID} :
|
||||
new String[]{Document.COLUMN_DISPLAY_NAME};
|
||||
final String[] projection = new String[]{Document.COLUMN_DISPLAY_NAME,
|
||||
Document.COLUMN_MIME_TYPE, Document.COLUMN_DOCUMENT_ID};
|
||||
try (Cursor cursor = getContentResolver().query(childrenUri, projection, null, null, null))
|
||||
{
|
||||
if (cursor != null)
|
||||
{
|
||||
while (cursor.moveToNext())
|
||||
{
|
||||
if (recursive && Document.MIME_TYPE_DIR.equals(cursor.getString(1)))
|
||||
{
|
||||
getChildNames(uri, cursor.getString(2), recursive, resultOut);
|
||||
}
|
||||
else
|
||||
{
|
||||
resultOut.add(cursor.getString(0));
|
||||
}
|
||||
callback.run(cursor.getString(0), cursor.getString(2),
|
||||
Document.MIME_TYPE_DIR.equals(cursor.getString(1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,10 +88,10 @@ public final class FileBrowserHelper
|
||||
|
||||
String path = uri.getLastPathSegment();
|
||||
if (path != null)
|
||||
extension = getExtension(new File(path).getName());
|
||||
extension = getExtension(new File(path).getName(), false);
|
||||
|
||||
if (extension == null)
|
||||
extension = getExtension(ContentHandler.getDisplayName(uri));
|
||||
extension = getExtension(ContentHandler.getDisplayName(uri), false);
|
||||
|
||||
if (extension != null && validExtensions.contains(extension))
|
||||
{
|
||||
@ -122,13 +122,15 @@ public final class FileBrowserHelper
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getExtension(@Nullable String fileName)
|
||||
public static String getExtension(@Nullable String fileName, boolean includeDot)
|
||||
{
|
||||
if (fileName == null)
|
||||
return null;
|
||||
|
||||
int dotIndex = fileName.lastIndexOf(".");
|
||||
return dotIndex != -1 ? fileName.substring(dotIndex + 1) : null;
|
||||
if (dotIndex == -1)
|
||||
return null;
|
||||
return fileName.substring(dotIndex + (includeDot ? 0 : 1));
|
||||
}
|
||||
|
||||
public static String setToSortedDelimitedString(Set<String> set)
|
||||
|
@ -44,6 +44,14 @@ std::vector<std::string> JStringArrayToVector(JNIEnv* env, jobjectArray array)
|
||||
return result;
|
||||
}
|
||||
|
||||
jobjectArray JStringArrayFromVector(JNIEnv* env, std::vector<std::string> vector)
|
||||
{
|
||||
jobjectArray result = env->NewObjectArray(vector.size(), IDCache::GetStringClass(), nullptr);
|
||||
for (jsize i = 0; i < vector.size(); ++i)
|
||||
env->SetObjectArrayElement(result, i, ToJString(env, vector[i]));
|
||||
return result;
|
||||
}
|
||||
|
||||
bool IsPathAndroidContent(const std::string& uri)
|
||||
{
|
||||
return StringBeginsWith(uri, "content://");
|
||||
@ -130,6 +138,17 @@ std::vector<std::string> GetAndroidContentChildNames(const std::string& uri)
|
||||
return JStringArrayToVector(env, reinterpret_cast<jobjectArray>(children));
|
||||
}
|
||||
|
||||
std::vector<std::string> DoFileSearchAndroidContent(const std::string& directory,
|
||||
const std::vector<std::string>& extensions,
|
||||
bool recursive)
|
||||
{
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
jobject result = env->CallStaticObjectMethod(
|
||||
IDCache::GetContentHandlerClass(), IDCache::GetContentHandlerDoFileSearch(),
|
||||
ToJString(env, directory), JStringArrayFromVector(env, extensions), recursive);
|
||||
return JStringArrayToVector(env, reinterpret_cast<jobjectArray>(result));
|
||||
}
|
||||
|
||||
int GetNetworkIpAddress()
|
||||
{
|
||||
JNIEnv* env = IDCache::GetEnvForThread();
|
||||
|
@ -38,6 +38,10 @@ std::string GetAndroidContentDisplayName(const std::string& uri);
|
||||
// Returns the display names of all children of a directory, non-recursively.
|
||||
std::vector<std::string> GetAndroidContentChildNames(const std::string& uri);
|
||||
|
||||
std::vector<std::string> DoFileSearchAndroidContent(const std::string& directory,
|
||||
const std::vector<std::string>& extensions,
|
||||
bool recursive);
|
||||
|
||||
int GetNetworkIpAddress();
|
||||
int GetNetworkPrefixLength();
|
||||
int GetNetworkGateway();
|
||||
|
@ -10,6 +10,8 @@ static constexpr jint JNI_VERSION = JNI_VERSION_1_6;
|
||||
|
||||
static JavaVM* s_java_vm;
|
||||
|
||||
static jclass s_string_class;
|
||||
|
||||
static jclass s_native_library_class;
|
||||
static jmethodID s_display_alert_msg;
|
||||
static jmethodID s_do_rumble;
|
||||
@ -47,6 +49,7 @@ static jmethodID s_content_handler_delete;
|
||||
static jmethodID s_content_handler_get_size_and_is_directory;
|
||||
static jmethodID s_content_handler_get_display_name;
|
||||
static jmethodID s_content_handler_get_child_names;
|
||||
static jmethodID s_content_handler_do_file_search;
|
||||
|
||||
static jclass s_network_helper_class;
|
||||
static jmethodID s_network_helper_get_network_ip_address;
|
||||
@ -78,6 +81,11 @@ JNIEnv* GetEnvForThread()
|
||||
return owned.env;
|
||||
}
|
||||
|
||||
jclass GetStringClass()
|
||||
{
|
||||
return s_string_class;
|
||||
}
|
||||
|
||||
jclass GetNativeLibraryClass()
|
||||
{
|
||||
return s_native_library_class;
|
||||
@ -228,6 +236,11 @@ jmethodID GetContentHandlerGetChildNames()
|
||||
return s_content_handler_get_child_names;
|
||||
}
|
||||
|
||||
jmethodID GetContentHandlerDoFileSearch()
|
||||
{
|
||||
return s_content_handler_do_file_search;
|
||||
}
|
||||
|
||||
jclass GetNetworkHelperClass()
|
||||
{
|
||||
return s_network_helper_class;
|
||||
@ -262,6 +275,9 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||
if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION) != JNI_OK)
|
||||
return JNI_ERR;
|
||||
|
||||
const jclass string_class = env->FindClass("java/lang/String");
|
||||
s_string_class = reinterpret_cast<jclass>(env->NewGlobalRef(string_class));
|
||||
|
||||
const jclass native_library_class = env->FindClass("org/dolphinemu/dolphinemu/NativeLibrary");
|
||||
s_native_library_class = reinterpret_cast<jclass>(env->NewGlobalRef(native_library_class));
|
||||
s_display_alert_msg = env->GetStaticMethodID(s_native_library_class, "displayAlertMsg",
|
||||
@ -331,6 +347,9 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved)
|
||||
s_content_handler_class, "getDisplayName", "(Ljava/lang/String;)Ljava/lang/String;");
|
||||
s_content_handler_get_child_names = env->GetStaticMethodID(
|
||||
s_content_handler_class, "getChildNames", "(Ljava/lang/String;Z)[Ljava/lang/String;");
|
||||
s_content_handler_do_file_search =
|
||||
env->GetStaticMethodID(s_content_handler_class, "doFileSearch",
|
||||
"(Ljava/lang/String;[Ljava/lang/String;Z)[Ljava/lang/String;");
|
||||
|
||||
const jclass network_helper_class =
|
||||
env->FindClass("org/dolphinemu/dolphinemu/utils/NetworkHelper");
|
||||
|
@ -10,6 +10,8 @@ namespace IDCache
|
||||
{
|
||||
JNIEnv* GetEnvForThread();
|
||||
|
||||
jclass GetStringClass();
|
||||
|
||||
jclass GetNativeLibraryClass();
|
||||
jmethodID GetDisplayAlertMsg();
|
||||
jmethodID GetDoRumble();
|
||||
@ -47,6 +49,7 @@ jmethodID GetContentHandlerDelete();
|
||||
jmethodID GetContentHandlerGetSizeAndIsDirectory();
|
||||
jmethodID GetContentHandlerGetDisplayName();
|
||||
jmethodID GetContentHandlerGetChildNames();
|
||||
jmethodID GetContentHandlerDoFileSearch();
|
||||
|
||||
jclass GetNetworkHelperClass();
|
||||
jmethodID GetNetworkHelperGetNetworkIpAddress();
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
|
||||
#include "Common/CommonPaths.h"
|
||||
#include "Common/FileSearch.h"
|
||||
@ -15,6 +16,10 @@
|
||||
namespace fs = std::filesystem;
|
||||
#define HAS_STD_FILESYSTEM
|
||||
#else
|
||||
#ifdef ANDROID
|
||||
#include "jni/AndroidCommon/AndroidCommon.h"
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include "Common/CommonFuncs.h"
|
||||
#include "Common/FileUtil.h"
|
||||
@ -24,36 +29,30 @@ namespace Common
|
||||
{
|
||||
#ifndef HAS_STD_FILESYSTEM
|
||||
|
||||
static std::vector<std::string>
|
||||
FileSearchWithTest(const std::vector<std::string>& directories, bool recursive,
|
||||
static void FileSearchWithTest(const std::string& directory, bool recursive,
|
||||
std::vector<std::string>* result_out,
|
||||
std::function<bool(const File::FSTEntry&)> callback)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
for (const std::string& directory : directories)
|
||||
{
|
||||
File::FSTEntry top = File::ScanDirectoryTree(directory, recursive);
|
||||
|
||||
std::function<void(File::FSTEntry&)> DoEntry;
|
||||
DoEntry = [&](File::FSTEntry& entry) {
|
||||
const std::function<void(File::FSTEntry&)> DoEntry = [&](File::FSTEntry& entry) {
|
||||
if (callback(entry))
|
||||
result.push_back(entry.physicalName);
|
||||
result_out->push_back(entry.physicalName);
|
||||
for (auto& child : entry.children)
|
||||
DoEntry(child);
|
||||
};
|
||||
|
||||
for (auto& child : top.children)
|
||||
DoEntry(child);
|
||||
}
|
||||
// remove duplicates
|
||||
std::sort(result.begin(), result.end());
|
||||
result.erase(std::unique(result.begin(), result.end()), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> DoFileSearch(const std::vector<std::string>& directories,
|
||||
const std::vector<std::string>& exts, bool recursive)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
|
||||
bool accept_all = exts.empty();
|
||||
return FileSearchWithTest(directories, recursive, [&](const File::FSTEntry& entry) {
|
||||
const auto callback = [&exts, accept_all](const File::FSTEntry& entry) {
|
||||
if (accept_all)
|
||||
return true;
|
||||
if (entry.isDirectory)
|
||||
@ -63,7 +62,34 @@ std::vector<std::string> DoFileSearch(const std::vector<std::string>& directorie
|
||||
return name.length() >= ext.length() &&
|
||||
strcasecmp(name.c_str() + name.length() - ext.length(), ext.c_str()) == 0;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
for (const std::string& directory : directories)
|
||||
{
|
||||
#ifdef ANDROID
|
||||
// While File::ScanDirectoryTree (which is called in FileSearchWithTest) does handle Android
|
||||
// content correctly, having a specialized implementation of DoFileSearch for Android content
|
||||
// provides a much needed performance boost. Also, this specialized implementation will be
|
||||
// required if we in the future replace the use of File::ScanDirectoryTree with std::filesystem.
|
||||
if (IsPathAndroidContent(directory))
|
||||
{
|
||||
const std::vector<std::string> partial_result =
|
||||
DoFileSearchAndroidContent(directory, exts, recursive);
|
||||
|
||||
result.insert(result.end(), std::make_move_iterator(partial_result.begin()),
|
||||
std::make_move_iterator(partial_result.end()));
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
FileSearchWithTest(directory, recursive, &result, callback);
|
||||
}
|
||||
}
|
||||
|
||||
// remove duplicates
|
||||
std::sort(result.begin(), result.end());
|
||||
result.erase(std::unique(result.begin(), result.end()), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
|
Loading…
Reference in New Issue
Block a user