Add support for writing to file. Basics work now (having memstick on a content path)

This commit is contained in:
Henrik Rydgård 2021-06-02 17:04:19 +02:00
parent dba0a6ba12
commit 2f31cb12fb
8 changed files with 100 additions and 35 deletions

View File

@ -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

View File

@ -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)

View File

@ -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;
}

View File

@ -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();

View File

@ -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;
}

View File

@ -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<std::string> 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<File::FileInfo> Android_ListContentUri(const std::string &path) {
@ -321,22 +348,11 @@ std::vector<File::FileInfo> 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<std::string> 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);
if (ParseFileInfo(file, &info)) {
items.push_back(info);
}
}
env->ReleaseStringUTFChars(str, charArray);
env->DeleteLocalRef(str);
}
@ -345,6 +361,9 @@ std::vector<File::FileInfo> 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());

View File

@ -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.

View File

@ -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,11 +218,15 @@ public class PpssppActivity extends NativeActivity {
Uri uri = Uri.parse(fileName);
DocumentFile documentFile = DocumentFile.fromSingleUri(this, uri);
if (documentFile != null) {
if (documentFile.exists()) {
String str = fileInfoToString(documentFile);
return str;
} else {
return null;
}
} else {
return null;
}
} catch (Exception e) {
Log.e(TAG, "contentUriGetFileInfo exception: " + e.toString());
return null;