diff --git a/Common/File/AndroidStorage.cpp b/Common/File/AndroidStorage.cpp
index 980bfaa9e2..d7de92c64e 100644
--- a/Common/File/AndroidStorage.cpp
+++ b/Common/File/AndroidStorage.cpp
@@ -12,6 +12,7 @@ static jmethodID listContentUriDir;
static jmethodID contentUriCreateFile;
static jmethodID contentUriCreateDirectory;
static jmethodID contentUriCopyFile;
+static jmethodID contentUriMoveFile;
static jmethodID contentUriRemoveFile;
static jmethodID contentUriRenameFileTo;
static jmethodID contentUriGetFileInfo;
@@ -39,6 +40,8 @@ void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj) {
_dbg_assert_(contentUriCopyFile);
contentUriRemoveFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriRemoveFile", "(Ljava/lang/String;)Z");
_dbg_assert_(contentUriRemoveFile);
+ contentUriMoveFile = env->GetMethodID(env->GetObjectClass(obj), "contentUriMoveFile", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z");
+ _dbg_assert_(contentUriMoveFile);
contentUriRenameFileTo = env->GetMethodID(env->GetObjectClass(obj), "contentUriRenameFileTo", "(Ljava/lang/String;Ljava/lang/String;)Z");
_dbg_assert_(contentUriRenameFileTo);
contentUriGetFileInfo = env->GetMethodID(env->GetObjectClass(obj), "contentUriGetFileInfo", "(Ljava/lang/String;)Ljava/lang/String;");
@@ -111,6 +114,17 @@ bool Android_CopyFile(const std::string &fileUri, const std::string &destParentU
return env->CallBooleanMethod(g_nativeActivity, contentUriCopyFile, paramFileName, paramDestParentUri);
}
+bool Android_MoveFile(const std::string &fileUri, const std::string &srcParentUri, const std::string &destParentUri) {
+ if (!g_nativeActivity) {
+ return false;
+ }
+ auto env = getEnv();
+ jstring paramFileName = env->NewStringUTF(fileUri.c_str());
+ jstring paramSrcParentUri = env->NewStringUTF(srcParentUri.c_str());
+ jstring paramDestParentUri = env->NewStringUTF(destParentUri.c_str());
+ return env->CallBooleanMethod(g_nativeActivity, contentUriMoveFile, paramFileName, paramSrcParentUri, paramDestParentUri);
+}
+
bool Android_RemoveFile(const std::string &fileUri) {
if (!g_nativeActivity) {
return false;
@@ -262,4 +276,9 @@ const char *Android_ErrorToString(ContentError error) {
}
}
+#else
+
+// This string should never appear except on Android.
+std::string g_extFilesDir = "(IF YOU SEE THIS THERE'S A BUG)";
+
#endif
diff --git a/Common/File/AndroidStorage.h b/Common/File/AndroidStorage.h
index 55d49da478..9decabaf48 100644
--- a/Common/File/AndroidStorage.h
+++ b/Common/File/AndroidStorage.h
@@ -41,6 +41,7 @@ bool Android_IsContentUri(const std::string &uri);
int Android_OpenContentUriFd(const std::string &uri, const Android_OpenContentUriMode mode);
bool Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName);
bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName);
+bool Android_MoveFile(const std::string &fileUri, const std::string &srcParentUri, const std::string &destParentUri);
bool Android_CopyFile(const std::string &fileUri, const std::string &destParentUri);
bool Android_RemoveFile(const std::string &fileUri);
bool Android_RenameFileTo(const std::string &fileUri, const std::string &newName);
@@ -57,12 +58,15 @@ void Android_RegisterStorageCallbacks(JNIEnv * env, jobject obj);
#else
+extern std::string g_extFilesDir;
+
// Stub out the Android Storage wrappers, so that we can avoid ifdefs everywhere.
inline bool Android_IsContentUri(const std::string &uri) { return false; }
inline int Android_OpenContentUriFd(const std::string &uri, const Android_OpenContentUriMode mode) { return -1; }
inline bool Android_CreateDirectory(const std::string &parentTreeUri, const std::string &dirName) { return false; }
inline bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) { return false; }
+inline bool Android_MoveFile(const std::string &fileUri, const std::string &srcParentUri, const std::string &destParentUri) { return false; }
inline bool Android_CopyFile(const std::string &fileUri, const std::string &destParentUri) { return false; }
inline bool Android_RemoveFile(const std::string &fileUri) { return false; }
inline bool Android_RenameFileTo(const std::string &fileUri, const std::string &newName) { return false; }
diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp
index f43e443c21..8560694a48 100644
--- a/Common/File/FileUtil.cpp
+++ b/Common/File/FileUtil.cpp
@@ -571,6 +571,7 @@ bool Rename(const Path &srcFilename, const Path &destFilename) {
break;
case PathType::CONTENT_URI:
// Content URI: Can only rename if in the same folder.
+ // TODO: Fallback to move + rename? Or do we even care about that use case?
if (srcFilename.GetDirectory() != destFilename.GetDirectory()) {
INFO_LOG(COMMON, "Content URI rename: Directories not matching, failing. %s --> %s", srcFilename.c_str(), destFilename.c_str());
return false;
@@ -600,26 +601,23 @@ bool Rename(const Path &srcFilename, const Path &destFilename) {
}
// copies file srcFilename to destFilename, returns true on success
-bool Copy(const Path &srcFilename, const Path &destFilename)
-{
+bool Copy(const Path &srcFilename, const Path &destFilename) {
switch (srcFilename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
- ERROR_LOG_REPORT_ONCE(copyUriNotSupported, COMMON, "Copying files by Android URI is not yet supported");
+ if (destFilename.Type() == PathType::CONTENT_URI && destFilename.CanNavigateUp()) {
+ Path destParent = destFilename.NavigateUp();
+ // Use native file copy.
+ if (Android_CopyFile(srcFilename.ToString(), destParent.ToString())) {
+ return true;
+ }
+ // Else fall through, and try using file I/O.
+ }
break;
default:
return false;
}
- switch (destFilename.Type()) {
- case PathType::NATIVE:
- break; // OK
- case PathType::CONTENT_URI:
- ERROR_LOG_REPORT_ONCE(copyUriNotSupported, COMMON, "Copying files by Android URI is not yet supported");
- return false;
- default:
- return false;
- }
INFO_LOG(COMMON, "Copy: %s --> %s", srcFilename.c_str(), destFilename.c_str());
#ifdef _WIN32
@@ -637,7 +635,7 @@ bool Copy(const Path &srcFilename, const Path &destFilename)
#else
// buffer size
-#define BSIZE 4096
+#define BSIZE 16384
char buffer[BSIZE];
@@ -692,6 +690,19 @@ bool Copy(const Path &srcFilename, const Path &destFilename)
}
bool Move(const Path &srcFilename, const Path &destFilename) {
+ // Try a shortcut in Android Storage scenarios.
+ if (srcFilename.Type() == PathType::CONTENT_URI && destFilename.Type() == PathType::CONTENT_URI && srcFilename.CanNavigateUp() && destFilename.CanNavigateUp()) {
+ // We do not handle simultaneous renames here.
+ if (srcFilename.GetFilename() == destFilename.GetFilename()) {
+ Path srcParent = srcFilename.NavigateUp();
+ Path dstParent = destFilename.NavigateUp();
+ if (Android_MoveFile(srcFilename.ToString(), srcParent.ToString(), dstParent.ToString())) {
+ return true;
+ }
+ // If failed, fall through and try other ways.
+ }
+ }
+
if (Rename(srcFilename, destFilename)) {
return true;
} else if (Copy(srcFilename, destFilename)) {
diff --git a/UWP/CommonUWP/CommonUWP.vcxproj b/UWP/CommonUWP/CommonUWP.vcxproj
index e91ec0d58b..3349afd9e6 100644
--- a/UWP/CommonUWP/CommonUWP.vcxproj
+++ b/UWP/CommonUWP/CommonUWP.vcxproj
@@ -386,6 +386,7 @@
+
@@ -518,6 +519,7 @@
+
@@ -640,4 +642,4 @@
-
+
\ No newline at end of file
diff --git a/UWP/CommonUWP/CommonUWP.vcxproj.filters b/UWP/CommonUWP/CommonUWP.vcxproj.filters
index a72807393e..740747dfd4 100644
--- a/UWP/CommonUWP/CommonUWP.vcxproj.filters
+++ b/UWP/CommonUWP/CommonUWP.vcxproj.filters
@@ -375,6 +375,9 @@
GPU
+
+ File
+
@@ -691,6 +694,9 @@
GPU
+
+ File
+
@@ -705,4 +711,4 @@
Math\lin
-
+
\ No newline at end of file
diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java
index 02c733ec5c..89e02699c9 100644
--- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java
+++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java
@@ -268,14 +268,27 @@ public class PpssppActivity extends NativeActivity {
try {
Uri srcUri = Uri.parse(srcFileUri);
Uri dstParentUri = Uri.parse(dstParentDirUri);
- DocumentsContract.copyDocument(getContentResolver(), srcUri, dstParentUri);
- return true;
+ return DocumentsContract.copyDocument(getContentResolver(), srcUri, dstParentUri) != null;
} catch (Exception e) {
Log.e(TAG, "contentUriCopyFile exception: " + e.toString());
return false;
}
}
+ // NOTE: The destination is the parent directory! This means that contentUriCopyFile
+ // cannot rename things as part of the operation.
+ public boolean contentUriMoveFile(String srcFileUri, String srcParentDirUri, String dstParentDirUri) {
+ try {
+ Uri srcUri = Uri.parse(srcFileUri);
+ Uri srcParentUri = Uri.parse(srcParentDirUri);
+ Uri dstParentUri = Uri.parse(dstParentDirUri);
+ return DocumentsContract.moveDocument(getContentResolver(), srcUri, srcParentUri, dstParentUri) != null;
+ } catch (Exception e) {
+ Log.e(TAG, "contentUriMoveFile exception: " + e.toString());
+ return false;
+ }
+ }
+
public boolean contentUriRenameFileTo(String fileUri, String newName) {
try {
Uri uri = Uri.parse(fileUri);