Android: Allow opening/getting files relative to downloads directory

This commit is contained in:
Connor McLaughlin
2021-04-24 16:03:28 +10:00
parent 600ae7bcc0
commit 46d19eeb1f
16 changed files with 281 additions and 41 deletions

View File

@ -2,6 +2,7 @@
#include "assert.h"
#include "file_system.h"
#include "log.h"
#include "string_util.h"
#include <array>
Log_SetChannel(CDImage);
@ -17,45 +18,50 @@ u32 CDImage::GetBytesPerSector(TrackMode mode)
std::unique_ptr<CDImage> CDImage::Open(const char* filename, Common::Error* error)
{
const char* extension = std::strrchr(filename, '.');
const char* extension;
#ifdef __ANDROID__
std::string filename_display_name(FileSystem::GetDisplayNameFromPath(filename));
if (filename_display_name.empty())
filename_display_name = filename;
extension = std::strrchr(filename_display_name.c_str(), '.');
#else
extension = std::strrchr(filename, '.');
#endif
if (!extension)
{
Log_ErrorPrintf("Invalid filename: '%s'", filename);
return nullptr;
}
#ifdef _MSC_VER
#define CASE_COMPARE _stricmp
#else
#define CASE_COMPARE strcasecmp
#endif
if (CASE_COMPARE(extension, ".cue") == 0)
if (StringUtil::Strcasecmp(extension, ".cue") == 0)
{
return OpenCueSheetImage(filename, error);
}
else if (CASE_COMPARE(extension, ".bin") == 0 || CASE_COMPARE(extension, ".img") == 0 ||
CASE_COMPARE(extension, ".iso") == 0)
else if (StringUtil::Strcasecmp(extension, ".bin") == 0 || StringUtil::Strcasecmp(extension, ".img") == 0 ||
StringUtil::Strcasecmp(extension, ".iso") == 0)
{
return OpenBinImage(filename, error);
}
else if (CASE_COMPARE(extension, ".chd") == 0)
else if (StringUtil::Strcasecmp(extension, ".chd") == 0)
{
return OpenCHDImage(filename, error);
}
else if (CASE_COMPARE(extension, ".ecm") == 0)
else if (StringUtil::Strcasecmp(extension, ".ecm") == 0)
{
return OpenEcmImage(filename, error);
}
else if (CASE_COMPARE(extension, ".mds") == 0)
else if (StringUtil::Strcasecmp(extension, ".mds") == 0)
{
return OpenMdsImage(filename, error);
}
else if (CASE_COMPARE(extension, ".pbp") == 0)
else if (StringUtil::Strcasecmp(extension, ".pbp") == 0)
{
return OpenPBPImage(filename, error);
}
else if (CASE_COMPARE(extension, ".m3u") == 0)
else if (StringUtil::Strcasecmp(extension, ".m3u") == 0)
{
return OpenM3uImage(filename, error);
}

View File

@ -94,7 +94,7 @@ bool CDImageBin::Open(const char* filename, Common::Error* error)
AddLeadOutIndex();
m_sbi.LoadSBI(FileSystem::ReplaceExtension(filename, "sbi").c_str());
m_sbi.LoadSBIFromImagePath(filename);
return Seek(1, Position{0, 0, 0});
}

View File

@ -279,7 +279,7 @@ bool CDImageCHD::Open(const char* filename, Common::Error* error)
m_lba_count = disc_lba;
AddLeadOutIndex();
m_sbi.LoadSBI(FileSystem::ReplaceExtension(filename, "sbi").c_str());
m_sbi.LoadSBIFromImagePath(filename);
return Seek(1, Position{0, 0, 0});
}

View File

@ -269,7 +269,7 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Common::Error* error)
m_lba_count = disc_lba;
AddLeadOutIndex();
m_sbi.LoadSBI(FileSystem::ReplaceExtension(filename, "sbi").c_str());
m_sbi.LoadSBIFromImagePath(filename);
return Seek(1, Position{0, 0, 0});
}

View File

@ -385,7 +385,7 @@ bool CDImageEcm::Open(const char* filename, Common::Error* error)
AddLeadOutIndex();
m_sbi.LoadSBI(FileSystem::ReplaceExtension(filename, "sbi").c_str());
m_sbi.LoadSBIFromImagePath(filename);
m_chunk_buffer.reserve(RAW_SECTOR_SIZE * 2);
return Seek(1, Position{0, 0, 0});

View File

@ -250,7 +250,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Common::Error* error)
m_lba_count = m_tracks.back().start_lba + m_tracks.back().length;
AddLeadOutIndex();
m_sbi.LoadSBI(FileSystem::ReplaceExtension(filename, "sbi").c_str());
m_sbi.LoadSBIFromImagePath(filename);
return Seek(1, Position{0, 0, 0});
}

View File

@ -673,8 +673,8 @@ bool CDImagePBP::OpenDisc(u32 index, Common::Error* error)
if (m_disc_offsets.size() > 1)
{
std::string sbi_path =
FileSystem::StripExtension(m_filename) + StringUtil::StdStringFromFormat("_%u.sbi", index + 1);
std::string sbi_path(FileSystem::StripExtension(m_filename));
sbi_path += TinyString::FromFormat("_%u.sbi", index + 1);
m_sbi.LoadSBI(sbi_path.c_str());
}
else

View File

@ -83,6 +83,11 @@ bool CDSubChannelReplacement::LoadSBI(const char* path)
return true;
}
bool CDSubChannelReplacement::LoadSBIFromImagePath(const char* image_path)
{
return LoadSBI(FileSystem::ReplaceExtension(image_path, "sbi").c_str());
}
void CDSubChannelReplacement::AddReplacementSubChannelQ(u32 lba, const CDImage::SubChannelQ& subq)
{
auto iter = m_replacement_subq.find(lba);

View File

@ -14,6 +14,7 @@ public:
u32 GetReplacementSectorCount() const { return static_cast<u32>(m_replacement_subq.size()); }
bool LoadSBI(const char* path);
bool LoadSBIFromImagePath(const char* image_path);
/// Adds a sector to the replacement map.
void AddReplacementSubChannelQ(u32 lba, const CDImage::SubChannelQ& subq);

View File

@ -51,6 +51,8 @@ static jfieldID s_android_FileHelper_FindResult_relativeName;
static jfieldID s_android_FileHelper_FindResult_size;
static jfieldID s_android_FileHelper_FindResult_modifiedTime;
static jfieldID s_android_FileHelper_FindResult_flags;
static jmethodID s_android_FileHelper_getDisplayName;
static jmethodID s_android_FileHelper_getRelativePathForURIPath;
// helper for retrieving the current per-thread jni environment
static JNIEnv* GetJNIEnv()
@ -89,6 +91,8 @@ void SetAndroidFileHelper(void* jvm, void* env, void* object)
jenv->DeleteGlobalRef(s_android_FileHelper_object);
jenv->DeleteGlobalRef(s_android_FileHelper_class);
s_android_FileHelper_getRelativePathForURIPath = {};
s_android_FileHelper_getDisplayName = {};
s_android_FileHelper_openURIAsFileDescriptor = {};
s_android_FileHelper_FindFiles = {};
s_android_FileHelper_object = {};
@ -114,7 +118,13 @@ void SetAndroidFileHelper(void* jvm, void* env, void* object)
s_android_FileHelper_FindFiles =
jenv->GetMethodID(s_android_FileHelper_class, "findFiles",
"(Ljava/lang/String;I)[Lcom/github/stenzek/duckstation/FileHelper$FindResult;");
Assert(s_android_FileHelper_openURIAsFileDescriptor && s_android_FileHelper_FindFiles);
s_android_FileHelper_getDisplayName =
jenv->GetMethodID(s_android_FileHelper_class, "getDisplayNameForURIPath", "(Ljava/lang/String;)Ljava/lang/String;");
s_android_FileHelper_getRelativePathForURIPath =
jenv->GetMethodID(s_android_FileHelper_class, "getRelativePathForURIPath",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
Assert(s_android_FileHelper_openURIAsFileDescriptor && s_android_FileHelper_FindFiles &&
s_android_FileHelper_getDisplayName && s_android_FileHelper_getRelativePathForURIPath);
jclass fr_class = jenv->FindClass("com/github/stenzek/duckstation/FileHelper$FindResult");
Assert(fr_class);
@ -279,6 +289,68 @@ static bool FindUriFiles(const char* path, const char* pattern, u32 flags, FindR
return true;
}
static bool GetDisplayNameForUriPath(const char* path, std::string* result)
{
if (!s_android_FileHelper_object)
return false;
JNIEnv* env = GetJNIEnv();
jstring path_jstr = env->NewStringUTF(path);
jstring result_jstr = static_cast<jstring>(
env->CallObjectMethod(s_android_FileHelper_object, s_android_FileHelper_getDisplayName, path_jstr));
env->DeleteLocalRef(path_jstr);
if (!result_jstr)
return false;
const char* result_name = env->GetStringUTFChars(result_jstr, nullptr);
if (result_name)
{
Log_DevPrintf("GetDisplayNameForUriPath(\"%s\") -> \"%s\"", path, result_name);
result->assign(result_name);
}
else
{
result->clear();
}
env->ReleaseStringUTFChars(result_jstr, result_name);
env->DeleteLocalRef(result_jstr);
return true;
}
static bool GetRelativePathForUriPath(const char* path, const char* filename, std::string* result)
{
if (!s_android_FileHelper_object)
return false;
JNIEnv* env = GetJNIEnv();
jstring path_jstr = env->NewStringUTF(path);
jstring filename_jstr = env->NewStringUTF(filename);
jstring result_jstr = static_cast<jstring>(env->CallObjectMethod(
s_android_FileHelper_object, s_android_FileHelper_getRelativePathForURIPath, path_jstr, filename_jstr));
env->DeleteLocalRef(filename_jstr);
env->DeleteLocalRef(path_jstr);
if (!result_jstr)
return false;
const char* result_name = env->GetStringUTFChars(result_jstr, nullptr);
if (result_name)
{
Log_DevPrintf("GetRelativePathForUriPath(\"%s\", \"%s\") -> \"%s\"", path, filename, result_name);
result->assign(result_name);
}
else
{
result->clear();
}
env->ReleaseStringUTFChars(result_jstr, result_name);
env->DeleteLocalRef(result_jstr);
return true;
}
#endif // __ANDROID__
ChangeNotifier::ChangeNotifier(const String& directoryPath, bool recursiveWatch)
@ -510,17 +582,33 @@ bool IsAbsolutePath(const std::string_view& path)
#endif
}
std::string StripExtension(const std::string_view& path)
std::string_view StripExtension(const std::string_view& path)
{
std::string_view::size_type pos = path.rfind('.');
if (pos == std::string::npos)
return std::string(path);
return path;
return std::string(path, 0, pos);
return path.substr(0, pos);
}
std::string ReplaceExtension(const std::string_view& path, const std::string_view& new_extension)
{
#ifdef __ANDROID__
// This is more complex on android because the path may not contain the actual filename.
if (IsUriPath(path))
{
std::string display_name(GetDisplayNameFromPath(path));
std::string_view::size_type pos = display_name.rfind('.');
if (pos == std::string::npos)
return std::string(path);
display_name.erase(pos + 1);
display_name.append(new_extension);
return BuildRelativePath(path, display_name);
}
#endif
std::string_view::size_type pos = path.rfind('.');
if (pos == std::string::npos)
return std::string(path);
@ -572,6 +660,28 @@ static std::string_view::size_type GetLastSeperatorPosition(const std::string_vi
return last_separator;
}
std::string GetDisplayNameFromPath(const std::string_view& path)
{
#if defined(__ANDROID__)
std::string result;
if (IsUriPath(path))
{
std::string temp(path);
if (!GetDisplayNameForUriPath(temp.c_str(), &result))
result = std::move(temp);
}
else
{
result = path;
}
return result;
#else
return std::string(GetFileNameFromPath(path));
#endif
}
std::string_view GetPathDirectory(const std::string_view& path)
{
std::string::size_type pos = GetLastSeperatorPosition(path, false);
@ -583,7 +693,7 @@ std::string_view GetPathDirectory(const std::string_view& path)
std::string_view GetFileNameFromPath(const std::string_view& path)
{
std::string::size_type pos = GetLastSeperatorPosition(path, true);
std::string_view::size_type pos = GetLastSeperatorPosition(path, true);
if (pos == std::string_view::npos)
return path;
@ -630,6 +740,15 @@ std::vector<std::string> GetRootDirectoryList()
std::string BuildRelativePath(const std::string_view& filename, const std::string_view& new_filename)
{
std::string new_string;
#ifdef __ANDROID__
if (IsUriPath(filename) &&
GetRelativePathForUriPath(std::string(filename).c_str(), std::string(new_filename).c_str(), &new_string))
{
return new_string;
}
#endif
std::string_view::size_type pos = GetLastSeperatorPosition(filename, true);
if (pos != std::string_view::npos)
new_string.assign(filename, 0, pos);

View File

@ -145,11 +145,15 @@ void SanitizeFileName(std::string& Destination, bool StripSlashes = true);
bool IsAbsolutePath(const std::string_view& path);
/// Removes the extension of a filename.
std::string StripExtension(const std::string_view& path);
std::string_view StripExtension(const std::string_view& path);
/// Replaces the extension of a filename with another.
std::string ReplaceExtension(const std::string_view& path, const std::string_view& new_extension);
/// Returns the display name of a filename. Usually this is the same as the path, except on Android
/// where it resolves a content URI to its name.
std::string GetDisplayNameFromPath(const std::string_view& path);
/// Returns the directory component of a filename.
std::string_view GetPathDirectory(const std::string_view& path);

View File

@ -33,8 +33,7 @@ HostInterface::HostInterface()
g_host_interface = this;
// we can get the program directory at construction time
const std::string program_path = FileSystem::GetProgramPath();
m_program_directory = FileSystem::GetPathDirectory(program_path.c_str());
m_program_directory = FileSystem::GetPathDirectory(FileSystem::GetProgramPath());
}
HostInterface::~HostInterface()
@ -896,8 +895,7 @@ void HostInterface::CheckForSettingsChanges(const Settings& old_settings)
void HostInterface::SetUserDirectoryToProgramDirectory()
{
const std::string program_path(FileSystem::GetProgramPath());
const std::string program_directory(FileSystem::GetPathDirectory(program_path.c_str()));
const std::string program_directory(FileSystem::GetProgramPath());
m_user_directory = program_directory;
}

View File

@ -2914,7 +2914,7 @@ void CommonHostInterface::GetGameInfo(const char* path, CDImage* image, std::str
}
else
{
*title = FileSystem::GetFileTitleFromPath(path);
*title = FileSystem::GetFileTitleFromPath(std::string(path));
if (image)
*code = System::GetGameCodeForImage(image, true);
}

View File

@ -662,7 +662,7 @@ static void DoChangeDiscFromFile()
};
OpenFileSelector(ICON_FA_COMPACT_DISC " Select Disc Image", false, std::move(callback), GetDiscImageFilters(),
std::string(FileSystem::GetPathDirectory(System::GetMediaFileName().c_str())));
std::string(FileSystem::GetPathDirectory(System::GetMediaFileName())));
}
static void DoChangeDisc()
@ -1076,7 +1076,7 @@ static bool SettingInfoButton(const SettingInfo& si, const char* section)
CloseFileSelector();
};
OpenFileSelector(si.visible_name, false, std::move(callback), ImGuiFullscreen::FileSelectorFilters(),
std::string(FileSystem::GetPathDirectory(value.c_str())));
std::string(FileSystem::GetPathDirectory(std::move(value))));
}
return false;
@ -2372,7 +2372,7 @@ void DrawQuickMenu(MainWindowType type)
SmallString subtitle;
if (!code.empty())
subtitle.Format("%s - ", code.c_str());
subtitle.AppendString(FileSystem::GetFileNameFromPath(System::GetRunningPath().c_str()));
subtitle.AppendString(FileSystem::GetFileNameFromPath(System::GetRunningPath()));
const ImVec2 title_size(
g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits<float>::max(), -1.0f, title.c_str()));
@ -2805,7 +2805,7 @@ void DrawGameListWindow()
else
summary.Format("%s - %s - ", entry->code.c_str(), Settings::GetDiscRegionName(entry->region));
summary.AppendString(FileSystem::GetFileNameFromPath(entry->path.c_str()));
summary.AppendString(FileSystem::GetFileNameFromPath(entry->path));
ImGui::GetWindowDrawList()->AddImage(cover_texture->GetHandle(), bb.Min, bb.Min + image_size, ImVec2(0.0f, 0.0f),
ImVec2(1.0f, 1.0f), IM_COL32(255, 255, 255, 255));

View File

@ -487,11 +487,8 @@ void GameList::ScanDirectory(const char* path, bool recursive, ProgressCallback*
if (AddFileFromCache(ffd.FileName, modified_time))
continue;
const std::string_view file_part(FileSystem::GetFileNameFromPath(ffd.FileName));
if (!file_part.empty())
progress->SetFormattedStatusText("Scanning '%*s'...", static_cast<int>(file_part.size()), file_part.data());
// ownership of fp is transferred
progress->SetFormattedStatusText("Scanning '%s'...", FileSystem::GetDisplayNameFromPath(ffd.FileName).c_str());
ScanFile(std::move(ffd.FileName), modified_time);
}