AndroidContentURI: Move code from the header to cpp. Some assorted cleanup, add a unit test for Download paths
This commit is contained in:
parent
acea6deb00
commit
cf9a628a2e
21 changed files with 317 additions and 256 deletions
|
@ -614,6 +614,8 @@ add_library(Common STATIC
|
|||
Common/File/VFS/DirectoryReader.h
|
||||
Common/File/AndroidStorage.h
|
||||
Common/File/AndroidStorage.cpp
|
||||
Common/File/AndroidContentURI.h
|
||||
Common/File/AndroidContentURI.cpp
|
||||
Common/File/DiskFree.h
|
||||
Common/File/DiskFree.cpp
|
||||
Common/File/Path.h
|
||||
|
|
|
@ -434,6 +434,7 @@
|
|||
<ClInclude Include="Data\Text\Parsers.h" />
|
||||
<ClInclude Include="Data\Text\WrapText.h" />
|
||||
<ClInclude Include="FakeEmitter.h" />
|
||||
<ClInclude Include="File\AndroidContentURI.h" />
|
||||
<ClInclude Include="File\AndroidStorage.h" />
|
||||
<ClInclude Include="File\DirListing.h" />
|
||||
<ClInclude Include="File\DiskFree.h" />
|
||||
|
@ -874,6 +875,7 @@
|
|||
<ClCompile Include="Data\Text\I18n.cpp" />
|
||||
<ClCompile Include="Data\Text\Parsers.cpp" />
|
||||
<ClCompile Include="Data\Text\WrapText.cpp" />
|
||||
<ClCompile Include="File\AndroidContentURI.cpp" />
|
||||
<ClCompile Include="File\AndroidStorage.cpp" />
|
||||
<ClCompile Include="File\DirListing.cpp" />
|
||||
<ClCompile Include="File\DiskFree.cpp" />
|
||||
|
|
|
@ -497,6 +497,9 @@
|
|||
<ClInclude Include="System\Request.h">
|
||||
<Filter>System</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="File\AndroidContentURI.h">
|
||||
<Filter>File</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="ABI.cpp" />
|
||||
|
@ -926,6 +929,9 @@
|
|||
<ClCompile Include="System\Request.cpp">
|
||||
<Filter>System</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="File\AndroidContentURI.cpp">
|
||||
<Filter>File</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="Crypto">
|
||||
|
|
182
Common/File/AndroidContentURI.cpp
Normal file
182
Common/File/AndroidContentURI.cpp
Normal file
|
@ -0,0 +1,182 @@
|
|||
#include "Common/File/AndroidContentURI.h"
|
||||
|
||||
bool AndroidContentURI::Parse(const std::string &path) {
|
||||
const char *prefix = "content://";
|
||||
if (!startsWith(path, prefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string components = path.substr(strlen(prefix));
|
||||
|
||||
std::vector<std::string> parts;
|
||||
SplitString(components, '/', parts);
|
||||
if (parts.size() == 3) {
|
||||
// Single file URI.
|
||||
provider = parts[0];
|
||||
if (parts[1] == "tree") {
|
||||
// Single directory URI.
|
||||
// Not sure when we encounter these?
|
||||
// file empty signals this type.
|
||||
root = UriDecode(parts[2]);
|
||||
return true;
|
||||
} else if (parts[1] == "document") {
|
||||
// root empty signals this type.
|
||||
file = UriDecode(parts[2]);
|
||||
return true;
|
||||
} else {
|
||||
// What's this?
|
||||
return false;
|
||||
}
|
||||
} else if (parts.size() == 5) {
|
||||
// Tree URI
|
||||
provider = parts[0];
|
||||
if (parts[1] != "tree") {
|
||||
return false;
|
||||
}
|
||||
root = UriDecode(parts[2]);
|
||||
if (parts[3] != "document") {
|
||||
return false;
|
||||
}
|
||||
file = UriDecode(parts[4]);
|
||||
// Sanity check file path.
|
||||
return startsWith(file, root);
|
||||
} else {
|
||||
// Invalid Content URI
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AndroidContentURI AndroidContentURI::WithRootFilePath(const std::string &filePath) {
|
||||
if (root.empty()) {
|
||||
ERROR_LOG(SYSTEM, "WithRootFilePath cannot be used with single file URIs.");
|
||||
return *this;
|
||||
}
|
||||
|
||||
AndroidContentURI uri = *this;
|
||||
uri.file = uri.root;
|
||||
if (!filePath.empty()) {
|
||||
uri.file += "/" + filePath;
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
AndroidContentURI AndroidContentURI::WithComponent(const std::string &filePath) {
|
||||
AndroidContentURI uri = *this;
|
||||
uri.file = uri.file + "/" + filePath;
|
||||
return uri;
|
||||
}
|
||||
|
||||
AndroidContentURI AndroidContentURI::WithExtraExtension(const std::string &extension) {
|
||||
AndroidContentURI uri = *this;
|
||||
uri.file = uri.file + extension;
|
||||
return uri;
|
||||
}
|
||||
|
||||
AndroidContentURI AndroidContentURI::WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const {
|
||||
_dbg_assert_(!oldExtension.empty() && oldExtension[0] == '.');
|
||||
_dbg_assert_(!newExtension.empty() && newExtension[0] == '.');
|
||||
AndroidContentURI uri = *this;
|
||||
if (endsWithNoCase(file, oldExtension)) {
|
||||
uri.file = file.substr(0, file.size() - oldExtension.size()) + newExtension;
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
AndroidContentURI AndroidContentURI::WithReplacedExtension(const std::string &newExtension) const {
|
||||
_dbg_assert_(!newExtension.empty() && newExtension[0] == '.');
|
||||
AndroidContentURI uri = *this;
|
||||
if (file.empty()) {
|
||||
return uri;
|
||||
}
|
||||
std::string extension = GetFileExtension();
|
||||
uri.file = file.substr(0, file.size() - extension.size()) + newExtension;
|
||||
return uri;
|
||||
}
|
||||
|
||||
bool AndroidContentURI::CanNavigateUp() const {
|
||||
if (root.empty()) {
|
||||
return false;
|
||||
}
|
||||
return file.size() > root.size();
|
||||
}
|
||||
|
||||
// Only goes downwards in hierarchies. No ".." will ever be generated.
|
||||
bool AndroidContentURI::ComputePathTo(const AndroidContentURI &other, std::string &path) const {
|
||||
size_t offset = FilePath().size() + 1;
|
||||
std::string otherFilePath = other.FilePath();
|
||||
if (offset >= otherFilePath.size()) {
|
||||
ERROR_LOG(SYSTEM, "Bad call to PathTo. '%s' -> '%s'", FilePath().c_str(), other.FilePath().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
path = other.FilePath().substr(FilePath().size() + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string AndroidContentURI::GetFileExtension() const {
|
||||
size_t pos = file.rfind(".");
|
||||
if (pos == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
size_t slash_pos = file.rfind("/");
|
||||
if (slash_pos != std::string::npos && slash_pos > pos) {
|
||||
// Don't want to detect "df/file" from "/as.df/file"
|
||||
return "";
|
||||
}
|
||||
std::string ext = file.substr(pos);
|
||||
for (size_t i = 0; i < ext.size(); i++) {
|
||||
ext[i] = tolower(ext[i]);
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
std::string AndroidContentURI::GetLastPart() const {
|
||||
if (file.empty()) {
|
||||
// Can't do anything anyway.
|
||||
return std::string();
|
||||
}
|
||||
|
||||
if (!CanNavigateUp()) {
|
||||
size_t colon = file.rfind(':');
|
||||
if (colon == std::string::npos) {
|
||||
return std::string();
|
||||
}
|
||||
return file.substr(colon + 1);
|
||||
}
|
||||
|
||||
size_t slash = file.rfind('/');
|
||||
if (slash == std::string::npos) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string part = file.substr(slash + 1);
|
||||
return part;
|
||||
}
|
||||
|
||||
bool AndroidContentURI::NavigateUp() {
|
||||
if (!CanNavigateUp()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t slash = file.rfind('/');
|
||||
if (slash == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file = file.substr(0, slash);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
std::string AndroidContentURI::ToString() const {
|
||||
if (file.empty()) {
|
||||
// Tree URI
|
||||
return StringFromFormat("content://%s/tree/%s", provider.c_str(), UriEncode(root).c_str());
|
||||
} else if (root.empty()) {
|
||||
// Single file URI
|
||||
return StringFromFormat("content://%s/document/%s", provider.c_str(), UriEncode(file).c_str());
|
||||
} else {
|
||||
// File URI from Tree
|
||||
return StringFromFormat("content://%s/tree/%s/document/%s", provider.c_str(), UriEncode(root).c_str(), UriEncode(file).c_str());
|
||||
}
|
||||
}
|
69
Common/File/AndroidContentURI.h
Normal file
69
Common/File/AndroidContentURI.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Net/URL.h"
|
||||
#include "Common/Log.h"
|
||||
|
||||
// Utility to deal with Android storage URIs of the forms:
|
||||
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO
|
||||
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO
|
||||
|
||||
// This file compiles on all platforms, to reduce the need for ifdefs.
|
||||
|
||||
// I am not 100% sure it's OK to rely on the internal format of file content URIs.
|
||||
// On the other hand, I'm sure tons of apps would break if these changed, so I think we can
|
||||
// consider them pretty stable. Additionally, the official Document library just manipulates the URIs
|
||||
// in similar ways...
|
||||
class AndroidContentURI {
|
||||
private:
|
||||
std::string provider;
|
||||
std::string root;
|
||||
std::string file;
|
||||
public:
|
||||
AndroidContentURI() {}
|
||||
explicit AndroidContentURI(const std::string &path) {
|
||||
Parse(path);
|
||||
}
|
||||
|
||||
bool Parse(const std::string &path);
|
||||
|
||||
AndroidContentURI WithRootFilePath(const std::string &filePath);
|
||||
AndroidContentURI WithComponent(const std::string &filePath);
|
||||
AndroidContentURI WithExtraExtension(const std::string &extension);
|
||||
AndroidContentURI WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const;
|
||||
AndroidContentURI WithReplacedExtension(const std::string &newExtension) const;
|
||||
|
||||
bool CanNavigateUp() const;
|
||||
|
||||
// Only goes downwards in hierarchies. No ".." will ever be generated.
|
||||
bool ComputePathTo(const AndroidContentURI &other, std::string &path) const;
|
||||
|
||||
std::string GetFileExtension() const;
|
||||
std::string GetLastPart() const;
|
||||
|
||||
bool NavigateUp();
|
||||
|
||||
bool TreeContains(const AndroidContentURI &fileURI) {
|
||||
if (root.empty()) {
|
||||
return false;
|
||||
}
|
||||
return startsWith(fileURI.file, root);
|
||||
}
|
||||
|
||||
std::string ToString() const;
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
const std::string &RootPath() const {
|
||||
return root.empty() ? file : root;
|
||||
}
|
||||
};
|
|
@ -25,7 +25,6 @@
|
|||
#include "ppsspp_config.h"
|
||||
|
||||
#include "android/jni/app-android.h"
|
||||
#include "android/jni/AndroidContentURI.h"
|
||||
|
||||
#ifdef __MINGW32__
|
||||
#include <unistd.h>
|
||||
|
@ -33,12 +32,14 @@
|
|||
#define _POSIX_THREAD_SAFE_FUNCTIONS 200112L
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <memory>
|
||||
|
||||
#include "Common/Log.h"
|
||||
#include "Common/LogReporting.h"
|
||||
#include "Common/File/AndroidContentURI.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/SysError.h"
|
||||
|
|
|
@ -4,13 +4,13 @@
|
|||
#include <cstring>
|
||||
|
||||
#include "Common/File/Path.h"
|
||||
#include "Common/File/AndroidContentURI.h"
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Log.h"
|
||||
#include "Common/Data/Encoding/Utf8.h"
|
||||
|
||||
#include "android/jni/app-android.h"
|
||||
#include "android/jni/AndroidContentURI.h"
|
||||
|
||||
#if HOST_IS_CASE_SENSITIVE
|
||||
#include <dirent.h>
|
||||
|
@ -157,7 +157,7 @@ std::string Path::GetFilename() const {
|
|||
return path_;
|
||||
}
|
||||
|
||||
static std::string GetExtFromString(const std::string &str) {
|
||||
std::string GetExtFromString(const std::string &str) {
|
||||
size_t pos = str.rfind(".");
|
||||
if (pos == std::string::npos) {
|
||||
return "";
|
||||
|
@ -177,7 +177,7 @@ static std::string GetExtFromString(const std::string &str) {
|
|||
std::string Path::GetFileExtension() const {
|
||||
if (type_ == PathType::CONTENT_URI) {
|
||||
AndroidContentURI uri(path_);
|
||||
return GetExtFromString(uri.FilePath());
|
||||
return uri.GetFileExtension();
|
||||
}
|
||||
return GetExtFromString(path_);
|
||||
}
|
||||
|
|
|
@ -133,6 +133,8 @@ private:
|
|||
PathType type_;
|
||||
};
|
||||
|
||||
// Utility function for parsing out file extensions.
|
||||
std::string GetExtFromString(const std::string &str);
|
||||
|
||||
// Utility function for fixing the case of paths. Only present on Unix-like systems.
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
#include "android/jni/app-android.h"
|
||||
#include "android/jni/AndroidContentURI.h"
|
||||
#endif
|
||||
|
||||
bool LoadRemoteFileList(const Path &url, const std::string &userAgent, bool *cancel, std::vector<File::FileInfo> &files) {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
using namespace PPSSPP_VK;
|
||||
|
||||
// Debug help: adb logcat -s DEBUG PPSSPPNativeActivity PPSSPP NativeGLView NativeRenderer NativeSurfaceView PowerSaveModeReceiver InputDeviceState
|
||||
// Debug help: adb logcat -s DEBUG AndroidRuntime PPSSPPNativeActivity PPSSPP NativeGLView NativeRenderer NativeSurfaceView PowerSaveModeReceiver InputDeviceState PpssppActivity CameraHelper
|
||||
|
||||
static void MergeRenderAreaRectInto(VkRect2D *dest, const VkRect2D &src) {
|
||||
if (dest->offset.x > src.offset.x) {
|
||||
|
|
|
@ -93,6 +93,20 @@ std::vector<std::string> ParamSFOData::GetKeys() const {
|
|||
return result;
|
||||
}
|
||||
|
||||
std::string ParamSFOData::GetDiscID() {
|
||||
const std::string discID = GetValueString("DISC_ID");
|
||||
if (discID.empty()) {
|
||||
std::string fakeID = GenerateFakeID();
|
||||
WARN_LOG(LOADER, "No DiscID found - generating a fake one: '%s' (from %s)", fakeID.c_str(), PSP_CoreParameter().fileToStart.c_str());
|
||||
ValueData data;
|
||||
data.type = VT_UTF8;
|
||||
data.s_value = fakeID;
|
||||
values["DISC_ID"] = data;
|
||||
return fakeID;
|
||||
}
|
||||
return discID;
|
||||
}
|
||||
|
||||
// I'm so sorry Ced but this is highly endian unsafe :(
|
||||
bool ParamSFOData::ReadSFO(const u8 *paramsfo, size_t size) {
|
||||
if (size < sizeof(Header))
|
||||
|
@ -319,8 +333,13 @@ std::string ParamSFOData::GenerateFakeID(std::string filename) const {
|
|||
int sumOfAllLetters = 0;
|
||||
for (char &c : file) {
|
||||
sumOfAllLetters += c;
|
||||
// Get rid of some garbage characters than can arise when opening content URIs. Well, I've only seen '%', but...
|
||||
if (strchr("%() []", c) != nullptr) {
|
||||
c = 'X';
|
||||
} else {
|
||||
c = toupper(c);
|
||||
}
|
||||
}
|
||||
|
||||
if (file.size() < 4) {
|
||||
file += "HOME";
|
||||
|
|
|
@ -38,19 +38,7 @@ public:
|
|||
std::vector<std::string> GetKeys() const;
|
||||
std::string GenerateFakeID(std::string filename = "") const;
|
||||
|
||||
std::string GetDiscID() {
|
||||
const std::string discID = GetValueString("DISC_ID");
|
||||
if (discID.empty()) {
|
||||
std::string fakeID = GenerateFakeID();
|
||||
WARN_LOG(LOADER, "No DiscID found - generating a fake one: '%s'", fakeID.c_str());
|
||||
ValueData data;
|
||||
data.type = VT_UTF8;
|
||||
data.s_value = fakeID;
|
||||
values["DISC_ID"] = data;
|
||||
return fakeID;
|
||||
}
|
||||
return discID;
|
||||
}
|
||||
std::string GetDiscID();
|
||||
|
||||
bool ReadSFO(const u8 *paramsfo, size_t size);
|
||||
bool WriteSFO(u8 **paramsfo, size_t *size) const;
|
||||
|
|
|
@ -128,6 +128,7 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin
|
|||
|
||||
// OK, quick methods of identification for common types failed. Moving on to more expensive methods,
|
||||
// starting by reading the first few bytes.
|
||||
// This can be necessary for weird Android content storage path types, see issue #17462
|
||||
|
||||
u32_le id;
|
||||
|
||||
|
@ -170,9 +171,9 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader, std::string *errorStrin
|
|||
|
||||
if (id == 'FLE\x7F') {
|
||||
Path filename = fileLoader->GetPath();
|
||||
// There are a few elfs misnamed as pbp (like Trig Wars), accept that.
|
||||
// There are a few elfs misnamed as pbp (like Trig Wars), accept that. Also accept extension-less paths.
|
||||
if (extension == ".plf" || strstr(filename.GetFilename().c_str(), "BOOT.BIN") ||
|
||||
extension == ".elf" || extension == ".prx" || extension == ".pbp") {
|
||||
extension == ".elf" || extension == ".prx" || extension == ".pbp" || extension == "") {
|
||||
return IdentifiedFileType::PSP_ELF;
|
||||
}
|
||||
return IdentifiedFileType::UNKNOWN_ELF;
|
||||
|
@ -296,11 +297,12 @@ bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
// Looks like a wrong fall through but is not, both paths are handled above.
|
||||
|
||||
case IdentifiedFileType::PSP_PBP:
|
||||
case IdentifiedFileType::PSP_ELF:
|
||||
{
|
||||
INFO_LOG(LOADER, "File is an ELF or loose PBP!");
|
||||
INFO_LOG(LOADER, "File is an ELF or loose PBP! %s", fileLoader->GetPath().c_str());
|
||||
return Load_PSP_ELF_PBP(fileLoader, error_string);
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
#include "UI/Theme.h"
|
||||
|
||||
#include "Common/File/FileUtil.h"
|
||||
#include "Common/File/AndroidContentURI.h"
|
||||
#include "Common/OSVersion.h"
|
||||
#include "Common/TimeUtil.h"
|
||||
#include "Common/StringUtils.h"
|
||||
|
@ -88,7 +89,6 @@
|
|||
#if PPSSPP_PLATFORM(ANDROID)
|
||||
|
||||
#include "android/jni/AndroidAudio.h"
|
||||
#include "android/jni/AndroidContentURI.h"
|
||||
|
||||
extern AndroidAudioState *g_audioState;
|
||||
|
||||
|
|
|
@ -285,6 +285,7 @@
|
|||
<ClInclude Include="..\..\Common\BitSet.h" />
|
||||
<ClInclude Include="..\..\Common\Buffer.h" />
|
||||
<ClInclude Include="..\..\Common\Data\Format\DDSLoad.h" />
|
||||
<ClInclude Include="..\..\Common\File\AndroidContentURI.h" />
|
||||
<ClInclude Include="..\..\Common\File\AndroidStorage.h" />
|
||||
<ClInclude Include="..\..\Common\GPU\Vulkan\VulkanLoader.h" />
|
||||
<ClInclude Include="..\..\Common\Math\Statistics.h" />
|
||||
|
@ -440,6 +441,7 @@
|
|||
<ClCompile Include="..\..\Common\ArmEmitter.cpp" />
|
||||
<ClCompile Include="..\..\Common\Buffer.cpp" />
|
||||
<ClCompile Include="..\..\Common\Data\Format\DDSLoad.cpp" />
|
||||
<ClCompile Include="..\..\Common\File\AndroidContentURI.cpp" />
|
||||
<ClCompile Include="..\..\Common\File\AndroidStorage.cpp" />
|
||||
<ClCompile Include="..\..\Common\GPU\Vulkan\VulkanLoader.cpp" />
|
||||
<ClCompile Include="..\..\Common\Math\Statistics.cpp" />
|
||||
|
|
|
@ -432,6 +432,9 @@
|
|||
<ClCompile Include="..\..\Common\System\Request.cpp">
|
||||
<Filter>System</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\Common\File\AndroidContentURI.cpp">
|
||||
<Filter>File</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="targetver.h" />
|
||||
|
@ -817,6 +820,9 @@
|
|||
<ClInclude Include="..\..\Common\System\Request.h">
|
||||
<Filter>System</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\Common\File\AndroidContentURI.h">
|
||||
<Filter>File</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="..\..\Common\Math\fast\fast_matrix_neon.S">
|
||||
|
|
|
@ -177,6 +177,7 @@ EXEC_AND_LIB_FILES := \
|
|||
$(SRC)/Common/Data/Text/Parsers.cpp \
|
||||
$(SRC)/Common/Data/Text/WrapText.cpp \
|
||||
$(SRC)/Common/File/AndroidStorage.cpp \
|
||||
$(SRC)/Common/File/AndroidContentURI.cpp \
|
||||
$(SRC)/Common/File/VFS/VFS.cpp \
|
||||
$(SRC)/Common/File/VFS/ZipFileReader.cpp \
|
||||
$(SRC)/Common/File/VFS/DirectoryReader.cpp \
|
||||
|
|
|
@ -1,229 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "Common/StringUtils.h"
|
||||
#include "Common/Net/URL.h"
|
||||
#include "Common/Log.h"
|
||||
|
||||
// Utility to deal with Android storage URIs of the forms:
|
||||
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO
|
||||
// content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO
|
||||
|
||||
// This file compiles on all platforms, to reduce the need for ifdefs.
|
||||
|
||||
// I am not 100% sure it's OK to rely on the internal format of file content URIs.
|
||||
// On the other hand, I'm sure tons of apps would break if these changed, so I think we can
|
||||
// consider them pretty stable. Additionally, the official Document library just manipulates the URIs
|
||||
// in similar ways...
|
||||
class AndroidContentURI {
|
||||
private:
|
||||
std::string provider;
|
||||
std::string root;
|
||||
std::string file;
|
||||
public:
|
||||
AndroidContentURI() {}
|
||||
explicit AndroidContentURI(const std::string &path) {
|
||||
Parse(path);
|
||||
}
|
||||
|
||||
bool Parse(const std::string &path) {
|
||||
const char *prefix = "content://";
|
||||
if (!startsWith(path, prefix)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string components = path.substr(strlen(prefix));
|
||||
|
||||
std::vector<std::string> parts;
|
||||
SplitString(components, '/', parts);
|
||||
if (parts.size() == 3) {
|
||||
// Single file URI.
|
||||
provider = parts[0];
|
||||
if (parts[1] == "tree") {
|
||||
// Single directory URI.
|
||||
// Not sure when we encounter these?
|
||||
// file empty signals this type.
|
||||
root = UriDecode(parts[2]);
|
||||
return true;
|
||||
} else if (parts[1] == "document") {
|
||||
// root empty signals this type.
|
||||
file = UriDecode(parts[2]);
|
||||
return true;
|
||||
} else {
|
||||
// What's this?
|
||||
return false;
|
||||
}
|
||||
} else if (parts.size() == 5) {
|
||||
// Tree URI
|
||||
provider = parts[0];
|
||||
if (parts[1] != "tree") {
|
||||
return false;
|
||||
}
|
||||
root = UriDecode(parts[2]);
|
||||
if (parts[3] != "document") {
|
||||
return false;
|
||||
}
|
||||
file = UriDecode(parts[4]);
|
||||
// Sanity check file path.
|
||||
return startsWith(file, root);
|
||||
} else {
|
||||
// Invalid Content URI
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AndroidContentURI WithRootFilePath(const std::string &filePath) {
|
||||
if (root.empty()) {
|
||||
ERROR_LOG(SYSTEM, "WithRootFilePath cannot be used with single file URIs.");
|
||||
return *this;
|
||||
}
|
||||
|
||||
AndroidContentURI uri = *this;
|
||||
uri.file = uri.root;
|
||||
if (!filePath.empty()) {
|
||||
uri.file += "/" + filePath;
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
AndroidContentURI WithComponent(const std::string &filePath) {
|
||||
AndroidContentURI uri = *this;
|
||||
uri.file = uri.file + "/" + filePath;
|
||||
return uri;
|
||||
}
|
||||
|
||||
AndroidContentURI WithExtraExtension(const std::string &extension) {
|
||||
AndroidContentURI uri = *this;
|
||||
uri.file = uri.file + extension;
|
||||
return uri;
|
||||
}
|
||||
|
||||
AndroidContentURI WithReplacedExtension(const std::string &oldExtension, const std::string &newExtension) const {
|
||||
_dbg_assert_(!oldExtension.empty() && oldExtension[0] == '.');
|
||||
_dbg_assert_(!newExtension.empty() && newExtension[0] == '.');
|
||||
AndroidContentURI uri = *this;
|
||||
if (endsWithNoCase(file, oldExtension)) {
|
||||
uri.file = file.substr(0, file.size() - oldExtension.size()) + newExtension;
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
|
||||
AndroidContentURI WithReplacedExtension(const std::string &newExtension) const {
|
||||
_dbg_assert_(!newExtension.empty() && newExtension[0] == '.');
|
||||
AndroidContentURI uri = *this;
|
||||
if (file.empty()) {
|
||||
return uri;
|
||||
}
|
||||
std::string extension = GetFileExtension();
|
||||
uri.file = file.substr(0, file.size() - extension.size()) + newExtension;
|
||||
return uri;
|
||||
}
|
||||
|
||||
bool CanNavigateUp() const {
|
||||
if (root.empty()) {
|
||||
return false;
|
||||
}
|
||||
return file.size() > root.size();
|
||||
}
|
||||
|
||||
// Only goes downwards in hierarchies. No ".." will ever be generated.
|
||||
bool ComputePathTo(const AndroidContentURI &other, std::string &path) const {
|
||||
size_t offset = FilePath().size() + 1;
|
||||
std::string otherFilePath = other.FilePath();
|
||||
if (offset >= otherFilePath.size()) {
|
||||
ERROR_LOG(SYSTEM, "Bad call to PathTo. '%s' -> '%s'", FilePath().c_str(), other.FilePath().c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
path = other.FilePath().substr(FilePath().size() + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string GetFileExtension() const {
|
||||
size_t pos = file.rfind(".");
|
||||
if (pos == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
size_t slash_pos = file.rfind("/");
|
||||
if (slash_pos != std::string::npos && slash_pos > pos) {
|
||||
// Don't want to detect "df/file" from "/as.df/file"
|
||||
return "";
|
||||
}
|
||||
std::string ext = file.substr(pos);
|
||||
for (size_t i = 0; i < ext.size(); i++) {
|
||||
ext[i] = tolower(ext[i]);
|
||||
}
|
||||
return ext;
|
||||
}
|
||||
|
||||
std::string GetLastPart() const {
|
||||
if (file.empty()) {
|
||||
// Can't do anything anyway.
|
||||
return std::string();
|
||||
}
|
||||
|
||||
if (!CanNavigateUp()) {
|
||||
size_t colon = file.rfind(':');
|
||||
if (colon == std::string::npos) {
|
||||
return std::string();
|
||||
}
|
||||
return file.substr(colon + 1);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
size_t slash = file.rfind('/');
|
||||
if (slash == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file = file.substr(0, slash);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TreeContains(const AndroidContentURI &fileURI) {
|
||||
if (root.empty()) {
|
||||
return false;
|
||||
}
|
||||
return startsWith(fileURI.file, root);
|
||||
}
|
||||
|
||||
std::string ToString() const {
|
||||
if (file.empty()) {
|
||||
// Tree URI
|
||||
return StringFromFormat("content://%s/tree/%s", provider.c_str(), UriEncode(root).c_str());
|
||||
} else if (root.empty()) {
|
||||
// Single file URI
|
||||
return StringFromFormat("content://%s/document/%s", provider.c_str(), UriEncode(file).c_str());
|
||||
} else {
|
||||
// File URI from Tree
|
||||
return StringFromFormat("content://%s/tree/%s/document/%s", provider.c_str(), UriEncode(root).c_str(), UriEncode(file).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
const std::string &RootPath() const {
|
||||
return root.empty() ? file : root;
|
||||
}
|
||||
};
|
|
@ -1196,7 +1196,9 @@ public abstract class NativeActivity extends Activity {
|
|||
String path = selectedDirectoryUri.toString();
|
||||
Log.i(TAG, "Browse folder finished: " + path);
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= 19) {
|
||||
getContentResolver().takePersistableUriPermission(selectedDirectoryUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.w(TAG, "Exception getting permissions for document: " + e.toString());
|
||||
}
|
||||
|
|
|
@ -276,6 +276,7 @@ SOURCES_CXX += \
|
|||
$(COMMONDIR)/File/VFS/DirectoryReader.cpp \
|
||||
$(COMMONDIR)/File/VFS/ZipFileReader.cpp \
|
||||
$(COMMONDIR)/File/AndroidStorage.cpp \
|
||||
$(COMMONDIR)/File/AndroidContentURI.cpp \
|
||||
$(COMMONDIR)/File/DiskFree.cpp \
|
||||
$(COMMONDIR)/File/Path.cpp \
|
||||
$(COMMONDIR)/File/PathBrowser.cpp \
|
||||
|
|
|
@ -73,7 +73,7 @@
|
|||
#include "GPU/Common/TextureDecoder.h"
|
||||
#include "GPU/Common/GPUStateUtils.h"
|
||||
|
||||
#include "android/jni/AndroidContentURI.h"
|
||||
#include "Common/File/AndroidContentURI.h"
|
||||
|
||||
#include "unittest/JitHarness.h"
|
||||
#include "unittest/TestVertexJit.h"
|
||||
|
@ -692,7 +692,7 @@ static bool TestAndroidContentURI() {
|
|||
static const char *directoryURIString = "content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO";
|
||||
static const char *fileTreeURIString = "content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO%2FTekken%206.iso";
|
||||
static const char *fileNonTreeString = "content://com.android.externalstorage.documents/document/primary%3APSP%2Fcrash_bad_execaddr.prx";
|
||||
|
||||
static const char *downloadURIString = "content://com.android.providers.downloads.documents/document/msf%3A10000000006";
|
||||
|
||||
AndroidContentURI treeURI;
|
||||
EXPECT_TRUE(treeURI.Parse(std::string(treeURIString)));
|
||||
|
@ -724,6 +724,12 @@ static bool TestAndroidContentURI() {
|
|||
EXPECT_EQ_STR(fileURI.GetFileExtension(), std::string(".prx"));
|
||||
EXPECT_FALSE(fileURI.CanNavigateUp());
|
||||
|
||||
// These are annoying because they hide the actual filename, and we can't get at a parent folder,
|
||||
// which confuses our elf loading.
|
||||
AndroidContentURI downloadURI;
|
||||
EXPECT_TRUE(downloadURI.Parse(std::string(downloadURIString)));
|
||||
EXPECT_EQ_STR(downloadURI.GetLastPart(), std::string("10000000006"));
|
||||
EXPECT_FALSE(downloadURI.CanNavigateUp());
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue