diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index f86ba17de..7129244b0 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -145,6 +145,23 @@ static void StripTailDirSlashes(std::string& fname) { fname.resize(i); } +#if defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) +namespace { +std::string AndroidTranslateFilename(const std::string& file) { + std::optional userDirLocation = AndroidStorage::GetUserDirectory(); + if (userDirLocation) { + return *userDirLocation + file; + } + + return ""; +} + +bool AndroidCanUseRawFS() { + return AndroidStorage::GetBuildFlavor() != AndroidStorage::AndroidBuildFlavors::GOOGLEPLAY; +} +} // anonymous namespace +#endif + bool Exists(const std::string& filename) { std::string copy(filename); StripTailDirSlashes(copy); @@ -157,7 +174,13 @@ bool Exists(const std::string& filename) { int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) - int result = AndroidStorage::FileExists(filename) ? 0 : -1; + int result; + if (AndroidCanUseRawFS()) { + struct stat file_info; + result = stat(AndroidTranslateFilename(copy).c_str(), &file_info); + } else { + result = AndroidStorage::FileExists(filename) ? 0 : -1; + } #else struct stat file_info; int result = stat(copy.c_str(), &file_info); @@ -167,9 +190,6 @@ bool Exists(const std::string& filename) { } bool IsDirectory(const std::string& filename) { -#if defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) - return AndroidStorage::IsDirectory(filename); -#endif std::string copy(filename); StripTailDirSlashes(copy); @@ -181,6 +201,14 @@ bool IsDirectory(const std::string& filename) { copy += DIR_SEP_CHR; int result = _wstat64(Common::UTF8ToUTF16W(copy).c_str(), &file_info); +#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) + struct stat file_info; + int result; + if (AndroidCanUseRawFS()) { + result = stat(AndroidTranslateFilename(copy).c_str(), &file_info); + } else { + return AndroidStorage::IsDirectory(filename); + } #else struct stat file_info; int result = stat(copy.c_str(), &file_info); @@ -239,9 +267,16 @@ bool Delete(const std::string& filename) { return false; #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) - if (!AndroidStorage::DeleteDocument(filename)) { - LOG_ERROR(Common_Filesystem, "unlink failed on {}", filename); - return false; + if (AndroidCanUseRawFS()) { + if (unlink(AndroidTranslateFilename(filename).c_str()) == -1) { + LOG_ERROR(Common_Filesystem, "unlink failed on {}: {}", filename, GetLastErrorMsg()); + return false; + } + } else { + if (!AndroidStorage::DeleteDocument(filename)) { + LOG_ERROR(Common_Filesystem, "unlink failed on {}", filename); + return false; + } } #else if (unlink(filename.c_str()) == -1) { @@ -266,23 +301,39 @@ bool CreateDir(const std::string& path) { LOG_ERROR(Common_Filesystem, "CreateDirectory failed on {}: {}", path, error); return false; #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) - std::string directory = path; - std::string filename = path; - if (Common::EndsWith(path, "/")) { - directory = GetParentPath(path); - filename = GetParentPath(path); - } - directory = GetParentPath(directory); - filename = GetFilename(filename); - // If directory path is empty, set it to root. - if (directory.empty()) { - directory = "/"; - } - if (!AndroidStorage::CreateDir(directory, filename)) { - LOG_ERROR(Common_Filesystem, "mkdir failed on {}", path); + if (AndroidCanUseRawFS()) { + if (mkdir(AndroidTranslateFilename(path).c_str(), 0755) == 0) + return true; + + int err = errno; + + if (err == EEXIST) { + LOG_DEBUG(Common_Filesystem, "mkdir failed on {}: already exists", path); + return true; + } + + LOG_ERROR(Common_Filesystem, "mkdir failed on {}: {}", path, strerror(err)); return false; - }; - return true; + } else { + std::string directory = path; + std::string filename = path; + if (Common::EndsWith(path, "/")) { + directory = GetParentPath(path); + filename = GetParentPath(path); + } + directory = GetParentPath(directory); + filename = GetFilename(filename); + // If directory path is empty, set it to root. + if (directory.empty()) { + directory = "/"; + } + if (!AndroidStorage::CreateDir(directory, filename)) { + LOG_ERROR(Common_Filesystem, "mkdir failed on {}", path); + return false; + }; + return true; + } + #else if (mkdir(path.c_str(), 0755) == 0) return true; @@ -353,8 +404,13 @@ bool DeleteDir(const std::string& filename) { if (::RemoveDirectoryW(Common::UTF8ToUTF16W(filename).c_str())) return true; #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) - if (AndroidStorage::DeleteDocument(filename)) - return true; + if (AndroidCanUseRawFS()) { + if (rmdir(AndroidTranslateFilename(filename).c_str()) == 0) + return true; + } else { + if (AndroidStorage::DeleteDocument(filename)) + return true; + } #else if (rmdir(filename.c_str()) == 0) return true; @@ -372,16 +428,14 @@ bool Rename(const std::string& srcFullPath, const std::string& destFullPath) { return true; } #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) - // srcFullPath and destFullPath are relative to the user directory - if (AndroidStorage::GetBuildFlavor() == AndroidStorage::AndroidBuildFlavors::GOOGLEPLAY) { - if (AndroidStorage::MoveAndRenameFile(srcFullPath, destFullPath)) + if (AndroidCanUseRawFS()) { + if (rename(AndroidTranslateFilename(srcFullPath).c_str(), + AndroidTranslateFilename(destFullPath).c_str()) == 0) { + return true; + } } else { - std::optional userDirLocation = AndroidStorage::GetUserDirectory(); - if (userDirLocation && rename((*userDirLocation + srcFullPath).c_str(), - (*userDirLocation + destFullPath).c_str()) == 0) { - AndroidStorage::UpdateDocumentLocation(srcFullPath, destFullPath); - // ^ TODO: This shouldn't fail, but what should we do if it somehow does? + if (AndroidStorage::MoveAndRenameFile(srcFullPath, destFullPath)) { return true; } } @@ -404,52 +458,62 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) { LOG_ERROR(Common_Filesystem, "failed {} --> {}: {}", srcFilename, destFilename, GetLastErrorMsg()); return false; -#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) - return AndroidStorage::CopyFile(srcFilename, std::string(GetParentPath(destFilename)), - std::string(GetFilename(destFilename))); #else + auto copy_files = [](const std::string& src, const std::string& dst) -> bool { + // Open input file + FILE* input = FOPEN(src.c_str(), "rb"); + if (!input) { + LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", src, dst, + GetLastErrorMsg()); + return false; + } + SCOPE_EXIT({ FCLOSE(input); }); - // Open input file - FILE* input = FOPEN(srcFilename.c_str(), "rb"); - if (!input) { - LOG_ERROR(Common_Filesystem, "opening input failed {} --> {}: {}", srcFilename, - destFilename, GetLastErrorMsg()); - return false; - } - SCOPE_EXIT({ FCLOSE(input); }); + // open output file + FILE* output = FOPEN(dst.c_str(), "wb"); + if (!output) { + LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", src, dst, + GetLastErrorMsg()); + return false; + } + SCOPE_EXIT({ FCLOSE(output); }); - // open output file - FILE* output = FOPEN(destFilename.c_str(), "wb"); - if (!output) { - LOG_ERROR(Common_Filesystem, "opening output failed {} --> {}: {}", srcFilename, - destFilename, GetLastErrorMsg()); - return false; - } - SCOPE_EXIT({ FCLOSE(output); }); + // copy loop + std::array buffer; + while (!FEOF(input)) { + // read input + std::size_t rnum = FREAD(buffer.data(), sizeof(char), buffer.size(), input); + if (rnum != buffer.size()) { + if (FERROR(input) != 0) { + LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}", src, + dst, GetLastErrorMsg()); + return false; + } + } - // copy loop - std::array buffer; - while (!FEOF(input)) { - // read input - std::size_t rnum = FREAD(buffer.data(), sizeof(char), buffer.size(), input); - if (rnum != buffer.size()) { - if (FERROR(input) != 0) { - LOG_ERROR(Common_Filesystem, "failed reading from source, {} --> {}: {}", - srcFilename, destFilename, GetLastErrorMsg()); + // write output + std::size_t wnum = FWRITE(buffer.data(), sizeof(char), rnum, output); + if (wnum != rnum) { + LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", src, dst, + GetLastErrorMsg()); return false; } } - // write output - std::size_t wnum = FWRITE(buffer.data(), sizeof(char), rnum, output); - if (wnum != rnum) { - LOG_ERROR(Common_Filesystem, "failed writing to output, {} --> {}: {}", srcFilename, - destFilename, GetLastErrorMsg()); - return false; - } - } + return true; + }; - return true; +#if defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) + if (AndroidCanUseRawFS()) { + return copy_files(AndroidTranslateFilename(srcFilename), + AndroidTranslateFilename(destFilename)); + } else { + return AndroidStorage::CopyFile(srcFilename, std::string(GetParentPath(destFilename)), + std::string(GetFilename(destFilename))); + } +#else + return copy_files(srcFilename, destFilename); +#endif #endif } @@ -470,9 +534,15 @@ u64 GetSize(const std::string& filename) { struct _stat64 buf; if (_wstat64(Common::UTF8ToUTF16W(filename).c_str(), &buf) == 0) #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) - u64 result = AndroidStorage::GetSize(filename); - LOG_TRACE(Common_Filesystem, "{}: {}", filename, result); - return result; + if (AndroidCanUseRawFS()) { + if (stat(AndroidTranslateFilename(filename).c_str(), &buf) == 0) { + return buf.st_size; + } + } else { + u64 result = AndroidStorage::GetSize(filename); + LOG_TRACE(Common_Filesystem, "{}: {}", filename, result); + return result; + } #else if (stat(filename.c_str(), &buf) == 0) #endif @@ -520,68 +590,97 @@ bool CreateEmptyFile(const std::string& filename) { return true; } +namespace { + +#ifdef _WIN32 + +std::optional> ListDirectoryEntries(const std::string& directory) { + std::vector entries; + + WIN32_FIND_DATAW ffd; + const std::wstring search_path = Common::UTF8ToUTF16W(directory + "\\*"); + + HANDLE handle = FindFirstFileW(search_path.c_str(), &ffd); + if (handle == INVALID_HANDLE_VALUE) { + return std::nullopt; + } + + do { + entries.emplace_back(Common::UTF16ToUTF8(ffd.cFileName)); + } while (FindNextFileW(handle, &ffd) != 0); + + FindClose(handle); + return entries; +} + +#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) + +std::optional> ListDirectoryEntries(const std::string& directory) { + if (AndroidCanUseRawFS()) { + std::vector entries; + + DIR* dirp = opendir(AndroidTranslateFilename(directory).c_str()); + if (!dirp) + return std::nullopt; + + while (dirent* entry = readdir(dirp)) { + entries.emplace_back(entry->d_name); + } + + closedir(dirp); + return entries; + } else { + return AndroidStorage::GetFilesName(directory); + } +} + +#else + +std::optional> ListDirectoryEntries(const std::string& directory) { + std::vector entries; + + DIR* dirp = opendir(directory.c_str()); + if (!dirp) + return std::nullopt; + + while (dirent* entry = readdir(dirp)) { + entries.emplace_back(entry->d_name); + } + + closedir(dirp); + return entries; +} + +#endif + +} // anonymous namespace + bool ForeachDirectoryEntry(u64* num_entries_out, const std::string& directory, DirectoryEntryCallable callback) { LOG_TRACE(Common_Filesystem, "directory {}", directory); - // How many files + directories we found - u64 found_entries = 0; - - // Save the status of callback function - bool callback_error = false; - -#ifdef _WIN32 - // Find the first file in the directory. - WIN32_FIND_DATAW ffd; - - HANDLE handle_find = FindFirstFileW(Common::UTF8ToUTF16W(directory + "\\*").c_str(), &ffd); - if (handle_find == INVALID_HANDLE_VALUE) { - FindClose(handle_find); + const auto entries = ListDirectoryEntries(directory); + if (!entries.has_value()) { return false; } - // windows loop - do { - const std::string virtual_name(Common::UTF16ToUTF8(ffd.cFileName)); -#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) - // android loop - auto result = AndroidStorage::GetFilesName(directory); - for (auto virtual_name : result) { -#else - DIR* dirp = opendir(directory.c_str()); - if (!dirp) - return false; - // non windows loop - while (struct dirent* result = readdir(dirp)) { - const std::string virtual_name(result->d_name); -#endif + u64 found_entries = 0; + for (const std::string& virtual_name : *entries) { if (virtual_name == "." || virtual_name == "..") continue; u64 ret_entries = 0; - if (!callback(&ret_entries, directory, virtual_name)) { - callback_error = true; - break; - } + if (!callback(&ret_entries, directory, virtual_name)) + return false; + found_entries += ret_entries; - -#ifdef _WIN32 - } while (FindNextFileW(handle_find, &ffd) != 0); - FindClose(handle_find); -#elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) } -#else - } - closedir(dirp); -#endif - - if (callback_error) - return false; // num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it - if (num_entries_out != nullptr) + if (num_entries_out) *num_entries_out = found_entries; + return true; } @@ -658,48 +757,39 @@ bool DeleteDirRecursively(const std::string& directory, unsigned int recursion) void CopyDir([[maybe_unused]] const std::string& source_path, [[maybe_unused]] const std::string& dest_path) { -#ifndef _WIN32 if (source_path == dest_path) return; + if (!FileUtil::Exists(source_path)) return; + if (!FileUtil::Exists(dest_path)) FileUtil::CreateFullPath(dest_path); -#if defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) - auto result = AndroidStorage::GetFilesName(source_path); - for (auto virtualName : result) { -#else - DIR* dirp = opendir(source_path.c_str()); - if (!dirp) + const auto entries = ListDirectoryEntries(source_path); + if (!entries.has_value() || (*entries).empty()) return; - while (struct dirent* result = readdir(dirp)) { - const std::string virtualName(result->d_name); -#endif // ANDROID - - // check for "." and ".." - if (((virtualName[0] == '.') && (virtualName[1] == '\0')) || - ((virtualName[0] == '.') && (virtualName[1] == '.') && (virtualName[2] == '\0'))) + for (const std::string& virtual_name : *entries) { + if (virtual_name == "." || virtual_name == "..") continue; - std::string source, dest; - source = source_path + virtualName; - dest = dest_path + virtualName; + std::string source = source_path + virtual_name; + std::string dest = dest_path + virtual_name; + if (IsDirectory(source)) { source += '/'; dest += '/'; + if (!FileUtil::Exists(dest)) FileUtil::CreateFullPath(dest); - CopyDir(source, dest); - } else if (!FileUtil::Exists(dest)) - FileUtil::Copy(source, dest); - } -#if !(defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS)) - closedir(dirp); -#endif // ANDROID -#endif // _WIN32 + CopyDir(source, dest); + } else { + if (!FileUtil::Exists(dest)) + FileUtil::Copy(source, dest); + } + } } std::optional GetCurrentDir() { @@ -1234,34 +1324,37 @@ bool IOFile::Open() { m_good = m_file != nullptr; #elif defined(ANDROID) && !defined(HAVE_LIBRETRO_VFS) - // Check whether filepath is startsWith content - AndroidStorage::AndroidOpenMode android_open_mode = AndroidStorage::ParseOpenmode(openmode); - if (android_open_mode == AndroidStorage::AndroidOpenMode::WRITE || - android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE || - android_open_mode == AndroidStorage::AndroidOpenMode::WRITE_APPEND || - android_open_mode == AndroidStorage::AndroidOpenMode::WRITE_TRUNCATE || - android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE_TRUNCATE || - android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE_APPEND) { - if (!FileUtil::Exists(filename)) { - std::string directory(GetParentPath(filename)); - std::string display_name(GetFilename(filename)); - if (!AndroidStorage::CreateFile(directory, display_name)) { - m_good = m_file != nullptr; - return m_good; + if (AndroidCanUseRawFS()) { + m_file = FOPEN(AndroidTranslateFilename(filename).c_str(), openmode.c_str()); + } else { + // Check whether filepath is startsWith content + AndroidStorage::AndroidOpenMode android_open_mode = AndroidStorage::ParseOpenmode(openmode); + if (android_open_mode == AndroidStorage::AndroidOpenMode::WRITE || + android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE || + android_open_mode == AndroidStorage::AndroidOpenMode::WRITE_APPEND || + android_open_mode == AndroidStorage::AndroidOpenMode::WRITE_TRUNCATE || + android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE_TRUNCATE || + android_open_mode == AndroidStorage::AndroidOpenMode::READ_WRITE_APPEND) { + if (!FileUtil::Exists(filename)) { + std::string directory(GetParentPath(filename)); + std::string display_name(GetFilename(filename)); + if (!AndroidStorage::CreateFile(directory, display_name)) { + m_good = m_file != nullptr; + return m_good; + } + } + } + m_fd = AndroidStorage::OpenContentUri(filename, android_open_mode); + if (m_fd != -1) { + int error_num = 0; + m_file = fdopen(m_fd, openmode.c_str()); + error_num = errno; + if (error_num != 0 && m_file == nullptr) { + LOG_ERROR(Common_Filesystem, "Error on file: {}, error: {}", filename, + strerror(error_num)); } } } - m_fd = AndroidStorage::OpenContentUri(filename, android_open_mode); - if (m_fd != -1) { - int error_num = 0; - m_file = fdopen(m_fd, openmode.c_str()); - error_num = errno; - if (error_num != 0 && m_file == nullptr) { - LOG_ERROR(Common_Filesystem, "Error on file: {}, error: {}", filename, - strerror(error_num)); - } - } - m_good = m_file != nullptr; #else m_file = FOPEN(filename.c_str(), openmode.c_str());