From c13a1225276d327c3c0188fee80926110c85301c Mon Sep 17 00:00:00 2001 From: OpenSauce04 Date: Sun, 14 Dec 2025 22:34:45 +0000 Subject: [PATCH] WIP re-implementation of Android `Rename` using native filesystem manipulation --- .../java/org/citra/citra_emu/NativeLibrary.kt | 34 ++++++++++++------- .../citra/citra_emu/utils/DocumentsTree.kt | 13 ------- .../org/citra/citra_emu/utils/FileUtil.kt | 12 ------- src/common/android_storage.cpp | 18 +++++----- src/common/android_storage.h | 6 ++-- src/common/file_util.cpp | 4 ++- 6 files changed, 35 insertions(+), 52 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt index e53354dc9..7d32ebddf 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/NativeLibrary.kt @@ -7,10 +7,12 @@ package org.citra.citra_emu import android.Manifest.permission import android.app.Dialog import android.content.DialogInterface +import android.content.SharedPreferences import android.content.pm.PackageManager import android.content.res.Configuration import android.net.Uri import android.os.Bundle +import android.os.Environment import android.text.Html import android.text.method.LinkMovementMethod import android.view.Surface @@ -18,7 +20,9 @@ import android.view.View import android.widget.TextView import androidx.annotation.Keep import androidx.core.content.ContextCompat +import androidx.core.net.toUri import androidx.fragment.app.DialogFragment +import androidx.preference.PreferenceManager import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.citra.citra_emu.activities.EmulationActivity import org.citra.citra_emu.utils.FileUtil @@ -629,6 +633,23 @@ object NativeLibrary { FileUtil.getFilesName(path) } + @Keep + @JvmStatic + fun getUserDirectory(): String { + val preferences: SharedPreferences = + PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) + + val udUri = preferences.getString("CITRA_DIRECTORY", "")!!.toUri() + val udPathSegment = udUri.lastPathSegment!! + val udVirtualPath = udPathSegment.removePrefix("primary:") + if (udVirtualPath == udPathSegment) { + throw IllegalStateException("TODO: User directory must be in primary external storage, is instead: $udVirtualPath") + } + val userDirNativePath = Environment.getExternalStorageDirectory().absolutePath + "/" + udVirtualPath + "/" + + return userDirNativePath + } + @Keep @JvmStatic fun getSize(path: String): Long = @@ -676,19 +697,6 @@ object NativeLibrary { ) } - @Keep - @JvmStatic - fun renameFile(path: String, destinationFilename: String): Boolean = - if (FileUtil.isNativePath(path)) { - try { - CitraApplication.documentsTree.renameFile(path, destinationFilename) - } catch (e: Exception) { - false - } - } else { - FileUtil.renameFile(path, destinationFilename) - } - @Keep @JvmStatic fun deleteDocument(path: String): Boolean = diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/DocumentsTree.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/DocumentsTree.kt index e2b015d47..e9e4af0a2 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/DocumentsTree.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/DocumentsTree.kt @@ -190,19 +190,6 @@ class DocumentsTree { } } - @Synchronized - fun renameFile(filepath: String, destinationFilename: String?): Boolean { - val node = resolvePath(filepath) ?: return false - try { - val filename = URLDecoder.decode(destinationFilename, FileUtil.DECODE_METHOD) - val newUri = DocumentsContract.renameDocument(context.contentResolver, node.uri!!, filename) - node.rename(filename, newUri) - return true - } catch (e: Exception) { - error("[DocumentsTree]: Cannot rename file, error: " + e.message) - } - } - @Synchronized fun deleteDocument(filepath: String): Boolean { val node = resolvePath(filepath) ?: return false diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/FileUtil.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/FileUtil.kt index 402a23857..b7e9b4219 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/FileUtil.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/FileUtil.kt @@ -422,18 +422,6 @@ object FileUtil { } } - @JvmStatic - fun renameFile(path: String, destinationFilename: String): Boolean { - try { - val uri = Uri.parse(path) - DocumentsContract.renameDocument(context.contentResolver, uri, destinationFilename) - return true - } catch (e: Exception) { - Log.error("[FileUtil]: Cannot rename file, error: " + e.message) - } - return false - } - @JvmStatic fun deleteDocument(path: String): Boolean { try { diff --git a/src/common/android_storage.cpp b/src/common/android_storage.cpp index a18ecefac..179682663 100644 --- a/src/common/android_storage.cpp +++ b/src/common/android_storage.cpp @@ -150,6 +150,14 @@ std::vector GetFilesName(const std::string& filepath) { return vector; } +std::string GetUserDirectory() { + if (get_user_directory == nullptr) + throw std::runtime_error("Unable to locate user directory: Function with ID 'get_user_directory' is missing"); + auto env = GetEnvForThread(); + auto j_user_directory = (jstring)(env->CallStaticObjectMethod(native_library, get_user_directory)); + return env->GetStringUTFChars(j_user_directory, nullptr); +} + bool CopyFile(const std::string& source, const std::string& destination_path, const std::string& destination_filename) { if (copy_file == nullptr) @@ -162,16 +170,6 @@ bool CopyFile(const std::string& source, const std::string& destination_path, j_destination_path, j_destination_filename); } -bool RenameFile(const std::string& source, const std::string& filename) { - if (rename_file == nullptr) - return false; - auto env = GetEnvForThread(); - jstring j_source_path = env->NewStringUTF(source.c_str()); - jstring j_destination_path = env->NewStringUTF(filename.c_str()); - return env->CallStaticBooleanMethod(native_library, rename_file, j_source_path, - j_destination_path); -} - #define FR(FunctionName, ReturnValue, JMethodID, Caller, JMethodName, Signature) \ F(FunctionName, ReturnValue, JMethodID, Caller) #define F(FunctionName, ReturnValue, JMethodID, Caller) \ diff --git a/src/common/android_storage.h b/src/common/android_storage.h index 2ea0eb57c..a208c4693 100644 --- a/src/common/android_storage.h +++ b/src/common/android_storage.h @@ -19,12 +19,12 @@ open_content_uri, "openContentUri", "(Ljava/lang/String;Ljava/lang/String;)I") \ V(GetFilesName, std::vector, (const std::string& filepath), get_files_name, \ "getFilesName", "(Ljava/lang/String;)[Ljava/lang/String;") \ + V(GetUserDirectory, std::string, (), get_user_directory, "getUserDirectory", \ + "()Ljava/lang/String;") \ V(CopyFile, bool, \ (const std::string& source, const std::string& destination_path, \ const std::string& destination_filename), \ - copy_file, "copyFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z") \ - V(RenameFile, bool, (const std::string& source, const std::string& filename), rename_file, \ - "renameFile", "(Ljava/lang/String;Ljava/lang/String;)Z") + copy_file, "copyFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z") #define ANDROID_SINGLE_PATH_DETERMINE_FUNCTIONS(V) \ V(IsDirectory, bool, is_directory, CallStaticBooleanMethod, "isDirectory", \ "(Ljava/lang/String;)Z") \ diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index c740e8416..7abf95cbd 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -311,7 +311,9 @@ bool Rename(const std::string& srcFilename, const std::string& destFilename) { Common::UTF8ToUTF16W(destFilename).c_str()) == 0) return true; #elif ANDROID - if (AndroidStorage::RenameFile(srcFilename, std::string(GetFilename(destFilename)))) + const std::string userDirLocation = AndroidStorage::GetUserDirectory(); + if (rename((userDirLocation + srcFilename).c_str(), + (userDirLocation + destFilename).c_str()) == 0) return true; #else if (rename(srcFilename.c_str(), destFilename.c_str()) == 0)