Start work on supporting Android Content URIs in the FileUtil API.

Add tentative Android Content URI support to much of FileUtil.h.

Buildfix

Fix some issues with the file browser in storage URIs.
This commit is contained in:
Henrik Rydgård 2021-04-26 23:34:20 +02:00
parent b397e5c455
commit 1f018d33c5
8 changed files with 187 additions and 15 deletions

View file

@ -25,6 +25,7 @@
#include "ppsspp_config.h"
#include "android/jni/app-android.h"
#include "android/jni/AndroidContentURI.h"
#ifdef __MINGW32__
#include <unistd.h>
@ -99,6 +100,19 @@ FILE *OpenCFile(const Path &path, const char *mode) {
switch (path.Type()) {
case PathType::NATIVE:
break;
case PathType::CONTENT_URI:
if (!strcmp(mode, "r") || !strcmp(mode, "rb")) {
// Read, let's support this - easy one.
int descriptor = Android_OpenContentUriFd(path.ToString(), Android_OpenContentUriMode::READ);
if (descriptor == -1) {
return nullptr;
}
return fdopen(descriptor, "rb");
} else {
ERROR_LOG(COMMON, "OpenCFile(%s): Mode not yet supported: %s", path.c_str(), mode);
return nullptr;
}
break;
default:
ERROR_LOG(COMMON, "OpenCFile(%s): Not yet supported", path.c_str());
return nullptr;
@ -107,7 +121,7 @@ FILE *OpenCFile(const Path &path, const char *mode) {
#if defined(_WIN32) && defined(UNICODE)
return _wfopen(path.ToWString().c_str(), ConvertUTF8ToWString(mode).c_str());
#else
return fopen(path.ToString().c_str(), mode);
return fopen(path.c_str(), mode);
#endif
}
@ -148,7 +162,13 @@ std::string ResolvePath(const std::string &path) {
if (startsWith(path, "http://") || startsWith(path, "https://")) {
return path;
}
#if PPSSPP_PLATFORM(WINDOWS)
if (Android_IsContentUri(path)) {
// Nothing to do?
return path;
}
#ifdef _WIN32
static const int BUF_SIZE = 32768;
wchar_t *buf = new wchar_t[BUF_SIZE] {};
@ -210,9 +230,23 @@ static void StripTailDirSlashes(std::string &fname) {
// Returns true if file filename exists. Will return true on directories.
bool ExistsInDir(const Path &path, const std::string &filename) {
return Exists(path / filename);
if (Android_IsContentUri(path.ToString())) {
AndroidStorageContentURI uri(path.ToString());
return Exists(Path(uri.WithFilePath(filename).ToString()));
} else {
return Exists(path / filename);
}
}
bool Exists(const std::string &filename) {
if (Android_IsContentUri(filename)) {
FileInfo info;
if (!Android_GetFileInfo(filename, &info)) {
return false;
}
return info.exists;
}
std::string fn = filename;
StripTailDirSlashes(fn);
@ -247,6 +281,14 @@ bool IsDirectory(const Path &filename) {
switch (filename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
{
FileInfo info;
if (!Android_GetFileInfo(filename.ToString(), &info)) {
return false;
}
return info.isDirectory;
}
default:
return false;
}
@ -284,6 +326,11 @@ bool Delete(const Path &filename) {
switch (filename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
{
FileInfo info;
return Android_RemoveFile(filename.ToString());
}
default:
return false;
}
@ -324,11 +371,26 @@ bool CreateDir(const Path &path) {
switch (path.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
{
// Convert it to a CreateDirIn call, if possible, since that's
// what we can do with the storage API.
AndroidStorageContentURI uri(path.ToString());
std::string newDirName = uri.GetLastPart();
if (uri.NavigateUp()) {
return Android_CreateDirectory(uri.ToString(), newDirName);
} else {
// Bad path - can't create this directory.
return false;
}
break;
}
default:
return false;
}
std::string fn = path.ToString();
StripTailDirSlashes(fn);
DEBUG_LOG(COMMON, "CreateDir('%s')", fn.c_str());
#ifdef _WIN32
if (::CreateDirectory(ConvertUTF8ToWString(fn).c_str(), NULL))
@ -357,16 +419,54 @@ bool CreateDir(const Path &path) {
#endif
}
// Returns true if successful, or path already exists.
// Supports Android Content Storage URIs more reliably than CreateDir.
bool CreateDirIn(const std::string &parentDir, const std::string &newDirName) {
if (Android_IsContentUri(parentDir)) {
return Android_CreateDirectory(parentDir, newDirName);
}
std::string fn = parentDir;
StripTailDirSlashes(fn);
DEBUG_LOG(COMMON, "CreateDir('%s')", fn.c_str());
#ifdef _WIN32
std::string fullName = parentDir + "\\" + newDirName;
if (::CreateDirectory(ConvertUTF8ToWString(fullName).c_str(), NULL))
return true;
DWORD error = GetLastError();
if (error == ERROR_ALREADY_EXISTS) {
WARN_LOG(COMMON, "CreateDir: CreateDirectory failed on %s: already exists", fullName.c_str());
return true;
}
ERROR_LOG(COMMON, "CreateDir: CreateDirectory failed on %s: %08x", fullName.c_str(), (uint32_t)error);
return false;
#else
std::string fullName = parentDir + "/" + newDirName;
if (mkdir(fullName.c_str(), 0755) == 0)
return true;
int err = errno;
if (err == EEXIST) {
WARN_LOG(COMMON, "CreateDir: mkdir failed on %s: already exists", fullName.c_str());
return true;
}
ERROR_LOG(COMMON, "CreateDir: mkdir failed on %s: %s", fullName.c_str(), strerror(err));
return false;
#endif
}
// Creates the full path of fullPath returns true on success
bool CreateFullPath(const Path &path) {
switch (path.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
ERROR_LOG(COMMON, "CreateFullPath(%s): Not supported", path.c_str());
return false;
default:
return false;
}
std::string fullPath = path.ToString();
StripTailDirSlashes(fullPath);
int panicCounter = 100;
VERBOSE_LOG(COMMON, "CreateFullPath: '%s'", fullPath.c_str());
@ -413,6 +513,8 @@ bool DeleteDir(const Path &path) {
switch (path.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
return Android_RemoveFile(path.ToString());
default:
return false;
}
@ -448,11 +550,15 @@ bool Rename(const Path &srcFilename, const Path &destFilename)
switch (srcFilename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
ERROR_LOG(COMMON, "Moving files by Android URI is not yet supported");
return false;
default:
return false;
}
INFO_LOG(COMMON, "Rename: %s --> %s", srcFilename.c_str(), destFilename.c_str());
#if defined(_WIN32) && defined(UNICODE)
std::wstring srcw = srcFilename.ToWString();
std::wstring destw = destFilename.ToWString();
@ -474,12 +580,18 @@ bool Copy(const Path &srcFilename, const Path &destFilename)
switch (srcFilename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
ERROR_LOG(COMMON, "Copying files by Android URI is not yet supported");
break;
default:
return false;
}
switch (destFilename.Type()) {
case PathType::NATIVE:
break; // OK
case PathType::CONTENT_URI:
ERROR_LOG(COMMON, "Copying files by Android URI is not yet supported");
return false;
default:
return false;
}
@ -567,6 +679,15 @@ bool Move(const Path &srcFilename, const Path &destFilename) {
// Returns the size of file (64bit)
// TODO: Add a way to return an error.
uint64_t GetFileSize(const Path &filename) {
if (Android_IsContentUri(filename.ToString())) {
FileInfo info;
if (Android_GetFileInfo(filename.ToString(), &info)) {
return info.size;
} else {
return 0;
}
}
#if defined(_WIN32) && defined(UNICODE)
WIN32_FILE_ATTRIBUTE_DATA attr;
if (!GetFileAttributesEx(filename.ToWString().c_str(), GetFileExInfoStandard, &attr))
@ -595,8 +716,7 @@ uint64_t GetFileSize(const Path &filename) {
#endif
}
uint64_t GetFileSize(FILE *f)
{
uint64_t GetFileSize(FILE *f) {
// This will only support 64-bit when large file support is available.
// That won't be the case on some versions of Android, at least.
#if defined(__ANDROID__) || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS < 64)
@ -636,7 +756,6 @@ uint64_t GetFileSize(FILE *f)
// creates an empty file filename, returns true on success
bool CreateEmptyFile(const Path &filename) {
INFO_LOG(COMMON, "CreateEmptyFile: %s", filename.c_str());
FILE *pFile = OpenCFile(filename, "wb");
if (!pFile) {
@ -649,6 +768,11 @@ bool CreateEmptyFile(const Path &filename) {
// Deletes the given directory and anything under it. Returns true on success.
bool DeleteDirRecursively(const Path &directory) {
if (Android_IsContentUri(directory.ToString())) {
ERROR_LOG(COMMON, "DeleteDirRecursively(%s) not yet supported on content URIs", directory.c_str());
return false;
}
//Removed check, it prevents the UWP from deleting store downloads
INFO_LOG(COMMON, "DeleteDirRecursively: %s", directory.c_str());
@ -712,7 +836,14 @@ bool DeleteDirRecursively(const Path &directory) {
return File::DeleteDir(directory);
}
void OpenFileInEditor(const Path &fileName) {
bool OpenFileInEditor(const Path &fileName) {
if (Android_IsContentUri(fileName.ToString())) {
// TODO: This might even be supportable?
ERROR_LOG(COMMON, "OpenFileInEditor(%s) not yet supported on content URIs", fileName.c_str());
return false;
}
#if defined(_WIN32)
#if PPSSPP_PLATFORM(UWP)
// Do nothing.
@ -733,6 +864,7 @@ void OpenFileInEditor(const Path &fileName) {
ERROR_LOG(COMMON, "Failed to launch ini file");
}
#endif
return true;
}
const Path &GetExeDirectory() {

View file

@ -51,6 +51,9 @@ bool Exists(const Path &path);
// Returns true if file filename exists in directory path.
bool ExistsInDir(const Path &path, const std::string &filename);
// Returns true if file filename exists in directory path.
bool ExistsInDir(const std::string &path, const std::string &filename);
// Returns true if filename exists, and is a directory
// Supports Android content URIs.
bool IsDirectory(const Path &filename);
@ -67,6 +70,10 @@ uint64_t GetFileSize(FILE *f);
// Returns true if successful, or path already exists.
bool CreateDir(const Path &filename);
// Returns true if successful, or path already exists.
// Supports Android Content Storage URIs, unlike CreateDir.
bool CreateDirIn(const std::string &parentDir, const std::string &newDirName);
// Creates the full path of fullPath returns true on success
bool CreateFullPath(const Path &fullPath);
@ -99,7 +106,7 @@ bool CreateEmptyFile(const Path &filename);
// Opens ini file (cheats, texture replacements etc.)
// TODO: Belongs in System or something.
void OpenFileInEditor(const Path &fileName);
bool OpenFileInEditor(const Path &fileName);
// TODO: Belongs in System or something.
const Path &GetExeDirectory();

View file

@ -6,11 +6,15 @@
#include "Common/Log.h"
#include "Common/Data/Encoding/Utf8.h"
#include "android/jni/app-android.h"
Path::Path(const std::string &str) {
if (str.empty()) {
type_ = PathType::UNDEFINED;
} else if (startsWith(str, "http://") || startsWith(str, "https://")) {
type_ = PathType::HTTP;
} else if (Android_IsContentUri(str)) {
type_ = PathType::CONTENT_URI;
} else {
type_ = PathType::NATIVE;
}

View file

@ -8,7 +8,7 @@ enum class PathType {
UNDEFINED = 0,
NATIVE = 1,
// RELATIVE, // (do we need this?)
// CONTENT_URI = 2, // Android only
CONTENT_URI = 2, // Android only
HTTP = 3, // http://, https://
};

View file

@ -1170,12 +1170,8 @@ UI::EventReturn GameSettingsScreen::OnChangeBackground(UI::EventParams &e) {
const Path bgJpg = GetSysDirectory(DIRECTORY_SYSTEM) / "background.jpg";
if (File::Exists(bgPng) || File::Exists(bgJpg)) {
if (File::Exists(bgPng)) {
File::Delete(bgPng);
}
if (File::Exists(bgJpg)) {
File::Delete(bgJpg);
}
File::Delete(bgPng);
File::Delete(bgJpg);
NativeMessageReceived("bgImage_updated", "");
} else {

View file

@ -493,7 +493,7 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch
g_Config.defaultCurrentDirectory = Path("/");
g_Config.internalDataDirectory = Path(savegame_dir);
#if defined(__ANDROID__)
#if PPSSPP_PLATFORM(ANDROID)
// TODO: This needs to change in Android 12.
//
@ -515,6 +515,8 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch
} else {
ERROR_LOG(SYSTEM, "Couldn't read directory '%s' specified by memstick_dir.txt.", memstickDir.c_str());
}
} else {
INFO_LOG(SYSTEM, "No memstick directory file found. Using '%s'", memstickDirFile.c_str());
}
#elif PPSSPP_PLATFORM(IOS)

View file

@ -75,6 +75,26 @@ public:
return file.size() > root.size();
}
std::string GetLastPart() const {
if (IsTreeURI()) {
// Can't do anything anyway.
return std::string();
}
if (!CanNavigateUp()) {
// Kinda useless to get the "primary:" volume.
return std::string();
}
size_t slash = file.rfind('/');
if (slash == std::string::npos) {
return std::string();
}
std::string part = file.substr(slash + 1);
return part;
}
bool NavigateUp() {
if (!CanNavigateUp()) {
return false;
@ -106,6 +126,11 @@ public:
}
}
// Never store the output of this, only show it to the user.
std::string ToVisualString() const {
return file;
}
const std::string &FilePath() const {
return file;
}

View file

@ -615,9 +615,15 @@ static bool TestAndroidContentURI() {
AndroidStorageContentURI treeURI;
EXPECT_TRUE(treeURI.Parse(std::string(treeURIString)));
AndroidStorageContentURI dirURI;
EXPECT_TRUE(dirURI.Parse(std::string(directoryURIString)));
AndroidStorageContentURI fileURI;
EXPECT_TRUE(fileURI.Parse(std::string(fileURIString)));
std::string lastPart = dirURI.GetLastPart();
EXPECT_EQ_STR(fileURI.GetLastPart(), std::string("Tekken 6.iso"));
EXPECT_TRUE(treeURI.TreeContains(fileURI));
EXPECT_TRUE(fileURI.CanNavigateUp());