diff --git a/Common/File/FileUtil.cpp b/Common/File/FileUtil.cpp index 6424956ca4..44ec6a6b7c 100644 --- a/Common/File/FileUtil.cpp +++ b/Common/File/FileUtil.cpp @@ -101,15 +101,42 @@ FILE *OpenCFile(const Path &path, const char *mode) { case PathType::NATIVE: break; case PathType::CONTENT_URI: + // We're gonna need some error codes.. if (!strcmp(mode, "r") || !strcmp(mode, "rb")) { + INFO_LOG(COMMON, "Opening content file for read: '%s'", path.c_str()); // Read, let's support this - easy one. int descriptor = Android_OpenContentUriFd(path.ToString(), Android_OpenContentUriMode::READ); if (descriptor == -1) { - // Set last error message? - // We're gonna need some error codes.. return nullptr; } return fdopen(descriptor, "rb"); + } else if (!strcmp(mode, "w") || !strcmp(mode, "wb")) { + // Need to be able to create the file here if it doesn't exist. + // Not exactly sure which abstractions are best, let's start simple. + if (!File::Exists(path)) { + INFO_LOG(COMMON, "Opening content file '%s' for write. Doesn't exist, creating empty and reopening.", path.c_str()); + std::string name = path.GetFilename(); + if (path.CanNavigateUp()) { + Path parent = path.NavigateUp(); + if (!Android_CreateFile(parent.ToString(), name)) { + WARN_LOG(COMMON, "Failed to create file '%s' in '%s'", name.c_str(), parent.c_str()); + return nullptr; + } + } else { + INFO_LOG(COMMON, "Failed to navigate up to create file"); + return nullptr; + } + } else { + INFO_LOG(COMMON, "Opening file by fd for write"); + } + + // Read, let's support this - easy one. + int descriptor = Android_OpenContentUriFd(path.ToString(), Android_OpenContentUriMode::READ_WRITE_TRUNCATE); + if (descriptor == -1) { + INFO_LOG(COMMON, "Opening '%s' for write failed", path.ToString().c_str()); + return nullptr; + } + return fdopen(descriptor, "wb"); } else { ERROR_LOG(COMMON, "OpenCFile(%s): Mode not yet supported: %s", path.c_str(), mode); return nullptr; @@ -365,6 +392,7 @@ bool CreateDir(const Path &path) { AndroidContentURI uri(path.ToString()); std::string newDirName = uri.GetLastPart(); if (uri.NavigateUp()) { + INFO_LOG(COMMON, "Calling Android_CreateDirectory(%s, %s)", uri.ToString().c_str(), newDirName.c_str()); return Android_CreateDirectory(uri.ToString(), newDirName); } else { // Bad path - can't create this directory. @@ -437,14 +465,18 @@ bool CreateFullPath(const Path &path) { Path curPath = root; + INFO_LOG(COMMON, "About to create folder tree '%s', rooted at '%s'", path.c_str(), root.c_str()); + for (auto &part : parts) { curPath /= part; - INFO_LOG(COMMON, "Creating %s", curPath.c_str()); - if (!File::Exists(curPath)) { + INFO_LOG(COMMON, "Creating folder '%s', doesn't already exist", curPath.c_str()); File::CreateDir(curPath); } } + INFO_LOG(COMMON, "Done"); + + return true; } // Deletes a directory filename, returns true on success diff --git a/Common/File/Path.cpp b/Common/File/Path.cpp index ffa2551af9..cfbae195d9 100644 --- a/Common/File/Path.cpp +++ b/Common/File/Path.cpp @@ -268,8 +268,8 @@ Path Path::GetRootVolume() const { if (type_ == PathType::CONTENT_URI) { AndroidContentURI uri(path_); - std::string rootPath = uri.RootPath(); - return Path(rootPath); + AndroidContentURI rootPath = uri.WithRootFilePath(""); + return Path(rootPath.ToString()); } #if PPSSPP_PLATFORM(WINDOWS) diff --git a/Common/Net/HTTPClient.cpp b/Common/Net/HTTPClient.cpp index e1b25bb073..edc202ed24 100644 --- a/Common/Net/HTTPClient.cpp +++ b/Common/Net/HTTPClient.cpp @@ -552,12 +552,12 @@ void Download::Do() { } if (resultCode == 200) { - INFO_LOG(IO, "Completed downloading %s to %s", url_.c_str(), outfile_.empty() ? "memory" : outfile_.ToVisualString().c_str()); + INFO_LOG(IO, "Completed downloading %s to %s", url_.c_str(), outfile_.empty() ? "memory" : outfile_.c_str()); if (!outfile_.empty() && !buffer_.FlushToFile(outfile_)) { - ERROR_LOG(IO, "Failed writing download to %s", outfile_.ToVisualString().c_str()); + ERROR_LOG(IO, "Failed writing download to '%s'", outfile_.c_str()); } } else { - ERROR_LOG(IO, "Error downloading %s to %s: %i", url_.c_str(), outfile_.ToVisualString().c_str(), resultCode); + ERROR_LOG(IO, "Error downloading '%s' to '%s': %i", url_.c_str(), outfile_.c_str(), resultCode); } resultCode_ = resultCode; } diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index b77d81e6d3..4b12a4a275 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -131,8 +131,10 @@ bool GameManager::Uninstall(std::string name) { void GameManager::Update() { if (curDownload_.get() && curDownload_->Done()) { INFO_LOG(HLE, "Download completed! Status = %d", curDownload_->ResultCode()); - Path fileName = Path(curDownload_->outfile()); + Path fileName = curDownload_->outfile(); if (curDownload_->ResultCode() == 200) { + // TODO: This fails. Wonder if there's a race condition? + if (!File::Exists(fileName)) { ERROR_LOG(HLE, "Downloaded file '%s' does not exist :(", fileName.c_str()); curDownload_.reset(); diff --git a/android/jni/AndroidContentURI.h b/android/jni/AndroidContentURI.h index 865ceb9d2f..f9837a5ba4 100644 --- a/android/jni/AndroidContentURI.h +++ b/android/jni/AndroidContentURI.h @@ -63,7 +63,10 @@ public: AndroidContentURI WithRootFilePath(const std::string &filePath) { AndroidContentURI uri = *this; - uri.file = uri.root + "/" + filePath; + uri.file = uri.root; + if (!filePath.empty()) { + uri.file += "/" + filePath; + } return uri; } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index e7587d61b8..5a5448e6e3 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -61,6 +61,7 @@ struct JNIEnv {}; #include "Common/System/System.h" #include "Common/Thread/ThreadUtil.h" #include "Common/File/Path.h" +#include "Common/File/DirListing.h" #include "Common/File/VFS/VFS.h" #include "Common/File/VFS/AssetReader.h" #include "Common/Input/InputState.h" @@ -269,7 +270,7 @@ int Android_OpenContentUriFd(const std::string &filename, Android_OpenContentUri bool Android_CreateDirectory(const std::string &rootTreeUri, const std::string &dirName) { if (!nativeActivity) { - return -1; + return false; } auto env = getEnv(); jstring paramRoot = env->NewStringUTF(rootTreeUri.c_str()); @@ -279,7 +280,7 @@ bool Android_CreateDirectory(const std::string &rootTreeUri, const std::string & bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fileName) { if (!nativeActivity) { - return -1; + return false; } auto env = getEnv(); jstring paramRoot = env->NewStringUTF(parentTreeUri.c_str()); @@ -289,20 +290,46 @@ bool Android_CreateFile(const std::string &parentTreeUri, const std::string &fil bool Android_RemoveFile(const std::string &fileUri) { if (!nativeActivity) { - return -1; + return false; } auto env = getEnv(); jstring paramFileName = env->NewStringUTF(fileUri.c_str()); return env->CallBooleanMethod(nativeActivity, contentUriRemoveFile, paramFileName); } -bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *info) { +static bool ParseFileInfo(const std::string &line, File::FileInfo *fileInfo) { + INFO_LOG(FILESYS, "!! %s", line.c_str()); + std::vector parts; + SplitString(line, '|', parts); + if (parts.size() != 5) { + ERROR_LOG(FILESYS, "Bad format: %s", line.c_str()); + return false; + } + fileInfo->name = std::string(parts[2]); + fileInfo->isDirectory = parts[0][0] == 'D'; + fileInfo->exists = true; + sscanf(parts[1].c_str(), "%ld", &fileInfo->size); + fileInfo->fullName = Path(parts[3]); + fileInfo->isWritable = false; // TODO: We don't yet request write access + sscanf(parts[4].c_str(), "%ld", &fileInfo->lastModified); + return true; +} + +bool Android_GetFileInfo(const std::string &fileUri, File::FileInfo *fileInfo) { if (!nativeActivity) { - return -1; + return false; } auto env = getEnv(); jstring paramFileUri = env->NewStringUTF(fileUri.c_str()); - return env->CallObjectMethod(nativeActivity, contentUriGetFileInfo, paramFileUri); + + jstring str = (jstring)env->CallObjectMethod(nativeActivity, contentUriGetFileInfo, paramFileUri); + if (!str) { + return false; + } + const char *charArray = env->GetStringUTFChars(str, 0); + bool retval = ParseFileInfo(std::string(charArray), fileInfo); + env->DeleteLocalRef(str); + return retval && fileInfo->exists; } std::vector Android_ListContentUri(const std::string &path) { @@ -321,21 +348,10 @@ std::vector Android_ListContentUri(const std::string &path) { const char *charArray = env->GetStringUTFChars(str, 0); if (charArray) { // paranoia std::string file = charArray; - INFO_LOG(FILESYS, "!! %s", file.c_str()); - std::vector parts; - SplitString(file, '|', parts); - if (parts.size() != 5) { - continue; - } File::FileInfo info; - info.name = parts[2]; - info.isDirectory = parts[0][0] == 'D'; - info.exists = true; - sscanf(parts[1].c_str(), "%ld", &info.size); - info.fullName = Path(parts[3]); - info.isWritable = false; // We don't yet request write access - sscanf(parts[4].c_str(), "%ld", &info.lastModified); - items.push_back(info); + if (ParseFileInfo(file, &info)) { + items.push_back(info); + } } env->ReleaseStringUTFChars(str, charArray); env->DeleteLocalRef(str); @@ -345,6 +361,9 @@ std::vector Android_ListContentUri(const std::string &path) { } int64_t Android_GetFreeSpaceByContentUri(const std::string &uri) { + if (!nativeActivity) { + return false; + } auto env = getEnv(); jstring param = env->NewStringUTF(uri.c_str()); @@ -352,6 +371,9 @@ int64_t Android_GetFreeSpaceByContentUri(const std::string &uri) { } int64_t Android_GetFreeSpaceByFilePath(const std::string &filePath) { + if (!nativeActivity) { + return false; + } auto env = getEnv(); jstring param = env->NewStringUTF(filePath.c_str()); diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index df47d30d68..9b6084c365 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -1317,7 +1317,7 @@ public abstract class NativeActivity extends Activity { } else if (command.equals("browse_folder")) { try { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION); intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); // Only allow local folders. diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index ed047f07bd..aae8330fd0 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -172,6 +172,7 @@ public class PpssppActivity extends NativeActivity { DocumentFile createdDir = documentFile.createDirectory(dirName); return createdDir != null; } else { + Log.e(TAG, "contentUriCreateDirectory: fromTreeUri returned null"); return false; } } catch (Exception e) { @@ -185,9 +186,10 @@ public class PpssppActivity extends NativeActivity { Uri uri = Uri.parse(rootTreeUri); DocumentFile documentFile = DocumentFile.fromTreeUri(this, uri); if (documentFile != null) { - DocumentFile createdFile = documentFile.createFile("application/arbitrary", fileName); + DocumentFile createdFile = documentFile.createFile("application/octet-stream", fileName); return createdFile != null; } else { + Log.e(TAG, "contentUriCreateFile: fromTreeUri returned null"); return false; } } catch (Exception e) { @@ -216,8 +218,12 @@ public class PpssppActivity extends NativeActivity { Uri uri = Uri.parse(fileName); DocumentFile documentFile = DocumentFile.fromSingleUri(this, uri); if (documentFile != null) { - String str = fileInfoToString(documentFile); - return str; + if (documentFile.exists()) { + String str = fileInfoToString(documentFile); + return str; + } else { + return null; + } } else { return null; }