diff --git a/src/common-tests/CMakeLists.txt b/src/common-tests/CMakeLists.txt index 6502fe81d..4809e4eba 100644 --- a/src/common-tests/CMakeLists.txt +++ b/src/common-tests/CMakeLists.txt @@ -2,6 +2,7 @@ add_executable(common-tests bitutils_tests.cpp event_tests.cpp file_system_tests.cpp + path_tests.cpp rectangle_tests.cpp ) diff --git a/src/common-tests/common-tests.vcxproj b/src/common-tests/common-tests.vcxproj index 6f9860091..de6b79b22 100644 --- a/src/common-tests/common-tests.vcxproj +++ b/src/common-tests/common-tests.vcxproj @@ -6,6 +6,7 @@ + @@ -19,9 +20,7 @@ {EA2B9C7A-B8CC-42F9-879B-191A98680C10} - - diff --git a/src/common-tests/common-tests.vcxproj.filters b/src/common-tests/common-tests.vcxproj.filters index 225e130ff..a7bbe5e6a 100644 --- a/src/common-tests/common-tests.vcxproj.filters +++ b/src/common-tests/common-tests.vcxproj.filters @@ -6,5 +6,6 @@ + \ No newline at end of file diff --git a/src/common-tests/file_system_tests.cpp b/src/common-tests/file_system_tests.cpp index bfa8176eb..ce997418a 100644 --- a/src/common-tests/file_system_tests.cpp +++ b/src/common-tests/file_system_tests.cpp @@ -1,25 +1,3 @@ #include "common/file_system.h" #include -TEST(FileSystem, IsAbsolutePath) -{ -#ifdef _WIN32 - ASSERT_TRUE(FileSystem::IsAbsolutePath("C:\\")); - ASSERT_TRUE(FileSystem::IsAbsolutePath("C:\\Path")); - ASSERT_TRUE(FileSystem::IsAbsolutePath("C:\\Path\\Subdirectory")); - ASSERT_TRUE(FileSystem::IsAbsolutePath("C:/")); - ASSERT_TRUE(FileSystem::IsAbsolutePath("C:/Path")); - ASSERT_TRUE(FileSystem::IsAbsolutePath("C:/Path/Subdirectory")); - ASSERT_FALSE(FileSystem::IsAbsolutePath("")); - ASSERT_FALSE(FileSystem::IsAbsolutePath("C:")); - ASSERT_FALSE(FileSystem::IsAbsolutePath("Path")); - ASSERT_FALSE(FileSystem::IsAbsolutePath("Path/Subdirectory")); -#else - ASSERT_TRUE(FileSystem::IsAbsolutePath("/")); - ASSERT_TRUE(FileSystem::IsAbsolutePath("/path")); - ASSERT_TRUE(FileSystem::IsAbsolutePath("/path/subdirectory")); - ASSERT_FALSE(FileSystem::IsAbsolutePath("")); - ASSERT_FALSE(FileSystem::IsAbsolutePath("path")); - ASSERT_FALSE(FileSystem::IsAbsolutePath("path/subdirectory")); -#endif -} diff --git a/src/common-tests/path_tests.cpp b/src/common-tests/path_tests.cpp new file mode 100644 index 000000000..0f68bb4f6 --- /dev/null +++ b/src/common-tests/path_tests.cpp @@ -0,0 +1,225 @@ +#include "common/path.h" +#include "common/types.h" +#include + +TEST(FileSystem, ToNativePath) +{ + ASSERT_EQ(Path::ToNativePath(""), ""); + +#ifdef _WIN32 + ASSERT_EQ(Path::ToNativePath("foo"), "foo"); + ASSERT_EQ(Path::ToNativePath("foo\\"), "foo"); + ASSERT_EQ(Path::ToNativePath("foo\\\\bar"), "foo\\bar"); + ASSERT_EQ(Path::ToNativePath("foo\\bar"), "foo\\bar"); + ASSERT_EQ(Path::ToNativePath("foo\\bar\\baz"), "foo\\bar\\baz"); + ASSERT_EQ(Path::ToNativePath("foo\\bar/baz"), "foo\\bar\\baz"); + ASSERT_EQ(Path::ToNativePath("foo/bar/baz"), "foo\\bar\\baz"); + ASSERT_EQ(Path::ToNativePath("foo/🙃bar/b🙃az"), "foo\\🙃bar\\b🙃az"); + ASSERT_EQ(Path::ToNativePath("\\\\foo\\bar\\baz"), "\\\\foo\\bar\\baz"); +#else + ASSERT_EQ(Path::ToNativePath("foo"), "foo"); + ASSERT_EQ(Path::ToNativePath("foo/"), "foo"); + ASSERT_EQ(Path::ToNativePath("foo//bar"), "foo/bar"); + ASSERT_EQ(Path::ToNativePath("foo/bar"), "foo/bar"); + ASSERT_EQ(Path::ToNativePath("foo/bar/baz"), "foo/bar/baz"); + ASSERT_EQ(Path::ToNativePath("/foo/bar/baz"), "/foo/bar/baz"); +#endif +} + +TEST(FileSystem, IsAbsolute) +{ + ASSERT_FALSE(Path::IsAbsolute("")); + ASSERT_FALSE(Path::IsAbsolute("foo")); + ASSERT_FALSE(Path::IsAbsolute("foo/bar")); + ASSERT_FALSE(Path::IsAbsolute("foo/b🙃ar")); +#ifdef _WIN32 + ASSERT_TRUE(Path::IsAbsolute("C:\\foo/bar")); + ASSERT_TRUE(Path::IsAbsolute("C://foo\\bar")); + ASSERT_FALSE(Path::IsAbsolute("\\foo/bar")); + ASSERT_TRUE(Path::IsAbsolute("\\\\foo\\bar\\baz")); + ASSERT_TRUE(Path::IsAbsolute("C:\\")); + ASSERT_TRUE(Path::IsAbsolute("C:\\Path")); + ASSERT_TRUE(Path::IsAbsolute("C:\\Path\\Subdirectory")); + ASSERT_TRUE(Path::IsAbsolute("C:/")); + ASSERT_TRUE(Path::IsAbsolute("C:/Path")); + ASSERT_TRUE(Path::IsAbsolute("C:/Path/Subdirectory")); + ASSERT_FALSE(Path::IsAbsolute("")); + ASSERT_FALSE(Path::IsAbsolute("C:")); + ASSERT_FALSE(Path::IsAbsolute("Path")); + ASSERT_FALSE(Path::IsAbsolute("Path/Subdirectory")); +#else + ASSERT_TRUE(Path::IsAbsolute("/foo/bar")); + ASSERT_TRUE(Path::IsAbsolute("/")); + ASSERT_TRUE(Path::IsAbsolute("/path")); + ASSERT_TRUE(Path::IsAbsolute("/path/subdirectory")); + ASSERT_FALSE(Path::IsAbsolute("")); + ASSERT_FALSE(Path::IsAbsolute("path")); + ASSERT_FALSE(Path::IsAbsolute("path/subdirectory")); +#endif +} + +TEST(FileSystem, Canonicalize) +{ + ASSERT_EQ(Path::Canonicalize(""), Path::ToNativePath("")); + ASSERT_EQ(Path::Canonicalize("foo/bar/../baz"), Path::ToNativePath("foo/baz")); + ASSERT_EQ(Path::Canonicalize("foo/bar/./baz"), Path::ToNativePath("foo/bar/baz")); + ASSERT_EQ(Path::Canonicalize("foo/./bar/./baz"), Path::ToNativePath("foo/bar/baz")); + ASSERT_EQ(Path::Canonicalize("foo/bar/../baz/../foo"), Path::ToNativePath("foo/foo")); + ASSERT_EQ(Path::Canonicalize("foo/bar/../baz/./foo"), Path::ToNativePath("foo/baz/foo")); + ASSERT_EQ(Path::Canonicalize("./foo"), Path::ToNativePath("foo")); + ASSERT_EQ(Path::Canonicalize("../foo"), Path::ToNativePath("../foo")); + ASSERT_EQ(Path::Canonicalize("foo/b🙃ar/../b🙃az/./foo"), Path::ToNativePath("foo/b🙃az/foo")); + ASSERT_EQ( + Path::Canonicalize( + "ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱/b🙃az/../foℹ︎o"), + Path::ToNativePath("ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱/foℹ︎o")); +#ifdef _WIN32 + ASSERT_EQ(Path::Canonicalize("C:\\foo\\bar\\..\\baz\\.\\foo"), "C:\\foo\\baz\\foo"); + ASSERT_EQ(Path::Canonicalize("C:/foo\\bar\\..\\baz\\.\\foo"), "C:\\foo\\baz\\foo"); + ASSERT_EQ(Path::Canonicalize("foo\\bar\\..\\baz\\.\\foo"), "foo\\baz\\foo"); + ASSERT_EQ(Path::Canonicalize("foo\\bar/..\\baz/.\\foo"), "foo\\baz\\foo"); + ASSERT_EQ(Path::Canonicalize("\\\\foo\\bar\\baz/..\\foo"), "\\\\foo\\bar\\foo"); +#else + ASSERT_EQ(Path::Canonicalize("/foo/bar/../baz/./foo"), "/foo/baz/foo"); +#endif +} + +TEST(FileSystem, Combine) +{ + ASSERT_EQ(Path::Combine("", ""), Path::ToNativePath("")); + ASSERT_EQ(Path::Combine("foo", "bar"), Path::ToNativePath("foo/bar")); + ASSERT_EQ(Path::Combine("foo/bar", "baz"), Path::ToNativePath("foo/bar/baz")); + ASSERT_EQ(Path::Combine("foo/bar", "../baz"), Path::ToNativePath("foo/bar/../baz")); + ASSERT_EQ(Path::Combine("foo/bar/", "/baz/"), Path::ToNativePath("foo/bar/baz")); + ASSERT_EQ(Path::Combine("foo//bar", "baz/"), Path::ToNativePath("foo/bar/baz")); + ASSERT_EQ(Path::Combine("foo//ba🙃r", "b🙃az/"), Path::ToNativePath("foo/ba🙃r/b🙃az")); +#ifdef _WIN32 + ASSERT_EQ(Path::Combine("C:\\foo\\bar", "baz"), "C:\\foo\\bar\\baz"); + ASSERT_EQ(Path::Combine("\\\\server\\foo\\bar", "baz"), "\\\\server\\foo\\bar\\baz"); + ASSERT_EQ(Path::Combine("foo\\bar", "baz"), "foo\\bar\\baz"); + ASSERT_EQ(Path::Combine("foo\\bar\\", "baz"), "foo\\bar\\baz"); + ASSERT_EQ(Path::Combine("foo/bar\\", "\\baz"), "foo\\bar\\baz"); + ASSERT_EQ(Path::Combine("\\\\foo\\bar", "baz"), "\\\\foo\\bar\\baz"); +#else + ASSERT_EQ(Path::Combine("/foo/bar", "baz"), "/foo/bar/baz"); +#endif +} + +TEST(FileSystem, AppendDirectory) +{ + ASSERT_EQ(Path::AppendDirectory("foo/bar", "baz"), Path::ToNativePath("foo/baz/bar")); + ASSERT_EQ(Path::AppendDirectory("", "baz"), Path::ToNativePath("baz")); + ASSERT_EQ(Path::AppendDirectory("", ""), Path::ToNativePath("")); + ASSERT_EQ(Path::AppendDirectory("foo/bar", "🙃"), Path::ToNativePath("foo/🙃/bar")); +#ifdef _WIN32 + ASSERT_EQ(Path::AppendDirectory("foo\\bar", "baz"), "foo\\baz\\bar"); + ASSERT_EQ(Path::AppendDirectory("\\\\foo\\bar", "baz"), "\\\\foo\\baz\\bar"); +#else + ASSERT_EQ(Path::AppendDirectory("/foo/bar", "baz"), "/foo/baz/bar"); +#endif +} + +TEST(FileSystem, MakeRelative) +{ + ASSERT_EQ(Path::MakeRelative("", ""), Path::ToNativePath("")); + ASSERT_EQ(Path::MakeRelative("foo", ""), Path::ToNativePath("foo")); + ASSERT_EQ(Path::MakeRelative("", "foo"), Path::ToNativePath("")); + ASSERT_EQ(Path::MakeRelative("foo", "bar"), Path::ToNativePath("foo")); + +#ifdef _WIN32 +#define A "C:\\" +#else +#define A "/" +#endif + + ASSERT_EQ(Path::MakeRelative(A "foo", A "bar"), Path::ToNativePath("../foo")); + ASSERT_EQ(Path::MakeRelative(A "foo/bar", A "foo"), Path::ToNativePath("bar")); + ASSERT_EQ(Path::MakeRelative(A "foo/bar", A "foo/baz"), Path::ToNativePath("../bar")); + ASSERT_EQ(Path::MakeRelative(A "foo/b🙃ar", A "foo/b🙃az"), Path::ToNativePath("../b🙃ar")); + ASSERT_EQ(Path::MakeRelative(A "f🙃oo/b🙃ar", A "f🙃oo/b🙃az"), Path::ToNativePath("../b🙃ar")); + ASSERT_EQ( + Path::MakeRelative(A "ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱/b🙃ar", + A "ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤℹ︎∩₲ ₱⟑♰⫳🐱/b🙃az"), + Path::ToNativePath("../b🙃ar")); + +#undef A + +#ifdef _WIN32 + ASSERT_EQ(Path::MakeRelative("\\\\foo\\bar\\baz\\foo", "\\\\foo\\bar\\baz"), "foo"); + ASSERT_EQ(Path::MakeRelative("\\\\foo\\bar\\foo", "\\\\foo\\bar\\baz"), "..\\foo"); + ASSERT_EQ(Path::MakeRelative("\\\\foo\\bar\\foo", "\\\\other\\bar\\foo"), "\\\\foo\\bar\\foo"); +#endif +} + +TEST(FileSystem, GetExtension) +{ + ASSERT_EQ(Path::GetExtension("foo"), ""); + ASSERT_EQ(Path::GetExtension("foo.txt"), "txt"); + ASSERT_EQ(Path::GetExtension("foo.t🙃t"), "t🙃t"); + ASSERT_EQ(Path::GetExtension("foo."), ""); + ASSERT_EQ(Path::GetExtension("a/b/foo.txt"), "txt"); + ASSERT_EQ(Path::GetExtension("a/b/foo"), ""); +} + +TEST(FileSystem, GetFileName) +{ + ASSERT_EQ(Path::GetFileName(""), ""); + ASSERT_EQ(Path::GetFileName("foo"), "foo"); + ASSERT_EQ(Path::GetFileName("foo.txt"), "foo.txt"); + ASSERT_EQ(Path::GetFileName("foo"), "foo"); + ASSERT_EQ(Path::GetFileName("foo/bar/."), "."); + ASSERT_EQ(Path::GetFileName("foo/bar/baz"), "baz"); + ASSERT_EQ(Path::GetFileName("foo/bar/baz.txt"), "baz.txt"); +#ifdef _WIN32 + ASSERT_EQ(Path::GetFileName("foo/bar\\baz"), "baz"); + ASSERT_EQ(Path::GetFileName("foo\\bar\\baz.txt"), "baz.txt"); +#endif +} + +TEST(FileSystem, GetFileTitle) +{ + ASSERT_EQ(Path::GetFileTitle(""), ""); + ASSERT_EQ(Path::GetFileTitle("foo"), "foo"); + ASSERT_EQ(Path::GetFileTitle("foo.txt"), "foo"); + ASSERT_EQ(Path::GetFileTitle("foo/bar/."), ""); + ASSERT_EQ(Path::GetFileTitle("foo/bar/baz"), "baz"); + ASSERT_EQ(Path::GetFileTitle("foo/bar/baz.txt"), "baz"); +#ifdef _WIN32 + ASSERT_EQ(Path::GetFileTitle("foo/bar\\baz"), "baz"); + ASSERT_EQ(Path::GetFileTitle("foo\\bar\\baz.txt"), "baz"); +#endif +} + +TEST(FileSystem, GetDirectory) +{ + ASSERT_EQ(Path::GetDirectory(""), ""); + ASSERT_EQ(Path::GetDirectory("foo"), ""); + ASSERT_EQ(Path::GetDirectory("foo.txt"), ""); + ASSERT_EQ(Path::GetDirectory("foo/bar/."), "foo/bar"); + ASSERT_EQ(Path::GetDirectory("foo/bar/baz"), "foo/bar"); + ASSERT_EQ(Path::GetDirectory("foo/bar/baz.txt"), "foo/bar"); +#ifdef _WIN32 + ASSERT_EQ(Path::GetDirectory("foo\\bar\\baz"), "foo\\bar"); + ASSERT_EQ(Path::GetDirectory("foo\\bar/baz.txt"), "foo\\bar"); +#endif +} + +TEST(FileSystem, ChangeFileName) +{ + ASSERT_EQ(Path::ChangeFileName("", ""), Path::ToNativePath("")); + ASSERT_EQ(Path::ChangeFileName("", "bar"), Path::ToNativePath("bar")); + ASSERT_EQ(Path::ChangeFileName("bar", ""), Path::ToNativePath("")); + ASSERT_EQ(Path::ChangeFileName("foo/bar", ""), Path::ToNativePath("foo")); + ASSERT_EQ(Path::ChangeFileName("foo/", "bar"), Path::ToNativePath("foo/bar")); + ASSERT_EQ(Path::ChangeFileName("foo/bar", "baz"), Path::ToNativePath("foo/baz")); + ASSERT_EQ(Path::ChangeFileName("foo//bar", "baz"), Path::ToNativePath("foo/baz")); + ASSERT_EQ(Path::ChangeFileName("foo//bar.txt", "baz.txt"), Path::ToNativePath("foo/baz.txt")); + ASSERT_EQ(Path::ChangeFileName("foo//ba🙃r.txt", "ba🙃z.txt"), Path::ToNativePath("foo/ba🙃z.txt")); +#ifdef _WIN32 + ASSERT_EQ(Path::ChangeFileName("foo/bar", "baz"), "foo\\baz"); + ASSERT_EQ(Path::ChangeFileName("foo//bar\\foo", "baz"), "foo\\bar\\baz"); + ASSERT_EQ(Path::ChangeFileName("\\\\foo\\bar\\foo", "baz"), "\\\\foo\\bar\\baz"); +#else + ASSERT_EQ(Path::ChangeFileName("/foo/bar", "baz"), "/foo/baz"); +#endif +} \ No newline at end of file diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index e4a337309..e98cf1be2 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -69,6 +69,7 @@ add_library(common memory_arena.h page_fault_handler.cpp page_fault_handler.h + path.h platform.h pbp_types.h progress_callback.cpp @@ -87,8 +88,6 @@ add_library(common thirdparty/thread_pool.h timer.cpp timer.h - timestamp.cpp - timestamp.h types.h vulkan/builders.cpp vulkan/builders.h diff --git a/src/common/cd_image.cpp b/src/common/cd_image.cpp index 1efd3ce9d..e3376e8cc 100644 --- a/src/common/cd_image.cpp +++ b/src/common/cd_image.cpp @@ -2,6 +2,7 @@ #include "assert.h" #include "file_system.h" #include "log.h" +#include "path.h" #include "string_util.h" #include Log_SetChannel(CDImage); @@ -293,7 +294,7 @@ std::string CDImage::GetMetadata(const std::string_view& type) const if (type == "title") { const std::string display_name(FileSystem::GetDisplayNameFromPath(m_filename)); - result = FileSystem::StripExtension(display_name); + result = Path::StripExtension(display_name); } return result; diff --git a/src/common/cd_image_chd.cpp b/src/common/cd_image_chd.cpp index b87b5dabb..5cd39d36c 100644 --- a/src/common/cd_image_chd.cpp +++ b/src/common/cd_image_chd.cpp @@ -303,7 +303,7 @@ bool CDImageCHD::HasNonStandardSubchannel() const CDImage::PrecacheResult CDImageCHD::Precache(ProgressCallback* progress) { - const std::string_view title(FileSystem::GetFileNameFromPath(m_filename)); + const std::string_view title(FileSystem::GetDisplayNameFromPath(m_filename)); progress->SetFormattedStatusText("Precaching %.*s...", static_cast(title.size()), title.data()); progress->SetProgressRange(100); diff --git a/src/common/cd_image_cue.cpp b/src/common/cd_image_cue.cpp index 8c7865b71..f72cc2ef8 100644 --- a/src/common/cd_image_cue.cpp +++ b/src/common/cd_image_cue.cpp @@ -5,6 +5,7 @@ #include "error.h" #include "file_system.h" #include "log.h" +#include "path.h" #include #include #include @@ -88,15 +89,14 @@ bool CDImageCueSheet::OpenAndParse(const char* filename, Common::Error* error) } if (track_file_index == m_files.size()) { - const std::string track_full_filename(!FileSystem::IsAbsolutePath(track_filename) ? - FileSystem::BuildRelativePath(m_filename, track_filename) : - track_filename); + const std::string track_full_filename( + !Path::IsAbsolute(track_filename) ? Path::BuildRelativePath(m_filename, track_filename) : track_filename); std::FILE* track_fp = FileSystem::OpenCFile(track_full_filename.c_str(), "rb"); if (!track_fp && track_file_index == 0) { // many users have bad cuesheets, or they're renamed the files without updating the cuesheet. // so, try searching for a bin with the same name as the cue, but only for the first referenced file. - const std::string alternative_filename(FileSystem::ReplaceExtension(filename, "bin")); + const std::string alternative_filename(Path::ReplaceExtension(filename, "bin")); track_fp = FileSystem::OpenCFile(alternative_filename.c_str(), "rb"); if (track_fp) { diff --git a/src/common/cd_image_m3u.cpp b/src/common/cd_image_m3u.cpp index 9e0afbbb8..900f758b5 100644 --- a/src/common/cd_image_m3u.cpp +++ b/src/common/cd_image_m3u.cpp @@ -4,10 +4,11 @@ #include "error.h" #include "file_system.h" #include "log.h" +#include "path.h" #include #include -#include #include +#include Log_SetChannel(CDImageMemory); class CDImageM3u : public CDImage @@ -88,9 +89,9 @@ bool CDImageM3u::Open(const char* path, Common::Error* error) Entry entry; std::string entry_filename(line.begin() + start_offset, line.begin() + end_offset + 1); - entry.title = FileSystem::GetFileTitleFromPath(entry_filename); - if (!FileSystem::IsAbsolutePath(entry_filename)) - entry.filename = FileSystem::BuildRelativePath(path, entry_filename); + entry.title = Path::GetFileTitle(entry_filename); + if (!Path::IsAbsolute(entry_filename)) + entry.filename = Path::BuildRelativePath(path, entry_filename); else entry.filename = std::move(entry_filename); @@ -154,7 +155,7 @@ std::string CDImageM3u::GetSubImageMetadata(u32 index, const std::string_view& t if (type == "title") return m_entries[index].title; else if (type == "file_title") - return std::string(FileSystem::GetFileTitleFromPath(m_entries[index].filename)); + return std::string(Path::GetFileTitle(m_entries[index].filename)); return CDImage::GetSubImageMetadata(index, type); } diff --git a/src/common/cd_image_mds.cpp b/src/common/cd_image_mds.cpp index 21a633762..22652bf3c 100644 --- a/src/common/cd_image_mds.cpp +++ b/src/common/cd_image_mds.cpp @@ -4,6 +4,7 @@ #include "error.h" #include "file_system.h" #include "log.h" +#include "path.h" #include #include #include @@ -80,7 +81,7 @@ bool CDImageMds::OpenAndParse(const char* filename, Common::Error* error) return false; } - std::string mdf_filename(FileSystem::ReplaceExtension(filename, "mdf")); + std::string mdf_filename(Path::ReplaceExtension(filename, "mdf")); m_mdf_file = FileSystem::OpenCFile(mdf_filename.c_str(), "rb"); if (!m_mdf_file) { diff --git a/src/common/cd_image_memory.cpp b/src/common/cd_image_memory.cpp index 1ce5eba83..9bc409004 100644 --- a/src/common/cd_image_memory.cpp +++ b/src/common/cd_image_memory.cpp @@ -3,6 +3,7 @@ #include "cd_subchannel_replacement.h" #include "file_system.h" #include "log.h" +#include "path.h" #include #include Log_SetChannel(CDImageMemory); @@ -109,7 +110,7 @@ bool CDImageMemory::CopyImage(CDImage* image, ProgressCallback* progress) m_filename = image->GetFileName(); m_lba_count = image->GetLBACount(); - m_sbi.LoadSBI(FileSystem::ReplaceExtension(m_filename, "sbi").c_str()); + m_sbi.LoadSBI(Path::ReplaceExtension(m_filename, "sbi").c_str()); return Seek(1, Position{0, 0, 0}); } diff --git a/src/common/cd_image_pbp.cpp b/src/common/cd_image_pbp.cpp index c1cd3950e..9733bf285 100644 --- a/src/common/cd_image_pbp.cpp +++ b/src/common/cd_image_pbp.cpp @@ -4,6 +4,7 @@ #include "error.h" #include "file_system.h" #include "log.h" +#include "path.h" #include "pbp_types.h" #include "string.h" #include "string_util.h" @@ -674,12 +675,12 @@ bool CDImagePBP::OpenDisc(u32 index, Common::Error* error) if (m_disc_offsets.size() > 1) { - std::string sbi_path(FileSystem::StripExtension(m_filename)); + std::string sbi_path(Path::StripExtension(m_filename)); sbi_path += TinyString::FromFormat("_%u.sbi", index + 1); m_sbi.LoadSBI(sbi_path.c_str()); } else - m_sbi.LoadSBI(FileSystem::ReplaceExtension(m_filename, "sbi").c_str()); + m_sbi.LoadSBI(Path::ReplaceExtension(m_filename, "sbi").c_str()); m_current_disc = index; return Seek(1, Position{0, 0, 0}); diff --git a/src/common/cd_subchannel_replacement.cpp b/src/common/cd_subchannel_replacement.cpp index 0a7915288..da21dcb6d 100644 --- a/src/common/cd_subchannel_replacement.cpp +++ b/src/common/cd_subchannel_replacement.cpp @@ -1,6 +1,7 @@ #include "cd_subchannel_replacement.h" #include "file_system.h" #include "log.h" +#include "path.h" #include #include Log_SetChannel(CDSubChannelReplacement); @@ -85,7 +86,7 @@ bool CDSubChannelReplacement::LoadSBI(const char* path) bool CDSubChannelReplacement::LoadSBIFromImagePath(const char* image_path) { - return LoadSBI(FileSystem::ReplaceExtension(image_path, "sbi").c_str()); + return LoadSBI(Path::ReplaceExtension(image_path, "sbi").c_str()); } void CDSubChannelReplacement::AddReplacementSubChannelQ(u32 lba, const CDImage::SubChannelQ& subq) diff --git a/src/common/common.vcxproj b/src/common/common.vcxproj index 91659905a..4a0115102 100644 --- a/src/common/common.vcxproj +++ b/src/common/common.vcxproj @@ -34,6 +34,7 @@ true + @@ -55,6 +56,7 @@ + @@ -71,7 +73,6 @@ true - @@ -91,6 +92,7 @@ true + @@ -161,7 +163,6 @@ true - diff --git a/src/common/common.vcxproj.filters b/src/common/common.vcxproj.filters index 9c6f5ba10..f3f9f4099 100644 --- a/src/common/common.vcxproj.filters +++ b/src/common/common.vcxproj.filters @@ -39,7 +39,6 @@ - @@ -143,6 +142,11 @@ vulkan + + + + gl + @@ -179,7 +183,6 @@ - diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp index 80eb36791..2ed7686b1 100644 --- a/src/common/file_system.cpp +++ b/src/common/file_system.cpp @@ -1,18 +1,32 @@ +/* PCSX2 - PS2 Emulator for PCs + * Copyright (C) 2002-2021 PCSX2 Dev Team + * + * PCSX2 is free software: you can redistribute it and/or modify it under the terms + * of the GNU Lesser General Public License as published by the Free Software Found- + * ation, either version 3 of the License, or (at your option) any later version. + * + * PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; + * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with PCSX2. + * If not, see . + */ + #include "file_system.h" #include "assert.h" -#include "byte_stream.h" #include "log.h" +#include "path.h" #include "string_util.h" #include #include #include +#include #ifdef __APPLE__ #include #include #include -#else -#include #endif #ifdef __FreeBSD__ @@ -20,12 +34,14 @@ #endif #if defined(_WIN32) +#include "windows_headers.h" +#include #include +#include #if defined(_UWP) #include #include - #include #include #include @@ -38,527 +54,31 @@ #else #include #include +#include #include #include #include #include #endif -#ifdef __ANDROID__ -#include -#endif - Log_SetChannel(FileSystem); -namespace FileSystem { - -#ifdef __ANDROID__ - -static JavaVM* s_android_jvm; -static jobject s_android_FileHelper_object; -static jclass s_android_FileHelper_class; -static jmethodID s_android_FileHelper_openURIAsFileDescriptor; -static jmethodID s_android_FileHelper_FindFiles; -static jmethodID s_android_FileHelper_StatFile; -static jclass s_android_FileHelper_FindResult_class; -static jfieldID s_android_FileHelper_FindResult_name; -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 jclass s_android_FileHelper_StatResult_class; -static jfieldID s_android_FileHelper_StatResult_size; -static jfieldID s_android_FileHelper_StatResult_modifiedTime; -static jfieldID s_android_FileHelper_StatResult_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() +#ifdef _WIN32 +static std::time_t ConvertFileTimeToUnixTime(const FILETIME& ft) { - JNIEnv* env; - if (s_android_jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) - return nullptr; - else - return env; + // based off https://stackoverflow.com/a/6161842 + static constexpr s64 WINDOWS_TICK = 10000000; + static constexpr s64 SEC_TO_UNIX_EPOCH = 11644473600LL; + + const s64 full = static_cast((static_cast(ft.dwHighDateTime) << 32) | static_cast(ft.dwLowDateTime)); + return static_cast(full / WINDOWS_TICK - SEC_TO_UNIX_EPOCH); } - -static bool IsUriPath(const std::string_view& path) -{ - return StringUtil::StartsWith(path, "content:/") || StringUtil::StartsWith(path, "file:/"); -} - -static bool UriHelpersAreAvailable() -{ - return (s_android_FileHelper_object != nullptr); -} - -void SetAndroidFileHelper(void* jvm, void* env, void* object) -{ - Assert(!jvm || !s_android_jvm || s_android_jvm == jvm); - - if (s_android_FileHelper_object) - { - JNIEnv* jenv = GetJNIEnv(); - jenv->DeleteGlobalRef(s_android_FileHelper_FindResult_class); - s_android_FileHelper_FindResult_name = {}; - s_android_FileHelper_FindResult_relativeName = {}; - s_android_FileHelper_FindResult_size = {}; - s_android_FileHelper_FindResult_modifiedTime = {}; - s_android_FileHelper_FindResult_flags = {}; - s_android_FileHelper_FindResult_class = {}; - jenv->DeleteGlobalRef(s_android_FileHelper_StatResult_class); - s_android_FileHelper_StatResult_size = {}; - s_android_FileHelper_StatResult_modifiedTime = {}; - s_android_FileHelper_StatResult_flags = {}; - s_android_FileHelper_StatResult_class = {}; - - 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_StatFile = {}; - s_android_FileHelper_FindFiles = {}; - s_android_FileHelper_object = {}; - s_android_FileHelper_class = {}; - s_android_jvm = {}; - } - - if (!object) - return; - - JNIEnv* jenv = static_cast(env); - s_android_jvm = static_cast(jvm); - s_android_FileHelper_object = jenv->NewGlobalRef(static_cast(object)); - Assert(s_android_FileHelper_object); - - jclass fh_class = jenv->GetObjectClass(static_cast(object)); - s_android_FileHelper_class = static_cast(jenv->NewGlobalRef(fh_class)); - Assert(s_android_FileHelper_class); - jenv->DeleteLocalRef(fh_class); - - s_android_FileHelper_openURIAsFileDescriptor = - jenv->GetMethodID(s_android_FileHelper_class, "openURIAsFileDescriptor", "(Ljava/lang/String;Ljava/lang/String;)I"); - s_android_FileHelper_StatFile = - jenv->GetMethodID(s_android_FileHelper_class, "statFile", - "(Ljava/lang/String;)Lcom/github/stenzek/duckstation/FileHelper$StatResult;"); - s_android_FileHelper_FindFiles = - jenv->GetMethodID(s_android_FileHelper_class, "findFiles", - "(Ljava/lang/String;I)[Lcom/github/stenzek/duckstation/FileHelper$FindResult;"); - 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); - s_android_FileHelper_FindResult_class = static_cast(jenv->NewGlobalRef(fr_class)); - Assert(s_android_FileHelper_FindResult_class); - jenv->DeleteLocalRef(fr_class); - - s_android_FileHelper_FindResult_relativeName = - jenv->GetFieldID(s_android_FileHelper_FindResult_class, "relativeName", "Ljava/lang/String;"); - s_android_FileHelper_FindResult_name = - jenv->GetFieldID(s_android_FileHelper_FindResult_class, "name", "Ljava/lang/String;"); - s_android_FileHelper_FindResult_size = jenv->GetFieldID(s_android_FileHelper_FindResult_class, "size", "J"); - s_android_FileHelper_FindResult_modifiedTime = - jenv->GetFieldID(s_android_FileHelper_FindResult_class, "modifiedTime", "J"); - s_android_FileHelper_FindResult_flags = jenv->GetFieldID(s_android_FileHelper_FindResult_class, "flags", "I"); - Assert(s_android_FileHelper_FindResult_relativeName && s_android_FileHelper_FindResult_name && - s_android_FileHelper_FindResult_size && s_android_FileHelper_FindResult_modifiedTime && - s_android_FileHelper_FindResult_flags); - - jclass st_class = jenv->FindClass("com/github/stenzek/duckstation/FileHelper$StatResult"); - Assert(st_class); - s_android_FileHelper_StatResult_class = static_cast(jenv->NewGlobalRef(st_class)); - Assert(s_android_FileHelper_StatResult_class); - jenv->DeleteLocalRef(st_class); - - s_android_FileHelper_StatResult_size = jenv->GetFieldID(s_android_FileHelper_StatResult_class, "size", "J"); - s_android_FileHelper_StatResult_modifiedTime = - jenv->GetFieldID(s_android_FileHelper_StatResult_class, "modifiedTime", "J"); - s_android_FileHelper_StatResult_flags = jenv->GetFieldID(s_android_FileHelper_StatResult_class, "flags", "I"); - Assert(s_android_FileHelper_StatResult_size && s_android_FileHelper_StatResult_modifiedTime && - s_android_FileHelper_StatResult_flags); -} - -static std::FILE* OpenUriFile(const char* path, const char* mode) -{ - // translate C modes to Java modes - TinyString mode_trimmed; - std::size_t mode_len = std::strlen(mode); - for (size_t i = 0; i < mode_len; i++) - { - if (mode[i] == 'r' || mode[i] == 'w' || mode[i] == '+') - mode_trimmed.AppendCharacter(mode[i]); - } - - // TODO: Handle append mode by seeking to end. - const char* java_mode = nullptr; - if (mode_trimmed == "r") - java_mode = "r"; - else if (mode_trimmed == "r+") - java_mode = "rw"; - else if (mode_trimmed == "w") - java_mode = "w"; - else if (mode_trimmed == "w+") - java_mode = "rwt"; - - if (!java_mode) - { - Log_ErrorPrintf("Could not translate file mode '%s' ('%s')", mode, mode_trimmed.GetCharArray()); - return nullptr; - } - - // Hand off to Java... - JNIEnv* env = GetJNIEnv(); - jstring path_jstr = env->NewStringUTF(path); - jstring mode_jstr = env->NewStringUTF(java_mode); - int fd = - env->CallIntMethod(s_android_FileHelper_object, s_android_FileHelper_openURIAsFileDescriptor, path_jstr, mode_jstr); - env->DeleteLocalRef(mode_jstr); - env->DeleteLocalRef(path_jstr); - - // Just in case... - if (env->ExceptionCheck()) - { - env->ExceptionClear(); - return nullptr; - } - - if (fd < 0) - return nullptr; - - // Convert to a C file object. - std::FILE* fp = fdopen(fd, mode); - if (!fp) - { - Log_ErrorPrintf("Failed to convert FD %d to C FILE for '%s'.", fd, path); - close(fd); - return nullptr; - } - - return fp; -} - -static bool FindUriFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* pVector) -{ - if (!s_android_FileHelper_object) - return false; - - JNIEnv* env = GetJNIEnv(); - - jstring path_jstr = env->NewStringUTF(path); - jobjectArray arr = static_cast(env->CallObjectMethod( - s_android_FileHelper_object, s_android_FileHelper_FindFiles, path_jstr, static_cast(flags))); - env->DeleteLocalRef(path_jstr); - if (!arr) - return false; - - // small speed optimization for '*' case - bool hasWildCards = false; - bool wildCardMatchAll = false; - if (std::strpbrk(pattern, "*?") != nullptr) - { - hasWildCards = true; - wildCardMatchAll = !(std::strcmp(pattern, "*")); - } - - jsize count = env->GetArrayLength(arr); - for (jsize i = 0; i < count; i++) - { - jobject result = env->GetObjectArrayElement(arr, i); - if (!result) - continue; - - jstring result_name_obj = static_cast(env->GetObjectField(result, s_android_FileHelper_FindResult_name)); - jstring result_relative_name_obj = - static_cast(env->GetObjectField(result, s_android_FileHelper_FindResult_relativeName)); - const u64 result_size = static_cast(env->GetLongField(result, s_android_FileHelper_FindResult_size)); - const u64 result_modified_time = - static_cast(env->GetLongField(result, s_android_FileHelper_FindResult_modifiedTime)); - const u32 result_flags = static_cast(env->GetIntField(result, s_android_FileHelper_FindResult_flags)); - - if (result_name_obj && result_relative_name_obj) - { - const char* result_name = env->GetStringUTFChars(result_name_obj, nullptr); - const char* result_relative_name = env->GetStringUTFChars(result_relative_name_obj, nullptr); - if (result_relative_name) - { - // match the filename - bool matched; - if (hasWildCards) - { - matched = wildCardMatchAll || StringUtil::WildcardMatch(result_relative_name, pattern); - } - else - { - matched = std::strcmp(result_relative_name, pattern) == 0; - } - - if (matched) - { - FILESYSTEM_FIND_DATA ffd; - ffd.FileName = ((flags & FILESYSTEM_FIND_RELATIVE_PATHS) != 0) ? result_relative_name : result_name; - ffd.Attributes = result_flags; - ffd.ModificationTime.SetUnixTimestamp(result_modified_time); - ffd.Size = result_size; - pVector->push_back(std::move(ffd)); - } - } - - if (result_name) - env->ReleaseStringUTFChars(result_name_obj, result_name); - if (result_relative_name) - env->ReleaseStringUTFChars(result_relative_name_obj, result_relative_name); - } - - if (result_name_obj) - env->DeleteLocalRef(result_name_obj); - if (result_relative_name_obj) - env->DeleteLocalRef(result_relative_name_obj); - - env->DeleteLocalRef(result); - } - - env->DeleteLocalRef(arr); - return true; -} - -static bool StatUriFile(const char* path, FILESYSTEM_STAT_DATA* sd) -{ - if (!s_android_FileHelper_object) - return false; - - JNIEnv* env = GetJNIEnv(); - - jstring path_jstr = env->NewStringUTF(path); - jobject result = static_cast( - env->CallObjectMethod(s_android_FileHelper_object, s_android_FileHelper_StatFile, path_jstr)); - env->DeleteLocalRef(path_jstr); - if (!result) - return false; - - const u64 result_size = static_cast(env->GetLongField(result, s_android_FileHelper_StatResult_size)); - const u64 result_modified_time = - static_cast(env->GetLongField(result, s_android_FileHelper_StatResult_modifiedTime)); - const u32 result_flags = static_cast(env->GetIntField(result, s_android_FileHelper_StatResult_flags)); - sd->Attributes = result_flags; - sd->ModificationTime.SetUnixTimestamp(result_modified_time); - sd->Size = result_size; - - env->DeleteLocalRef(result); - 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( - 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(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) - : m_directoryPath(directoryPath), m_recursiveWatch(recursiveWatch) -{ -} - -ChangeNotifier::~ChangeNotifier() {} - -void CanonicalizePath(char* Destination, u32 cbDestination, const char* Path, bool OSPath /*= true*/) -{ - u32 i, j; - DebugAssert(Destination && cbDestination > 0 && Path); - - // get length - u32 pathLength = static_cast(std::strlen(Path)); - - // clone to a local buffer if the same pointer - if (Destination == Path) - { - char* pathClone = (char*)alloca(pathLength + 1); - StringUtil::Strlcpy(pathClone, Path, pathLength + 1); - Path = pathClone; - } - - // zero destination - std::memset(Destination, 0, cbDestination); - - // iterate path - u32 destinationLength = 0; - for (i = 0; i < pathLength;) - { - char prevCh = (i > 0) ? Path[i - 1] : '\0'; - char currentCh = Path[i]; - char nextCh = (i < (pathLength - 1)) ? Path[i + 1] : '\0'; - - if (currentCh == '.') - { - if (prevCh == '\\' || prevCh == '/' || prevCh == '\0') - { - // handle '.' - if (nextCh == '\\' || nextCh == '/' || nextCh == '\0') - { - // skip '.\' - i++; - - // remove the previous \, if we have one trailing the dot it'll append it anyway - if (destinationLength > 0) - Destination[--destinationLength] = '\0'; - // if there was no previous \, skip past the next one - else if (nextCh != '\0') - i++; - - continue; - } - // handle '..' - else if (nextCh == '.') - { - char afterNext = ((i + 1) < pathLength) ? Path[i + 2] : '\0'; - if (afterNext == '\\' || afterNext == '/' || afterNext == '\0') - { - // remove one directory of the path, including the /. - if (destinationLength > 1) - { - for (j = destinationLength - 2; j > 0; j--) - { - if (Destination[j] == '\\' || Destination[j] == '/') - break; - } - - destinationLength = j; -#ifdef _DEBUG - Destination[destinationLength] = '\0'; #endif - } - - // skip the dot segment - i += 2; - continue; - } - } - } - } - - // fix ospath - if (OSPath && (currentCh == '\\' || currentCh == '/')) - currentCh = FS_OSPATH_SEPARATOR_CHARACTER; - - // copy character - if (destinationLength < cbDestination) - { - Destination[destinationLength++] = currentCh; -#ifdef _DEBUG - Destination[destinationLength] = '\0'; -#endif - } - else - break; - - // increment position by one - i++; - } - - // if we end up with the empty string, return '.' - if (destinationLength == 0) - Destination[destinationLength++] = '.'; - - // ensure nullptr termination - if (destinationLength < cbDestination) - Destination[destinationLength] = '\0'; - else - Destination[destinationLength - 1] = '\0'; -} - -void CanonicalizePath(String& Destination, const char* Path, bool OSPath /* = true */) -{ - // the function won't actually write any more characters than are present to the buffer, - // so we can get away with simply passing both pointers if they are the same. - if (Destination.GetWriteableCharArray() != Path) - { - // otherwise, resize the destination to at least the source's size, and then pass as-is - Destination.Reserve(static_cast(std::strlen(Path)) + 1); - } - - CanonicalizePath(Destination.GetWriteableCharArray(), Destination.GetBufferSize(), Path, OSPath); - Destination.UpdateSize(); -} - -void CanonicalizePath(String& Destination, bool OSPath /* = true */) -{ - CanonicalizePath(Destination, Destination); -} - -void CanonicalizePath(std::string& path, bool OSPath /*= true*/) -{ - CanonicalizePath(path.data(), static_cast(path.size() + 1), path.c_str(), OSPath); -} static inline bool FileSystemCharacterIsSane(char c, bool StripSlashes) { - if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && !(c >= '0' && c <= '9') && c != ' ' && c != '_' && - c != '-' && c != '.') + if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && !(c >= '0' && c <= '9') && c != ' ' && c != ' ' && + c != '_' && c != '-' && c != '.') { if (!StripSlashes && (c == '/' || c == '\\')) return true; @@ -569,7 +89,50 @@ static inline bool FileSystemCharacterIsSane(char c, bool StripSlashes) return true; } -void SanitizeFileName(char* Destination, u32 cbDestination, const char* FileName, bool StripSlashes /* = true */) +template +static inline void PathAppendString(std::string& dst, const T& src) +{ + if (dst.capacity() < (dst.length() + src.length())) + dst.reserve(dst.length() + src.length()); + + bool last_separator = (!dst.empty() && dst.back() == FS_OSPATH_SEPARATOR_CHARACTER); + + size_t index = 0; + +#ifdef _WIN32 + // special case for UNC paths here + if (dst.empty() && src.length() >= 3 && src[0] == '\\' && src[1] == '\\' && src[2] != '\\') + { + dst.append("\\\\"); + index = 2; + } +#endif + + for (; index < src.length(); index++) + { + const char ch = src[index]; + +#ifdef _WIN32 + // convert forward slashes to backslashes + if (ch == '\\' || ch == '/') +#else + if (ch == '/') +#endif + { + if (last_separator) + continue; + last_separator = true; + dst.push_back(FS_OSPATH_SEPARATOR_CHARACTER); + } + else + { + last_separator = false; + dst.push_back(ch); + } + } +} + +void Path::SanitizeFileName(char* Destination, u32 cbDestination, const char* FileName, bool StripSlashes /* = true */) { u32 i; u32 fileNameLength = static_cast(std::strlen(FileName)); @@ -594,41 +157,7 @@ void SanitizeFileName(char* Destination, u32 cbDestination, const char* FileName } } -void SanitizeFileName(String& Destination, const char* FileName, bool StripSlashes /* = true */) -{ - u32 i; - u32 fileNameLength; - - // if same buffer, use fastpath - if (Destination.GetWriteableCharArray() == FileName) - { - fileNameLength = Destination.GetLength(); - for (i = 0; i < fileNameLength; i++) - { - if (!FileSystemCharacterIsSane(FileName[i], StripSlashes)) - Destination[i] = '_'; - } - } - else - { - fileNameLength = static_cast(std::strlen(FileName)); - Destination.Resize(fileNameLength); - for (i = 0; i < fileNameLength; i++) - { - if (FileSystemCharacterIsSane(FileName[i], StripSlashes)) - Destination[i] = FileName[i]; - else - Destination[i] = '_'; - } - } -} - -void SanitizeFileName(String& Destination, bool StripSlashes /* = true */) -{ - return SanitizeFileName(Destination, Destination, StripSlashes); -} - -void SanitizeFileName(std::string& Destination, bool StripSlashes /* = true*/) +void Path::SanitizeFileName(std::string& Destination, bool StripSlashes /* = true*/) { const std::size_t len = Destination.length(); for (std::size_t i = 0; i < len; i++) @@ -638,45 +167,142 @@ void SanitizeFileName(std::string& Destination, bool StripSlashes /* = true*/) } } -bool IsAbsolutePath(const std::string_view& path) +bool Path::IsAbsolute(const std::string_view& path) { #ifdef _WIN32 return (path.length() >= 3 && ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z')) && - path[1] == ':' && (path[2] == '/' || path[2] == '\\')); + path[1] == ':' && (path[2] == '/' || path[2] == '\\')) || + (path.length() >= 3 && path[0] == '\\' && path[1] == '\\'); #else return (path.length() >= 1 && path[0] == '/'); #endif } -std::string_view StripExtension(const std::string_view& path) +std::string Path::ToNativePath(const std::string_view& path) { - std::string_view::size_type pos = path.rfind('.'); - if (pos == std::string::npos) + std::string ret; + PathAppendString(ret, path); + + // remove trailing slashes + if (ret.length() > 1) + { + while (ret.back() == FS_OSPATH_SEPARATOR_CHARACTER) + ret.pop_back(); + } + + return ret; +} + +void Path::ToNativePath(std::string* path) +{ + *path = Path::ToNativePath(*path); +} + +std::string Path::Canonicalize(const std::string_view& path) +{ + std::vector components = Path::SplitNativePath(path); + std::vector new_components; + new_components.reserve(components.size()); + for (const std::string_view& component : components) + { + if (component == ".") + { + // current directory, so it can be skipped, unless it's the only component + if (components.size() == 1) + new_components.push_back(std::move(component)); + } + else if (component == "..") + { + // parent directory, pop one off if we're not at the beginning, otherwise preserve. + if (!new_components.empty()) + new_components.pop_back(); + else + new_components.push_back(std::move(component)); + } + else + { + // anything else, preserve + new_components.push_back(std::move(component)); + } + } + + return Path::JoinNativePath(new_components); +} + +void Path::Canonicalize(std::string* path) +{ + *path = Canonicalize(*path); +} + +std::string Path::MakeRelative(const std::string_view& path, const std::string_view& relative_to) +{ + // simple algorithm, we just work on the components. could probably be better, but it'll do for now. + std::vector path_components(SplitNativePath(path)); + std::vector relative_components(SplitNativePath(relative_to)); + std::vector new_components; + + // both must be absolute paths + if (Path::IsAbsolute(path) && Path::IsAbsolute(relative_to)) + { + // find the number of same components + size_t num_same = 0; + for (size_t i = 0; i < path_components.size() && i < relative_components.size(); i++) + { + if (path_components[i] == relative_components[i]) + num_same++; + else + break; + } + + // we need at least one same component + if (num_same > 0) + { + // from the relative_to directory, back up to the start of the common components + const size_t num_ups = relative_components.size() - num_same; + for (size_t i = 0; i < num_ups; i++) + new_components.emplace_back(".."); + + // and add the remainder of the path components + for (size_t i = num_same; i < path_components.size(); i++) + new_components.push_back(std::move(path_components[i])); + } + else + { + // no similarity + new_components = std::move(path_components); + } + } + else + { + // not absolute + new_components = std::move(path_components); + } + + return JoinNativePath(new_components); +} + +std::string_view Path::GetExtension(const std::string_view& path) +{ + const std::string_view::size_type pos = path.rfind('.'); + if (pos == std::string_view::npos) + return std::string_view(); + else + return path.substr(pos + 1); +} + +std::string_view Path::StripExtension(const std::string_view& path) +{ + const std::string_view::size_type pos = path.rfind('.'); + if (pos == std::string_view::npos) return path; return path.substr(0, pos); } -std::string ReplaceExtension(const std::string_view& path, const std::string_view& new_extension) +std::string Path::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) + const std::string_view::size_type pos = path.rfind('.'); + if (pos == std::string_view::npos) return std::string(path); std::string ret(path, 0, pos + 1); @@ -699,90 +325,208 @@ static std::string_view::size_type GetLastSeperatorPosition(const std::string_vi if (last_separator == std::string_view::npos || other_last_separator > last_separator) last_separator = other_last_separator; } - -#elif defined(__ANDROID__) - if (IsUriPath(filename)) - { - // scoped storage rubbish - std::string_view::size_type other_last_separator = filename.rfind("%2F"); - if (other_last_separator != std::string_view::npos) - { - if (include_separator) - other_last_separator += 3; - if (last_separator == std::string_view::npos || other_last_separator > last_separator) - last_separator = other_last_separator; - } - std::string_view::size_type lower_other_last_separator = filename.rfind("%2f"); - if (lower_other_last_separator != std::string_view::npos) - { - if (include_separator) - lower_other_last_separator += 3; - if (last_separator == std::string_view::npos || lower_other_last_separator > last_separator) - last_separator = lower_other_last_separator; - } - } #endif return last_separator; } -std::string GetDisplayNameFromPath(const std::string_view& path) +std::string FileSystem::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 + return std::string(Path::GetFileName(path)); } -std::string_view GetPathDirectory(const std::string_view& path) +std::string_view Path::GetDirectory(const std::string_view& path) { - std::string::size_type pos = GetLastSeperatorPosition(path, false); + const std::string::size_type pos = GetLastSeperatorPosition(path, false); if (pos == std::string_view::npos) return {}; return path.substr(0, pos); } -std::string_view GetFileNameFromPath(const std::string_view& path) +std::string_view Path::GetFileName(const std::string_view& path) { - std::string_view::size_type pos = GetLastSeperatorPosition(path, true); + const std::string_view::size_type pos = GetLastSeperatorPosition(path, true); if (pos == std::string_view::npos) return path; return path.substr(pos); } -std::string_view GetFileTitleFromPath(const std::string_view& path) +std::string_view Path::GetFileTitle(const std::string_view& path) { - std::string_view filename(GetFileNameFromPath(path)); - std::string::size_type pos = filename.rfind('.'); + const std::string_view filename(GetFileName(path)); + const std::string::size_type pos = filename.rfind('.'); if (pos == std::string_view::npos) return filename; return filename.substr(0, pos); } -std::vector GetRootDirectoryList() +std::string Path::ChangeFileName(const std::string_view& path, const std::string_view& new_filename) +{ + std::string ret; + PathAppendString(ret, path); + + const std::string_view::size_type pos = GetLastSeperatorPosition(ret, true); + if (pos == std::string_view::npos) + { + ret.clear(); + PathAppendString(ret, new_filename); + } + else + { + if (!new_filename.empty()) + { + ret.erase(pos); + PathAppendString(ret, new_filename); + } + else + { + ret.erase(pos - 1); + } + } + + return ret; +} + +void Path::ChangeFileName(std::string* path, const std::string_view& new_filename) +{ + *path = ChangeFileName(*path, new_filename); +} + +std::string Path::AppendDirectory(const std::string_view& path, const std::string_view& new_dir) +{ + std::string ret; + if (!new_dir.empty()) + { + const std::string_view::size_type pos = GetLastSeperatorPosition(path, true); + + ret.reserve(path.length() + new_dir.length() + 1); + if (pos != std::string_view::npos) + PathAppendString(ret, path.substr(0, pos)); + + while (!ret.empty() && ret.back() == FS_OSPATH_SEPARATOR_CHARACTER) + ret.pop_back(); + + if (!ret.empty()) + ret += FS_OSPATH_SEPARATOR_CHARACTER; + + PathAppendString(ret, new_dir); + + if (pos != std::string_view::npos) + { + const std::string_view filepart(path.substr(pos)); + if (!filepart.empty()) + { + ret += FS_OSPATH_SEPARATOR_CHARACTER; + PathAppendString(ret, filepart); + } + } + else if (!path.empty()) + { + ret += FS_OSPATH_SEPARATOR_CHARACTER; + PathAppendString(ret, path); + } + } + else + { + PathAppendString(ret, path); + } + + return ret; +} + +void Path::AppendDirectory(std::string* path, const std::string_view& new_dir) +{ + *path = AppendDirectory(*path, new_dir); +} + +std::vector Path::SplitWindowsPath(const std::string_view& path) +{ + std::vector parts; + + std::string::size_type start = 0; + std::string::size_type pos = 0; + + // preserve unc paths + if (path.size() > 2 && path[0] == '\\' && path[1] == '\\') + pos = 2; + + while (pos < path.size()) + { + if (path[pos] != '/' && path[pos] != '\\') + { + pos++; + continue; + } + + // skip consecutive separators + if (pos != start) + parts.push_back(path.substr(start, pos - start)); + + pos++; + start = pos; + } + + if (start != pos) + parts.push_back(path.substr(start)); + + return parts; +} + +std::string Path::JoinWindowsPath(const std::vector& components) +{ + return StringUtil::JoinString(components.begin(), components.end(), '\\'); +} + +std::vector Path::SplitNativePath(const std::string_view& path) +{ +#ifdef _WIN32 + return SplitWindowsPath(path); +#else + std::vector parts; + + std::string::size_type start = 0; + std::string::size_type pos = 0; + while (pos < path.size()) + { + if (path[pos] != '/') + { + pos++; + continue; + } + + // skip consecutive separators + // for unix, we create an empty element at the beginning when it's an absolute path + // that way, when it's re-joined later, we preserve the starting slash. + if (pos != start || pos == 0) + parts.push_back(path.substr(start, pos - start)); + + pos++; + start = pos; + } + + if (start != pos) + parts.push_back(path.substr(start)); + + return parts; +#endif +} + +std::string Path::JoinNativePath(const std::vector& components) +{ + return StringUtil::JoinString(components.begin(), components.end(), FS_OSPATH_SEPARATOR_CHARACTER); +} + +std::vector FileSystem::GetRootDirectoryList() { std::vector results; #if defined(_WIN32) && !defined(_UWP) char buf[256]; - if (GetLogicalDriveStringsA(sizeof(buf), buf) != 0) + const DWORD size = GetLogicalDriveStringsA(sizeof(buf), buf); + if (size != 0 && size < (sizeof(buf) - 1)) { const char* ptr = buf; while (*ptr != '\0') @@ -825,18 +569,10 @@ std::vector GetRootDirectoryList() return results; } -std::string BuildRelativePath(const std::string_view& filename, const std::string_view& new_filename) +std::string Path::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); @@ -844,18 +580,47 @@ std::string BuildRelativePath(const std::string_view& filename, const std::strin return new_string; } -FileSystem::ManagedCFilePtr OpenManagedCFile(const char* filename, const char* mode) +std::string Path::Combine(const std::string_view& base, const std::string_view& next) { - return ManagedCFilePtr(OpenCFile(filename, mode), [](std::FILE* fp) { std::fclose(fp); }); + std::string ret; + ret.reserve(base.length() + next.length() + 1); + + PathAppendString(ret, base); + while (!ret.empty() && ret.back() == FS_OSPATH_SEPARATOR_CHARACTER) + ret.pop_back(); + + ret += FS_OSPATH_SEPARATOR_CHARACTER; + PathAppendString(ret, next); + while (!ret.empty() && ret.back() == FS_OSPATH_SEPARATOR_CHARACTER) + ret.pop_back(); + + return ret; } #ifdef _UWP -std::FILE* OpenCFileUWP(const wchar_t* wfilename, const wchar_t* mode) +static std::FILE* OpenCFileUWP(const wchar_t* wfilename, const wchar_t* mode, FileSystem::FileShareMode share_mode) { DWORD access = 0; DWORD share = 0; DWORD disposition = 0; + switch (share_mode) + { + case FileSystem::FileShareMode::DenyNone: + share = FILE_SHARE_READ | FILE_SHARE_WRITE; + break; + case FileSystem::FileShareMode::DenyRead: + share = FILE_SHARE_WRITE; + break; + case FileSystem::FileShareMode::DenyWrite: + share = FILE_SHARE_READ; + break; + case FileSystem::FileShareMode::DenyReadWrite: + default: + share = 0; + break; + } + int flags = 0; const wchar_t* tmode = mode; while (*tmode) @@ -863,7 +628,6 @@ std::FILE* OpenCFileUWP(const wchar_t* wfilename, const wchar_t* mode) if (*tmode == L'r' && *(tmode + 1) == L'+') { access = GENERIC_READ | GENERIC_WRITE; - share = 0; disposition = OPEN_EXISTING; flags |= _O_RDWR; tmode += 2; @@ -871,7 +635,6 @@ std::FILE* OpenCFileUWP(const wchar_t* wfilename, const wchar_t* mode) else if (*tmode == L'w' && *(tmode + 1) == L'+') { access = GENERIC_READ | GENERIC_WRITE; - share = 0; disposition = CREATE_ALWAYS; flags |= _O_RDWR | _O_CREAT | _O_TRUNC; tmode += 2; @@ -879,7 +642,6 @@ std::FILE* OpenCFileUWP(const wchar_t* wfilename, const wchar_t* mode) else if (*tmode == L'a' && *(tmode + 1) == L'+') { access = GENERIC_READ | GENERIC_WRITE; - share = 0; disposition = CREATE_ALWAYS; flags |= _O_RDWR | _O_APPEND | _O_CREAT | _O_TRUNC; tmode += 2; @@ -887,7 +649,6 @@ std::FILE* OpenCFileUWP(const wchar_t* wfilename, const wchar_t* mode) else if (*tmode == L'r') { access = GENERIC_READ; - share = 0; disposition = OPEN_EXISTING; flags |= _O_RDONLY; tmode++; @@ -895,7 +656,6 @@ std::FILE* OpenCFileUWP(const wchar_t* wfilename, const wchar_t* mode) else if (*tmode == L'w') { access = GENERIC_WRITE; - share = 0; disposition = CREATE_ALWAYS; flags |= _O_WRONLY | _O_CREAT | _O_TRUNC; tmode++; @@ -903,7 +663,6 @@ std::FILE* OpenCFileUWP(const wchar_t* wfilename, const wchar_t* mode) else if (*tmode == L'a') { access = GENERIC_READ | GENERIC_WRITE; - share = 0; disposition = CREATE_ALWAYS; flags |= _O_WRONLY | _O_APPEND | _O_CREAT | _O_TRUNC; tmode++; @@ -959,36 +718,24 @@ std::FILE* OpenCFileUWP(const wchar_t* wfilename, const wchar_t* mode) } #endif // _UWP -std::FILE* OpenCFile(const char* filename, const char* mode) +std::FILE* FileSystem::OpenCFile(const char* filename, const char* mode) { #ifdef _WIN32 - int filename_len = static_cast(std::strlen(filename)); - int mode_len = static_cast(std::strlen(mode)); - int wlen = MultiByteToWideChar(CP_UTF8, 0, filename, filename_len, nullptr, 0); - int wmodelen = MultiByteToWideChar(CP_UTF8, 0, mode, mode_len, nullptr, 0); - if (wlen > 0 && wmodelen > 0) + const std::wstring wfilename(StringUtil::UTF8StringToWideString(filename)); + const std::wstring wmode(StringUtil::UTF8StringToWideString(mode)); + if (!wfilename.empty() && !wmode.empty()) { - wchar_t* wfilename = static_cast(alloca(sizeof(wchar_t) * (wlen + 1))); - wchar_t* wmode = static_cast(alloca(sizeof(wchar_t) * (wmodelen + 1))); - wlen = MultiByteToWideChar(CP_UTF8, 0, filename, filename_len, wfilename, wlen); - wmodelen = MultiByteToWideChar(CP_UTF8, 0, mode, mode_len, wmode, wmodelen); - if (wlen > 0 && wmodelen > 0) + std::FILE* fp; + if (_wfopen_s(&fp, wfilename.c_str(), wmode.c_str()) != 0) { - wfilename[wlen] = 0; - wmode[wmodelen] = 0; - - std::FILE* fp; - if (_wfopen_s(&fp, wfilename, wmode) != 0) - { #ifdef _UWP - return OpenCFileUWP(wfilename, wmode); + return OpenCFileUWP(wfilename.c_str(), wmode.c_str(), FileShareMode::DenyReadWrite); #else - return nullptr; + return nullptr; #endif - } - - return fp; } + + return fp; } std::FILE* fp; @@ -997,21 +744,83 @@ std::FILE* OpenCFile(const char* filename, const char* mode) return fp; #else -#ifdef __ANDROID__ - if (IsUriPath(filename) && UriHelpersAreAvailable()) - return OpenUriFile(filename, mode); -#endif - return std::fopen(filename, mode); #endif } -int FSeek64(std::FILE* fp, s64 offset, int whence) +int FileSystem::OpenFDFile(const char* filename, int flags, int mode) +{ +#ifdef _WIN32 + const std::wstring wfilename(StringUtil::UTF8StringToWideString(filename)); + if (!wfilename.empty()) + { + // TODO: UWP + return _wopen(wfilename.c_str(), flags, mode); + } + + return -1; +#else + return open(filename, flags, mode); +#endif +} + +FileSystem::ManagedCFilePtr FileSystem::OpenManagedCFile(const char* filename, const char* mode) +{ + return ManagedCFilePtr(OpenCFile(filename, mode), [](std::FILE* fp) { std::fclose(fp); }); +} + +std::FILE* FileSystem::OpenSharedCFile(const char* filename, const char* mode, FileShareMode share_mode) +{ +#ifdef _WIN32 + const std::wstring wfilename(StringUtil::UTF8StringToWideString(filename)); + const std::wstring wmode(StringUtil::UTF8StringToWideString(mode)); + if (wfilename.empty() || wmode.empty()) + return nullptr; + + int share_flags = 0; + switch (share_mode) + { + case FileShareMode::DenyNone: + share_flags = _SH_DENYNO; + break; + case FileShareMode::DenyRead: + share_flags = _SH_DENYRD; + break; + case FileShareMode::DenyWrite: + share_flags = _SH_DENYWR; + break; + case FileShareMode::DenyReadWrite: + default: + share_flags = _SH_DENYRW; + break; + } + + std::FILE* fp = _wfsopen(wfilename.c_str(), wmode.c_str(), share_flags); + if (fp) + return fp; + +#ifdef _UWP + return OpenCFileUWP(wfilename.c_str(), wmode.c_str(), share_mode); +#else + return nullptr; +#endif +#else + return std::fopen(filename, mode); +#endif +} + +FileSystem::ManagedCFilePtr FileSystem::OpenManagedSharedCFile(const char* filename, const char* mode, + FileShareMode share_mode) +{ + return ManagedCFilePtr(OpenSharedCFile(filename, mode, share_mode), [](std::FILE* fp) { std::fclose(fp); }); +} + +int FileSystem::FSeek64(std::FILE* fp, s64 offset, int whence) { #ifdef _WIN32 return _fseeki64(fp, offset, whence); #else - // Prevent truncation on platforms which don't have a 64-bit off_t (Android 32-bit). + // Prevent truncation on platforms which don't have a 64-bit off_t. if constexpr (sizeof(off_t) != sizeof(s64)) { if (offset < std::numeric_limits::min() || offset > std::numeric_limits::max()) @@ -1022,7 +831,7 @@ int FSeek64(std::FILE* fp, s64 offset, int whence) #endif } -s64 FTell64(std::FILE* fp) +s64 FileSystem::FTell64(std::FILE* fp) { #ifdef _WIN32 return static_cast(_ftelli64(fp)); @@ -1031,7 +840,32 @@ s64 FTell64(std::FILE* fp) #endif } -std::optional> ReadBinaryFile(const char* filename) +s64 FileSystem::FSize64(std::FILE* fp) +{ + const s64 pos = FTell64(fp); + if (pos >= 0) + { + if (FSeek64(fp, 0, SEEK_END) == 0) + { + const s64 size = FTell64(fp); + if (FSeek64(fp, pos, SEEK_SET) == 0) + return size; + } + } + + return -1; +} + +s64 FileSystem::GetPathFileSize(const char* Path) +{ + FILESYSTEM_STAT_DATA sd; + if (!StatFile(Path, &sd)) + return -1; + + return sd.Size; +} + +std::optional> FileSystem::ReadBinaryFile(const char* filename) { ManagedCFilePtr fp = OpenManagedCFile(filename, "rb"); if (!fp) @@ -1040,10 +874,10 @@ std::optional> ReadBinaryFile(const char* filename) return ReadBinaryFile(fp.get()); } -std::optional> ReadBinaryFile(std::FILE* fp) +std::optional> FileSystem::ReadBinaryFile(std::FILE* fp) { std::fseek(fp, 0, SEEK_END); - long size = std::ftell(fp); + const long size = std::ftell(fp); std::fseek(fp, 0, SEEK_SET); if (size < 0) return std::nullopt; @@ -1055,7 +889,7 @@ std::optional> ReadBinaryFile(std::FILE* fp) return res; } -std::optional ReadFileToString(const char* filename) +std::optional FileSystem::ReadFileToString(const char* filename) { ManagedCFilePtr fp = OpenManagedCFile(filename, "rb"); if (!fp) @@ -1064,23 +898,24 @@ std::optional ReadFileToString(const char* filename) return ReadFileToString(fp.get()); } -std::optional ReadFileToString(std::FILE* fp) +std::optional FileSystem::ReadFileToString(std::FILE* fp) { std::fseek(fp, 0, SEEK_END); - long size = std::ftell(fp); + const long size = std::ftell(fp); std::fseek(fp, 0, SEEK_SET); if (size < 0) return std::nullopt; std::string res; res.resize(static_cast(size)); + // NOTE - assumes mode 'rb', for example, this will fail over missing Windows carriage return bytes if (size > 0 && std::fread(res.data(), 1u, static_cast(size), fp) != static_cast(size)) return std::nullopt; return res; } -bool WriteBinaryFile(const char* filename, const void* data, size_t data_length) +bool FileSystem::WriteBinaryFile(const char* filename, const void* data, size_t data_length) { ManagedCFilePtr fp = OpenManagedCFile(filename, "wb"); if (!fp) @@ -1092,7 +927,7 @@ bool WriteBinaryFile(const char* filename, const void* data, size_t data_length) return true; } -bool WriteFileToString(const char* filename, const std::string_view& sv) +bool FileSystem::WriteStringToFile(const char* filename, const std::string_view& sv) { ManagedCFilePtr fp = OpenManagedCFile(filename, "wb"); if (!fp) @@ -1104,64 +939,79 @@ bool WriteFileToString(const char* filename, const std::string_view& sv) return true; } -void BuildOSPath(char* Destination, u32 cbDestination, const char* Path) +bool FileSystem::EnsureDirectoryExists(const char* path, bool recursive) { - u32 i; - u32 pathLength = static_cast(std::strlen(Path)); + if (FileSystem::DirectoryExists(path)) + return true; - if (Destination == Path) - { - // fast path - for (i = 0; i < pathLength; i++) - { - if (Destination[i] == '/') - Destination[i] = FS_OSPATH_SEPARATOR_CHARACTER; - } - } - else - { - // slow path - pathLength = std::max(pathLength, cbDestination - 1); - for (i = 0; i < pathLength; i++) - { - Destination[i] = (Path[i] == '/') ? FS_OSPATH_SEPARATOR_CHARACTER : Path[i]; - } - - Destination[pathLength] = '\0'; - } + // if it fails to create, we're not going to be able to use it anyway + return FileSystem::CreateDirectory(path, recursive); } -void BuildOSPath(String& Destination, const char* Path) +bool FileSystem::RecursiveDeleteDirectory(const char* path) { - u32 i; - u32 pathLength; + FindResultsArray results; + if (FindFiles(path, "*", FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_FOLDERS | FILESYSTEM_FIND_HIDDEN_FILES, &results)) + { + for (const FILESYSTEM_FIND_DATA& fd : results) + { + if (fd.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) + { + if (!RecursiveDeleteDirectory(fd.FileName.c_str())) + return false; + } + else + { + if (!DeleteFile(fd.FileName.c_str())) + return false; + } + } + } - if (Destination.GetWriteableCharArray() == Path) - { - // fast path - pathLength = Destination.GetLength(); - ; - for (i = 0; i < pathLength; i++) - { - if (Destination[i] == '/') - Destination[i] = FS_OSPATH_SEPARATOR_CHARACTER; - } - } - else - { - // slow path - pathLength = static_cast(std::strlen(Path)); - Destination.Resize(pathLength); - for (i = 0; i < pathLength; i++) - { - Destination[i] = (Path[i] == '/') ? FS_OSPATH_SEPARATOR_CHARACTER : Path[i]; - } - } + return DeleteDirectory(path); } -void BuildOSPath(String& Destination) +bool FileSystem::CopyFilePath(const char* source, const char* destination, bool replace) { - BuildOSPath(Destination, Destination); +#ifndef _WIN32 + // TODO: There's technically a race here between checking and opening the file.. + // But fopen doesn't specify any way to say "don't create if it exists"... + if (!replace && FileExists(destination)) + return false; + + auto in_fp = OpenManagedCFile(source, "rb"); + if (!in_fp) + return false; + + auto out_fp = OpenManagedCFile(destination, "wb"); + if (!out_fp) + return false; + + u8 buf[4096]; + while (!std::feof(in_fp.get())) + { + size_t bytes_in = std::fread(buf, 1, sizeof(buf), in_fp.get()); + if ((bytes_in == 0 && !std::feof(in_fp.get())) || + (bytes_in > 0 && std::fwrite(buf, 1, bytes_in, out_fp.get()) != bytes_in)) + { + out_fp.reset(); + DeleteFile(destination); + return false; + } + } + + if (std::fflush(out_fp.get()) != 0) + { + out_fp.reset(); + DeleteFile(destination); + return false; + } + + return true; +#else + return CopyFileW(StringUtil::UTF8StringToWideString(source).c_str(), + StringUtil::UTF8StringToWideString(destination).c_str(), !replace); +#endif } #ifdef _WIN32 @@ -1193,181 +1043,26 @@ static DWORD WrapGetFileAttributes(const wchar_t* path) #endif } -static const u32 READ_DIRECTORY_CHANGES_NOTIFY_FILTER = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | - FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE | - FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_CREATION; - -class ChangeNotifierWin32 : public FileSystem::ChangeNotifier -{ -public: - ChangeNotifierWin32(HANDLE hDirectory, const String& directoryPath, bool recursiveWatch) - : FileSystem::ChangeNotifier(directoryPath, recursiveWatch), m_hDirectory(hDirectory), - m_directoryChangeQueued(false) - { - m_bufferSize = 16384; - m_pBuffer = new u8[m_bufferSize]; - } - - virtual ~ChangeNotifierWin32() - { - // if there is outstanding io, cancel it - if (m_directoryChangeQueued) - { - CancelIo(m_hDirectory); - - DWORD bytesTransferred; - GetOverlappedResult(m_hDirectory, &m_overlapped, &bytesTransferred, TRUE); - } - - CloseHandle(m_hDirectory); - delete[] m_pBuffer; - } - - virtual void EnumerateChanges(EnumerateChangesCallback callback, void* pUserData) override - { - DWORD bytesRead; - if (!GetOverlappedResult(m_hDirectory, &m_overlapped, &bytesRead, FALSE)) - { - if (GetLastError() == ERROR_IO_INCOMPLETE) - return; - - CancelIo(m_hDirectory); - m_directoryChangeQueued = false; - - QueueReadDirectoryChanges(); - return; - } - - // not queued any more - m_directoryChangeQueued = false; - - // has any bytes? - if (bytesRead > 0) - { - const u8* pCurrentPointer = m_pBuffer; - PathString fileName; - for (;;) - { - const FILE_NOTIFY_INFORMATION* pFileNotifyInformation = - reinterpret_cast(pCurrentPointer); - - // translate the event - u32 changeEvent = 0; - if (pFileNotifyInformation->Action == FILE_ACTION_ADDED) - changeEvent = ChangeEvent_FileAdded; - else if (pFileNotifyInformation->Action == FILE_ACTION_REMOVED) - changeEvent = ChangeEvent_FileRemoved; - else if (pFileNotifyInformation->Action == FILE_ACTION_MODIFIED) - changeEvent = ChangeEvent_FileModified; - else if (pFileNotifyInformation->Action == FILE_ACTION_RENAMED_OLD_NAME) - changeEvent = ChangeEvent_RenamedOldName; - else if (pFileNotifyInformation->Action == FILE_ACTION_RENAMED_NEW_NAME) - changeEvent = ChangeEvent_RenamedNewName; - - // translate the filename - int fileNameLength = - WideCharToMultiByte(CP_UTF8, 0, pFileNotifyInformation->FileName, - pFileNotifyInformation->FileNameLength / sizeof(WCHAR), nullptr, 0, nullptr, nullptr); - DebugAssert(fileNameLength >= 0); - fileName.Resize(fileNameLength); - fileNameLength = WideCharToMultiByte(CP_UTF8, 0, pFileNotifyInformation->FileName, - pFileNotifyInformation->FileNameLength / sizeof(WCHAR), - fileName.GetWriteableCharArray(), fileName.GetLength(), nullptr, nullptr); - if (fileNameLength != (int)fileName.GetLength()) - fileName.Resize(fileNameLength); - - // prepend the base path - fileName.PrependFormattedString("%s\\", m_directoryPath.GetCharArray()); - - // construct change info - ChangeInfo changeInfo; - changeInfo.Path = fileName; - changeInfo.Event = changeEvent; - - // invoke callback - callback(&changeInfo, pUserData); - - // has a next entry? - if (pFileNotifyInformation->NextEntryOffset == 0) - break; - - pCurrentPointer += pFileNotifyInformation->NextEntryOffset; - DebugAssert(pCurrentPointer < (m_pBuffer + m_bufferSize)); - } - } - - // re-queue the operation - QueueReadDirectoryChanges(); - } - - bool QueueReadDirectoryChanges() - { - DebugAssert(!m_directoryChangeQueued); - - std::memset(&m_overlapped, 0, sizeof(m_overlapped)); - if (ReadDirectoryChangesW(m_hDirectory, m_pBuffer, m_bufferSize, m_recursiveWatch, - READ_DIRECTORY_CHANGES_NOTIFY_FILTER, nullptr, &m_overlapped, nullptr) == FALSE) - return false; - - m_directoryChangeQueued = true; - return true; - } - -private: - HANDLE m_hDirectory; - OVERLAPPED m_overlapped; - bool m_directoryChangeQueued; - u8* m_pBuffer; - u32 m_bufferSize; -}; - -std::unique_ptr CreateChangeNotifier(const char* path, bool recursiveWatch) -{ - // open the directory up - std::wstring path_wstr(StringUtil::UTF8StringToWideString(path)); -#ifndef _UWP - HANDLE hDirectory = - CreateFileW(path_wstr.c_str(), FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, - OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr); -#else - CREATEFILE2_EXTENDED_PARAMETERS ep = {}; - ep.dwSize = sizeof(ep); - ep.dwFileAttributes = FILE_ATTRIBUTE_NORMAL; - ep.dwFileFlags = FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED; - HANDLE hDirectory = CreateFile2FromAppW(path_wstr.c_str(), FILE_LIST_DIRECTORY, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, OPEN_EXISTING, &ep); -#endif - if (hDirectory == nullptr) - return nullptr; - - // queue up the overlapped io - auto pChangeNotifier = std::make_unique(hDirectory, path, recursiveWatch); - if (!pChangeNotifier->QueueReadDirectoryChanges()) - return nullptr; - - return pChangeNotifier; -} - -static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, const char* Path, const char* Pattern, - u32 Flags, FileSystem::FindResultsArray* pResults) +static u32 RecursiveFindFiles(const char* origin_path, const char* parent_path, const char* path, const char* pattern, + u32 flags, FileSystem::FindResultsArray* results) { std::string tempStr; - if (Path) + if (path) { - if (ParentPath) - tempStr = StringUtil::StdStringFromFormat("%s\\%s\\%s\\*", OriginPath, ParentPath, Path); + if (parent_path) + tempStr = StringUtil::StdStringFromFormat("%s\\%s\\%s\\*", origin_path, parent_path, path); else - tempStr = StringUtil::StdStringFromFormat("%s\\%s\\*", OriginPath, Path); + tempStr = StringUtil::StdStringFromFormat("%s\\%s\\*", origin_path, path); } else { - tempStr = StringUtil::StdStringFromFormat("%s\\*", OriginPath); + tempStr = StringUtil::StdStringFromFormat("%s\\*", origin_path); } // holder for utf-8 conversion WIN32_FIND_DATAW wfd; std::string utf8_filename; - utf8_filename.reserve(countof(wfd.cFileName) * 2); + utf8_filename.reserve((sizeof(wfd.cFileName) / sizeof(wfd.cFileName[0])) * 2); #ifndef _UWP HANDLE hFind = FindFirstFileW(StringUtil::UTF8StringToWideString(tempStr).c_str(), &wfd); @@ -1383,16 +1078,16 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co bool hasWildCards = false; bool wildCardMatchAll = false; u32 nFiles = 0; - if (std::strpbrk(Pattern, "*?") != nullptr) + if (std::strpbrk(pattern, "*?") != nullptr) { hasWildCards = true; - wildCardMatchAll = !(std::strcmp(Pattern, "*")); + wildCardMatchAll = !(std::strcmp(pattern, "*")); } // iterate results do { - if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN && !(Flags & FILESYSTEM_FIND_HIDDEN_FILES)) + if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN && !(flags & FILESYSTEM_FIND_HIDDEN_FILES)) continue; if (wfd.cFileName[0] == L'.') @@ -1409,28 +1104,28 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - if (Flags & FILESYSTEM_FIND_RECURSIVE) + if (flags & FILESYSTEM_FIND_RECURSIVE) { // recurse into this directory - if (ParentPath != nullptr) + if (parent_path != nullptr) { - const std::string recurseDir = StringUtil::StdStringFromFormat("%s\\%s", ParentPath, Path); - nFiles += RecursiveFindFiles(OriginPath, recurseDir.c_str(), utf8_filename.c_str(), Pattern, Flags, pResults); + const std::string recurseDir = StringUtil::StdStringFromFormat("%s\\%s", parent_path, path); + nFiles += RecursiveFindFiles(origin_path, recurseDir.c_str(), utf8_filename.c_str(), pattern, flags, results); } else { - nFiles += RecursiveFindFiles(OriginPath, Path, utf8_filename.c_str(), Pattern, Flags, pResults); + nFiles += RecursiveFindFiles(origin_path, path, utf8_filename.c_str(), pattern, flags, results); } } - if (!(Flags & FILESYSTEM_FIND_FOLDERS)) + if (!(flags & FILESYSTEM_FIND_FOLDERS)) continue; outData.Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY; } else { - if (!(Flags & FILESYSTEM_FIND_FILES)) + if (!(flags & FILESYSTEM_FIND_FILES)) continue; } @@ -1440,84 +1135,125 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co // match the filename if (hasWildCards) { - if (!wildCardMatchAll && !StringUtil::WildcardMatch(utf8_filename.c_str(), Pattern)) + if (!wildCardMatchAll && !StringUtil::WildcardMatch(utf8_filename.c_str(), pattern)) continue; } else { - if (std::strcmp(utf8_filename.c_str(), Pattern) != 0) + if (std::strcmp(utf8_filename.c_str(), pattern) != 0) continue; } // add file to list // TODO string formatter, clean this mess.. - if (!(Flags & FILESYSTEM_FIND_RELATIVE_PATHS)) + if (!(flags & FILESYSTEM_FIND_RELATIVE_PATHS)) { - if (ParentPath != nullptr) + if (parent_path != nullptr) outData.FileName = - StringUtil::StdStringFromFormat("%s\\%s\\%s\\%s", OriginPath, ParentPath, Path, utf8_filename.c_str()); - else if (Path != nullptr) - outData.FileName = StringUtil::StdStringFromFormat("%s\\%s\\%s", OriginPath, Path, utf8_filename.c_str()); + StringUtil::StdStringFromFormat("%s\\%s\\%s\\%s", origin_path, parent_path, path, utf8_filename.c_str()); + else if (path != nullptr) + outData.FileName = StringUtil::StdStringFromFormat("%s\\%s\\%s", origin_path, path, utf8_filename.c_str()); else - outData.FileName = StringUtil::StdStringFromFormat("%s\\%s", OriginPath, utf8_filename.c_str()); + outData.FileName = StringUtil::StdStringFromFormat("%s\\%s", origin_path, utf8_filename.c_str()); } else { - if (ParentPath != nullptr) - outData.FileName = StringUtil::StdStringFromFormat("%s\\%s\\%s", ParentPath, Path, utf8_filename.c_str()); - else if (Path != nullptr) - outData.FileName = StringUtil::StdStringFromFormat("%s\\%s", Path, utf8_filename.c_str()); + if (parent_path != nullptr) + outData.FileName = StringUtil::StdStringFromFormat("%s\\%s\\%s", parent_path, path, utf8_filename.c_str()); + else if (path != nullptr) + outData.FileName = StringUtil::StdStringFromFormat("%s\\%s", path, utf8_filename.c_str()); else outData.FileName = utf8_filename; } - outData.ModificationTime.SetWindowsFileTime(&wfd.ftLastWriteTime); - outData.Size = (u64)wfd.nFileSizeHigh << 32 | (u64)wfd.nFileSizeLow; + outData.CreationTime = ConvertFileTimeToUnixTime(wfd.ftCreationTime); + outData.ModificationTime = ConvertFileTimeToUnixTime(wfd.ftLastWriteTime); + outData.Size = (static_cast(wfd.nFileSizeHigh) << 32) | static_cast(wfd.nFileSizeLow); nFiles++; - pResults->push_back(std::move(outData)); + results->push_back(std::move(outData)); } while (FindNextFileW(hFind, &wfd) == TRUE); FindClose(hFind); return nFiles; } -bool FileSystem::FindFiles(const char* Path, const char* Pattern, u32 Flags, FindResultsArray* pResults) +bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results) { // has a path - if (Path[0] == '\0') + if (path[0] == '\0') return false; // clear result array - if (!(Flags & FILESYSTEM_FIND_KEEP_ARRAY)) - pResults->clear(); + if (!(flags & FILESYSTEM_FIND_KEEP_ARRAY)) + results->clear(); // enter the recursive function - return (RecursiveFindFiles(Path, nullptr, nullptr, Pattern, Flags, pResults) > 0); + return (RecursiveFindFiles(path, nullptr, nullptr, pattern, flags, results) > 0); } -bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* pStatData) +static void TranslateStat64(struct stat* st, const struct _stat64& st64) +{ + static constexpr __int64 MAX_SIZE = static_cast<__int64>(std::numeric_limitsst_size)>::max()); + st->st_dev = st64.st_dev; + st->st_ino = st64.st_ino; + st->st_mode = st64.st_mode; + st->st_nlink = st64.st_nlink; + st->st_uid = st64.st_uid; + st->st_rdev = st64.st_rdev; + st->st_size = static_castst_size)>((st64.st_size > MAX_SIZE) ? MAX_SIZE : st64.st_size); + st->st_atime = static_cast(st64.st_atime); + st->st_mtime = static_cast(st64.st_mtime); + st->st_ctime = static_cast(st64.st_ctime); +} + +bool FileSystem::StatFile(const char* path, struct stat* st) { // has a path if (path[0] == '\0') return false; // convert to wide string - int len = static_cast(std::strlen(path)); - int wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, nullptr, 0); - if (wlen <= 0) + const std::wstring wpath(StringUtil::UTF8StringToWideString(path)); + if (wpath.empty()) return false; - wchar_t* wpath = static_cast(alloca(sizeof(wchar_t) * (wlen + 1))); - wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, wpath, wlen); - if (wlen <= 0) + struct _stat64 st64; + if (_wstati64(wpath.c_str(), &st64) != 0) return false; - wpath[wlen] = 0; + TranslateStat64(st, st64); + return true; +} + +bool FileSystem::StatFile(std::FILE* fp, struct stat* st) +{ + const int fd = _fileno(fp); + if (fd < 0) + return false; + + struct _stat64 st64; + if (_fstati64(fd, &st64) != 0) + return false; + + TranslateStat64(st, st64); + return true; +} + +bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd) +{ + // has a path + if (path[0] == '\0') + return false; + + // convert to wide string + const std::wstring wpath(StringUtil::UTF8StringToWideString(path)); + if (wpath.empty()) + return false; #ifndef _UWP // determine attributes for the path. if it's a directory, things have to be handled differently.. - DWORD fileAttributes = GetFileAttributesW(wpath); + DWORD fileAttributes = GetFileAttributesW(wpath.c_str()); if (fileAttributes == INVALID_FILE_ATTRIBUTES) return false; @@ -1525,12 +1261,12 @@ bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* pStatData) HANDLE hFile; if (fileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - hFile = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); } else { - hFile = CreateFileW(wpath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, + hFile = CreateFileW(wpath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, 0, nullptr); } @@ -1550,23 +1286,25 @@ bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* pStatData) CloseHandle(hFile); // fill in the stat data - pStatData->Attributes = TranslateWin32Attributes(bhfi.dwFileAttributes); - pStatData->ModificationTime.SetWindowsFileTime(&bhfi.ftLastWriteTime); - pStatData->Size = ((u64)bhfi.nFileSizeHigh) << 32 | (u64)bhfi.nFileSizeLow; + sd->Attributes = TranslateWin32Attributes(bhfi.dwFileAttributes); + sd->CreationTime = ConvertFileTimeToUnixTime(bhfi.ftCreationTime); + sd->ModificationTime = ConvertFileTimeToUnixTime(bhfi.ftLastWriteTime); + sd->Size = static_cast(((u64)bhfi.nFileSizeHigh) << 32 | (u64)bhfi.nFileSizeLow); return true; #else WIN32_FILE_ATTRIBUTE_DATA fad; - if (!GetFileAttributesExFromAppW(wpath, GetFileExInfoStandard, &fad)) + if (!GetFileAttributesExFromAppW(wpath.c_str(), GetFileExInfoStandard, &fad)) return false; - pStatData->Attributes = TranslateWin32Attributes(fad.dwFileAttributes); - pStatData->ModificationTime.SetWindowsFileTime(&fad.ftLastWriteTime); - pStatData->Size = ((u64)fad.nFileSizeHigh) << 32 | (u64)fad.nFileSizeLow; + sd->Attributes = TranslateWin32Attributes(fad.dwFileAttributes); + sd->CreationTime = ConvertFileTimeToUnixTime(fad.ftCreationTime); + sd->ModificationTime = ConvertFileTimeToUnixTime(fad.ftLastWriteTime); + sd->Size = static_cast(((u64)fad.nFileSizeHigh) << 32 | (u64)fad.nFileSizeLow); return true; #endif } -bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* pStatData) +bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd) { const int fd = _fileno(fp); if (fd < 0) @@ -1577,18 +1315,17 @@ bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* pStatData) return false; // parse attributes - pStatData->Attributes = 0; + sd->CreationTime = st.st_ctime; + sd->ModificationTime = st.st_mtime; + sd->Attributes = 0; if ((st.st_mode & _S_IFMT) == _S_IFDIR) - pStatData->Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY; - - // parse times - pStatData->ModificationTime.SetUnixTimestamp((Timestamp::UnixTimestampValue)st.st_mtime); + sd->Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY; // parse size if ((st.st_mode & _S_IFMT) == _S_IFREG) - pStatData->Size = static_cast(st.st_size); + sd->Size = st.st_size; else - pStatData->Size = 0; + sd->Size = 0; return true; } @@ -1600,20 +1337,12 @@ bool FileSystem::FileExists(const char* path) return false; // convert to wide string - int len = static_cast(std::strlen(path)); - int wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, nullptr, 0); - if (wlen <= 0) + const std::wstring wpath(StringUtil::UTF8StringToWideString(path)); + if (wpath.empty()) return false; - wchar_t* wpath = static_cast(alloca(sizeof(wchar_t) * (wlen + 1))); - wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, wpath, wlen); - if (wlen <= 0) - return false; - - wpath[wlen] = 0; - // determine attributes for the path. if it's a directory, things have to be handled differently.. - DWORD fileAttributes = WrapGetFileAttributes(wpath); + DWORD fileAttributes = WrapGetFileAttributes(wpath.c_str()); if (fileAttributes == INVALID_FILE_ATTRIBUTES) return false; @@ -1630,20 +1359,12 @@ bool FileSystem::DirectoryExists(const char* path) return false; // convert to wide string - int len = static_cast(std::strlen(path)); - int wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, nullptr, 0); - if (wlen <= 0) + const std::wstring wpath(StringUtil::UTF8StringToWideString(path)); + if (wpath.empty()) return false; - wchar_t* wpath = static_cast(alloca(sizeof(wchar_t) * (wlen + 1))); - wlen = MultiByteToWideChar(CP_UTF8, 0, path, len, wpath, wlen); - if (wlen <= 0) - return false; - - wpath[wlen] = 0; - // determine attributes for the path. if it's a directory, things have to be handled differently.. - DWORD fileAttributes = WrapGetFileAttributes(wpath); + DWORD fileAttributes = WrapGetFileAttributes(wpath.c_str()); if (fileAttributes == INVALID_FILE_ATTRIBUTES) return false; @@ -1653,12 +1374,43 @@ bool FileSystem::DirectoryExists(const char* path) return false; } +bool FileSystem::DirectoryIsEmpty(const char* path) +{ + std::wstring wpath(StringUtil::UTF8StringToWideString(path)); + wpath += L"\\*"; + + WIN32_FIND_DATAW wfd; +#ifndef _UWP + HANDLE hFind = FindFirstFileW(wpath.c_str(), &wfd); +#else + HANDLE hFind = FindFirstFileExFromAppW(wpath.c_str(), FindExInfoBasic, &wfd, FindExSearchNameMatch, nullptr, 0); +#endif + + if (hFind == INVALID_HANDLE_VALUE) + return true; + + do + { + if (wfd.cFileName[0] == L'.') + { + if (wfd.cFileName[1] == L'\0' || (wfd.cFileName[1] == L'.' && wfd.cFileName[2] == L'\0')) + continue; + } + + FindClose(hFind); + return false; + } while (FindNextFileW(hFind, &wfd)); + + FindClose(hFind); + return true; +} + bool FileSystem::CreateDirectory(const char* Path, bool Recursive) { - std::wstring wpath(StringUtil::UTF8StringToWideString(Path)); + const std::wstring wpath(StringUtil::UTF8StringToWideString(Path)); // has a path - if (wpath[0] == L'\0') + if (wpath.empty()) return false; // try just flat-out, might work if there's no other segments that have to be made @@ -1670,6 +1422,9 @@ bool FileSystem::CreateDirectory(const char* Path, bool Recursive) return true; #endif + if (!Recursive) + return false; + // check error DWORD lastError = GetLastError(); if (lastError == ERROR_ALREADY_EXISTS) @@ -1684,21 +1439,20 @@ bool FileSystem::CreateDirectory(const char* Path, bool Recursive) else if (lastError == ERROR_PATH_NOT_FOUND) { // part of the path does not exist, so we'll create the parent folders, then - // the full path again. allocate another buffer with the same length - u32 pathLength = static_cast(wpath.size()); - wchar_t* tempStr = (wchar_t*)alloca(sizeof(wchar_t) * (pathLength + 1)); + // the full path again. + const size_t pathLength = wpath.size(); + std::wstring tempPath; + tempPath.reserve(pathLength); // create directories along the path - for (u32 i = 0; i < pathLength; i++) + for (size_t i = 0; i < pathLength; i++) { if (wpath[i] == L'\\' || wpath[i] == L'/') { - tempStr[i] = L'\0'; - #ifndef _UWP - const BOOL result = CreateDirectoryW(tempStr, nullptr); + const BOOL result = CreateDirectoryW(tempPath.c_str(), nullptr); #else - const BOOL result = CreateDirectoryFromAppW(tempStr, nullptr); + const BOOL result = CreateDirectoryFromAppW(tempPath.c_str(), nullptr); #endif if (!result) { @@ -1706,9 +1460,14 @@ bool FileSystem::CreateDirectory(const char* Path, bool Recursive) if (lastError != ERROR_ALREADY_EXISTS) // fine, continue to next path segment return false; } - } - tempStr[i] = wpath[i]; + // replace / with \. + tempPath.push_back('\\'); + } + else + { + tempPath.push_back(wpath[i]); + } } // re-create the end if it's not a separator, check / as well because windows can interpret them @@ -1737,12 +1496,12 @@ bool FileSystem::CreateDirectory(const char* Path, bool Recursive) } } -bool FileSystem::DeleteFile(const char* Path) +bool FileSystem::DeleteFile(const char* path) { - if (Path[0] == '\0') + if (path[0] == '\0') return false; - const std::wstring wpath(StringUtil::UTF8StringToWideString(Path)); + const std::wstring wpath(StringUtil::UTF8StringToWideString(path)); const DWORD fileAttributes = WrapGetFileAttributes(wpath.c_str()); if (fileAttributes == INVALID_FILE_ATTRIBUTES || fileAttributes & FILE_ATTRIBUTE_DIRECTORY) return false; @@ -1754,15 +1513,15 @@ bool FileSystem::DeleteFile(const char* Path) #endif } -bool FileSystem::RenamePath(const char* OldPath, const char* NewPath) +bool FileSystem::RenamePath(const char* old_path, const char* new_path) { - const std::wstring old_wpath(StringUtil::UTF8StringToWideString(OldPath)); - const std::wstring new_wpath(StringUtil::UTF8StringToWideString(NewPath)); + const std::wstring old_wpath(StringUtil::UTF8StringToWideString(old_path)); + const std::wstring new_wpath(StringUtil::UTF8StringToWideString(new_path)); #ifndef _UWP if (!MoveFileExW(old_wpath.c_str(), new_wpath.c_str(), MOVEFILE_REPLACE_EXISTING)) { - Log_ErrorPrintf("MoveFileEx('%s', '%s') failed: %08X", OldPath, NewPath, GetLastError()); + Log_ErrorPrintf("MoveFileEx('%s', '%s') failed: %08X", old_path, new_path, GetLastError()); return false; } #else @@ -1778,7 +1537,7 @@ bool FileSystem::RenamePath(const char* OldPath, const char* NewPath) if (!MoveFileFromAppW(old_wpath.c_str(), new_wpath.c_str())) { - Log_ErrorPrintf("MoveFileFromAppW('%s', '%s') failed: %08X", OldPath, NewPath, GetLastError()); + Log_ErrorPrintf("MoveFileFromAppW('%s', '%s') failed: %08X", old_path, new_path, GetLastError()); return false; } #endif @@ -1786,100 +1545,13 @@ bool FileSystem::RenamePath(const char* OldPath, const char* NewPath) return true; } -static bool RecursiveDeleteDirectory(const std::wstring& wpath, bool Recursive) +bool FileSystem::DeleteDirectory(const char* path) { - // ensure it exists - const DWORD fileAttributes = WrapGetFileAttributes(wpath.c_str()); - if (fileAttributes == INVALID_FILE_ATTRIBUTES || fileAttributes & FILE_ATTRIBUTE_DIRECTORY) - return false; - - // non-recursive case just try removing the directory - if (!Recursive) - { -#ifndef _UWP - return (RemoveDirectoryW(wpath.c_str()) == TRUE); -#else - return (RemoveDirectoryFromAppW(wpath.c_str()) == TRUE); -#endif - } - - // doing a recursive delete - std::wstring fileName = wpath; - fileName += L"\\*"; - - // is there any files? - WIN32_FIND_DATAW findData; -#ifndef _UWP - HANDLE hFind = FindFirstFileW(fileName.c_str(), &findData); -#else - HANDLE hFind = - FindFirstFileExFromAppW(fileName.c_str(), FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, 0); -#endif - if (hFind == INVALID_HANDLE_VALUE) - return false; - - // search through files - do - { - // skip . and .. - if (findData.cFileName[0] == L'.') - { - if ((findData.cFileName[1] == L'\0') || (findData.cFileName[1] == L'.' && findData.cFileName[2] == L'\0')) - { - continue; - } - } - - // found a directory? - fileName = wpath; - fileName += L"\\"; - fileName += findData.cFileName; - if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - // recurse into that - if (!RecursiveDeleteDirectory(fileName, true)) - { - FindClose(hFind); - return false; - } - } - else - { - // found a file, so delete it -#ifndef _UWP - const BOOL result = DeleteFileW(fileName.c_str()); -#else - const BOOL result = DeleteFileFromAppW(fileName.c_str()); -#endif - if (!result) - { - FindClose(hFind); - return false; - } - } - } while (FindNextFileW(hFind, &findData)); - FindClose(hFind); - - // nuke the directory itself -#ifndef _UWP - const BOOL result = RemoveDirectoryW(wpath.c_str()); -#else - const BOOL result = RemoveDirectoryFromAppW(wpath.c_str()); -#endif - if (!result) - return false; - - // done - return true; + const std::wstring wpath(StringUtil::UTF8StringToWideString(path)); + return RemoveDirectoryW(wpath.c_str()); } -bool FileSystem::DeleteDirectory(const char* Path, bool Recursive) -{ - const std::wstring wpath(StringUtil::UTF8StringToWideString(Path)); - return RecursiveDeleteDirectory(wpath, Recursive); -} - -std::string GetProgramPath() +std::string FileSystem::GetProgramPath() { std::wstring buffer; buffer.resize(MAX_PATH); @@ -1904,12 +1576,10 @@ std::string GetProgramPath() break; } - std::string utf8_path(StringUtil::WideStringToUTF8String(buffer)); - CanonicalizePath(utf8_path); - return utf8_path; + return StringUtil::WideStringToUTF8String(buffer); } -std::string GetWorkingDirectory() +std::string FileSystem::GetWorkingDirectory() { DWORD required_size = GetCurrentDirectoryW(0, nullptr); if (!required_size) @@ -1924,22 +1594,52 @@ std::string GetWorkingDirectory() return StringUtil::WideStringToUTF8String(buffer); } -bool SetWorkingDirectory(const char* path) +bool FileSystem::SetWorkingDirectory(const char* path) { const std::wstring wpath(StringUtil::UTF8StringToWideString(path)); return (SetCurrentDirectoryW(wpath.c_str()) == TRUE); } -#else - -std::unique_ptr CreateChangeNotifier(const char* path, bool recursiveWatch) +bool FileSystem::SetPathCompression(const char* path, bool enable) { - Log_ErrorPrintf("FileSystem::CreateChangeNotifier(%s) not implemented", path); - return nullptr; + const std::wstring wpath(StringUtil::UTF8StringToWideString(path)); + const DWORD attrs = GetFileAttributesW(wpath.c_str()); + if (attrs == INVALID_FILE_ATTRIBUTES) + return false; + + const bool isCompressed = (attrs & FILE_ATTRIBUTE_COMPRESSED) != 0; + if (enable == isCompressed) + { + // already compressed/not compressed + return true; + } + + const bool isFile = !(attrs & FILE_ATTRIBUTE_DIRECTORY); + const DWORD flags = isFile ? FILE_ATTRIBUTE_NORMAL : (FILE_FLAG_BACKUP_SEMANTICS | FILE_ATTRIBUTE_DIRECTORY); + +#ifndef _UWP + const HANDLE handle = CreateFileW(wpath.c_str(), FILE_GENERIC_WRITE | FILE_GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, flags, nullptr); +#else + const HANDLE handle = CreateFileFromAppW(wpath.c_str(), FILE_GENERIC_WRITE | FILE_GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, flags, nullptr); +#endif + if (handle == INVALID_HANDLE_VALUE) + return false; + + DWORD bytesReturned = 0; + DWORD compressMode = enable ? COMPRESSION_FORMAT_DEFAULT : COMPRESSION_FORMAT_NONE; + + bool result = DeviceIoControl(handle, FSCTL_SET_COMPRESSION, &compressMode, 2, nullptr, 0, &bytesReturned, nullptr); + + CloseHandle(handle); + return result; } +#else + static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, const char* Path, const char* Pattern, - u32 Flags, FindResultsArray* pResults) + u32 Flags, FileSystem::FindResultsArray* pResults) { std::string tempStr; if (Path) @@ -1969,13 +1669,9 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co } // iterate results - PathString full_path; struct dirent* pDirEnt; while ((pDirEnt = readdir(pDir)) != nullptr) { - // if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN && !(Flags & FILESYSTEM_FIND_HIDDEN_FILES)) - // continue; - // if (pDirEnt->d_name[0] == '.') { if (pDirEnt->d_name[1] == '\0' || (pDirEnt->d_name[1] == '.' && pDirEnt->d_name[2] == '\0')) @@ -1985,24 +1681,25 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co continue; } + std::string full_path; if (ParentPath != nullptr) - full_path.Format("%s/%s/%s/%s", OriginPath, ParentPath, Path, pDirEnt->d_name); + full_path = StringUtil::StdStringFromFormat("%s/%s/%s/%s", OriginPath, ParentPath, Path, pDirEnt->d_name); else if (Path != nullptr) - full_path.Format("%s/%s/%s", OriginPath, Path, pDirEnt->d_name); + full_path = StringUtil::StdStringFromFormat("%s/%s/%s", OriginPath, Path, pDirEnt->d_name); else - full_path.Format("%s/%s", OriginPath, pDirEnt->d_name); + full_path = StringUtil::StdStringFromFormat("%s/%s", OriginPath, pDirEnt->d_name); FILESYSTEM_FIND_DATA outData; outData.Attributes = 0; #if defined(__HAIKU__) || defined(__APPLE__) || defined(__FreeBSD__) struct stat sDir; - if (stat(full_path, &sDir) < 0) + if (stat(full_path.c_str(), &sDir) < 0) continue; #else struct stat64 sDir; - if (stat64(full_path, &sDir) < 0) + if (stat64(full_path.c_str(), &sDir) < 0) continue; #endif @@ -2034,7 +1731,8 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co } outData.Size = static_cast(sDir.st_size); - outData.ModificationTime.SetUnixTimestamp(static_cast(sDir.st_mtime)); + outData.CreationTime = sDir.st_ctime; + outData.ModificationTime = sDir.st_mtime; // match the filename if (hasWildCards) @@ -2052,14 +1750,14 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co // TODO string formatter, clean this mess.. if (!(Flags & FILESYSTEM_FIND_RELATIVE_PATHS)) { - outData.FileName = std::string(full_path.GetCharArray()); + outData.FileName = std::move(full_path); } else { if (ParentPath != nullptr) - outData.FileName = StringUtil::StdStringFromFormat("%s\\%s\\%s", ParentPath, Path, pDirEnt->d_name); + outData.FileName = StringUtil::StdStringFromFormat("%s/%s/%s", ParentPath, Path, pDirEnt->d_name); else if (Path != nullptr) - outData.FileName = StringUtil::StdStringFromFormat("%s\\%s", Path, pDirEnt->d_name); + outData.FileName = StringUtil::StdStringFromFormat("%s/%s", Path, pDirEnt->d_name); else outData.FileName = pDirEnt->d_name; } @@ -2072,7 +1770,7 @@ static u32 RecursiveFindFiles(const char* OriginPath, const char* ParentPath, co return nFiles; } -bool FindFiles(const char* Path, const char* Pattern, u32 Flags, FindResultsArray* pResults) +bool FileSystem::FindFiles(const char* Path, const char* Pattern, u32 Flags, FindResultsArray* pResults) { // has a path if (Path[0] == '\0') @@ -2082,57 +1780,60 @@ bool FindFiles(const char* Path, const char* Pattern, u32 Flags, FindResultsArra if (!(Flags & FILESYSTEM_FIND_KEEP_ARRAY)) pResults->clear(); -#ifdef __ANDROID__ - if (IsUriPath(Path) && UriHelpersAreAvailable()) - return FindUriFiles(Path, Pattern, Flags, pResults); -#endif - // enter the recursive function return (RecursiveFindFiles(Path, nullptr, nullptr, Pattern, Flags, pResults) > 0); } -bool StatFile(const char* Path, FILESYSTEM_STAT_DATA* pStatData) +bool FileSystem::StatFile(const char* path, struct stat* st) { - // has a path - if (Path[0] == '\0') + return stat(path, st) == 0; +} + +bool FileSystem::StatFile(std::FILE* fp, struct stat* st) +{ + const int fd = fileno(fp); + if (fd < 0) return false; -#ifdef __ANDROID__ - if (IsUriPath(Path) && UriHelpersAreAvailable()) - return StatUriFile(Path, pStatData); -#endif + return fstat(fd, st) == 0; +} + +bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd) +{ + // has a path + if (path[0] == '\0') + return false; // stat file #if defined(__HAIKU__) || defined(__APPLE__) || defined(__FreeBSD__) struct stat sysStatData; - if (stat(Path, &sysStatData) < 0) + if (stat(path, &sysStatData) < 0) #else struct stat64 sysStatData; - if (stat64(Path, &sysStatData) < 0) + if (stat64(path, &sysStatData) < 0) #endif return false; // parse attributes - pStatData->Attributes = 0; + sd->CreationTime = sysStatData.st_ctime; + sd->ModificationTime = sysStatData.st_mtime; + sd->Attributes = 0; if (S_ISDIR(sysStatData.st_mode)) - pStatData->Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY; - - // parse times - pStatData->ModificationTime.SetUnixTimestamp((Timestamp::UnixTimestampValue)sysStatData.st_mtime); + sd->Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY; // parse size if (S_ISREG(sysStatData.st_mode)) - pStatData->Size = static_cast(sysStatData.st_size); + sd->Size = sysStatData.st_size; else - pStatData->Size = 0; + sd->Size = 0; // ok return true; } -bool StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* pStatData) +bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd) { - int fd = fileno(fp); + const int fd = fileno(fp); if (fd < 0) return false; @@ -2147,44 +1848,35 @@ bool StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* pStatData) return false; // parse attributes - pStatData->Attributes = 0; + sd->CreationTime = sysStatData.st_ctime; + sd->ModificationTime = sysStatData.st_mtime; + sd->Attributes = 0; if (S_ISDIR(sysStatData.st_mode)) - pStatData->Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY; - - // parse times - pStatData->ModificationTime.SetUnixTimestamp((Timestamp::UnixTimestampValue)sysStatData.st_mtime); + sd->Attributes |= FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY; // parse size if (S_ISREG(sysStatData.st_mode)) - pStatData->Size = static_cast(sysStatData.st_size); + sd->Size = sysStatData.st_size; else - pStatData->Size = 0; + sd->Size = 0; // ok return true; } -bool FileExists(const char* Path) +bool FileSystem::FileExists(const char* path) { // has a path - if (Path[0] == '\0') + if (path[0] == '\0') return false; -#ifdef __ANDROID__ - if (IsUriPath(Path) && UriHelpersAreAvailable()) - { - FILESYSTEM_STAT_DATA sd; - return (StatUriFile(Path, &sd) && (sd.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) == 0); - } -#endif - - // stat file + // stat file #if defined(__HAIKU__) || defined(__APPLE__) || defined(__FreeBSD__) struct stat sysStatData; - if (stat(Path, &sysStatData) < 0) + if (stat(path, &sysStatData) < 0) #else struct stat64 sysStatData; - if (stat64(Path, &sysStatData) < 0) + if (stat64(path, &sysStatData) < 0) #endif return false; @@ -2194,27 +1886,19 @@ bool FileExists(const char* Path) return true; } -bool DirectoryExists(const char* Path) +bool FileSystem::DirectoryExists(const char* path) { // has a path - if (Path[0] == '\0') + if (path[0] == '\0') return false; -#ifdef __ANDROID__ - if (IsUriPath(Path) && UriHelpersAreAvailable()) - { - FILESYSTEM_STAT_DATA sd; - return (StatUriFile(Path, &sd) && (sd.Attributes & FILESYSTEM_FILE_ATTRIBUTE_DIRECTORY) != 0); - } -#endif - - // stat file + // stat file #if defined(__HAIKU__) || defined(__APPLE__) || defined(__FreeBSD__) struct stat sysStatData; - if (stat(Path, &sysStatData) < 0) + if (stat(path, &sysStatData) < 0) #else struct stat64 sysStatData; - if (stat64(Path, &sysStatData) < 0) + if (stat64(path, &sysStatData) < 0) #endif return false; @@ -2224,26 +1908,51 @@ bool DirectoryExists(const char* Path) return false; } -bool CreateDirectory(const char* Path, bool Recursive) +bool FileSystem::DirectoryIsEmpty(const char* path) { - u32 i; - int lastError; + DIR* pDir = opendir(path); + if (pDir == nullptr) + return true; + // iterate results + struct dirent* pDirEnt; + while ((pDirEnt = readdir(pDir)) != nullptr) + { + if (pDirEnt->d_name[0] == '.') + { + if (pDirEnt->d_name[1] == '\0' || (pDirEnt->d_name[1] == '.' && pDirEnt->d_name[2] == '\0')) + continue; + } + + closedir(pDir); + return false; + } + + closedir(pDir); + return true; +} + +bool FileSystem::CreateDirectory(const char* path, bool recursive) +{ // has a path - if (Path[0] == '\0') + const size_t pathLength = std::strlen(path); + if (pathLength == 0) return false; // try just flat-out, might work if there's no other segments that have to be made - if (mkdir(Path, 0777) == 0) + if (mkdir(path, 0777) == 0) return true; + if (!recursive) + return false; + // check error - lastError = errno; + int lastError = errno; if (lastError == EEXIST) { // check the attributes struct stat sysStatData; - if (stat(Path, &sysStatData) == 0 && S_ISDIR(sysStatData.st_mode)) + if (stat(path, &sysStatData) == 0 && S_ISDIR(sysStatData.st_mode)) return true; else return false; @@ -2251,17 +1960,16 @@ bool CreateDirectory(const char* Path, bool Recursive) else if (lastError == ENOENT) { // part of the path does not exist, so we'll create the parent folders, then - // the full path again. allocate another buffer with the same length - u32 pathLength = static_cast(std::strlen(Path)); - char* tempStr = (char*)alloca(pathLength + 1); + // the full path again. + std::string tempPath; + tempPath.reserve(pathLength); // create directories along the path - for (i = 0; i < pathLength; i++) + for (size_t i = 0; i < pathLength; i++) { - if (Path[i] == '/') + if (i > 0 && path[i] == '/') { - tempStr[i] = '\0'; - if (mkdir(tempStr, 0777) < 0) + if (mkdir(tempPath.c_str(), 0777) < 0) { lastError = errno; if (lastError != EEXIST) // fine, continue to next path segment @@ -2269,13 +1977,13 @@ bool CreateDirectory(const char* Path, bool Recursive) } } - tempStr[i] = Path[i]; + tempPath.push_back(path[i]); } // re-create the end if it's not a separator, check / as well because windows can interpret them - if (Path[pathLength - 1] != '/') + if (path[pathLength - 1] != '/') { - if (mkdir(Path, 0777) < 0) + if (mkdir(path, 0777) < 0) { lastError = errno; if (lastError != EEXIST) @@ -2293,39 +2001,45 @@ bool CreateDirectory(const char* Path, bool Recursive) } } -bool DeleteFile(const char* Path) +bool FileSystem::DeleteFile(const char* path) { - if (Path[0] == '\0') + if (path[0] == '\0') return false; struct stat sysStatData; - if (stat(Path, &sysStatData) != 0 || S_ISDIR(sysStatData.st_mode)) + if (stat(path, &sysStatData) != 0 || S_ISDIR(sysStatData.st_mode)) return false; - return (unlink(Path) == 0); + return (unlink(path) == 0); } -bool RenamePath(const char* OldPath, const char* NewPath) +bool FileSystem::RenamePath(const char* old_path, const char* new_path) { - if (OldPath[0] == '\0' || NewPath[0] == '\0') + if (old_path[0] == '\0' || new_path[0] == '\0') return false; - if (rename(OldPath, NewPath) != 0) + if (rename(old_path, new_path) != 0) { - Log_ErrorPrintf("rename('%s', '%s') failed: %d", OldPath, NewPath, errno); + Log_ErrorPrintf("rename('%s', '%s') failed: %d", old_path, new_path, errno); return false; } return true; } -bool DeleteDirectory(const char* Path, bool Recursive) +bool FileSystem::DeleteDirectory(const char* path) { - Log_ErrorPrintf("FileSystem::DeleteDirectory(%s) not implemented", Path); - return false; + if (path[0] == '\0') + return false; + + struct stat sysStatData; + if (stat(path, &sysStatData) != 0 || !S_ISDIR(sysStatData.st_mode)) + return false; + + return (unlink(path) == 0); } -std::string GetProgramPath() +std::string FileSystem::GetProgramPath() { #if defined(__linux__) static const char* exeFileName = "/proc/self/exe"; @@ -2384,7 +2098,7 @@ std::string GetProgramPath() int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1}; char buffer[PATH_MAX]; size_t cb = sizeof(buffer) - 1; - int res = sysctl(mib, countof(mib), buffer, &cb, nullptr, 0); + int res = sysctl(mib, std::size(mib), buffer, &cb, nullptr, 0); if (res != 0) return {}; @@ -2395,32 +2109,30 @@ std::string GetProgramPath() #endif } -std::string GetWorkingDirectory() +std::string FileSystem::GetWorkingDirectory() { std::string buffer; buffer.resize(PATH_MAX); while (!getcwd(buffer.data(), buffer.size())) { if (errno != ERANGE) - { - buffer.clear(); - break; - } + return {}; buffer.resize(buffer.size() * 2); } - if (!buffer.empty()) - buffer.resize(std::strlen(buffer.c_str())); - + buffer.resize(std::strlen(buffer.c_str())); // Remove excess nulls return buffer; } -bool SetWorkingDirectory(const char* path) +bool FileSystem::SetWorkingDirectory(const char* path) { return (chdir(path) == 0); } -#endif +bool FileSystem::SetPathCompression(const char* path, bool enable) +{ + return false; +} -} // namespace FileSystem +#endif diff --git a/src/common/file_system.h b/src/common/file_system.h index ada29c8a8..2bdc770e8 100644 --- a/src/common/file_system.h +++ b/src/common/file_system.h @@ -1,13 +1,12 @@ #pragma once -#include "timestamp.h" #include "types.h" #include #include #include #include #include -#include #include +#include #ifdef _WIN32 #define FS_OSPATH_SEPARATOR_CHARACTER '\\' @@ -36,177 +35,104 @@ enum FILESYSTEM_FIND_FLAGS struct FILESYSTEM_STAT_DATA { + std::time_t CreationTime; // actually inode change time on linux + std::time_t ModificationTime; + s64 Size; u32 Attributes; - Timestamp ModificationTime; - u64 Size; }; struct FILESYSTEM_FIND_DATA { + std::time_t CreationTime; // actually inode change time on linux + std::time_t ModificationTime; std::string FileName; - Timestamp ModificationTime; + s64 Size; u32 Attributes; - u64 Size; -}; - -struct FILESYSTEM_CHANGE_NOTIFY_DATA -{ - String DirectoryPath; - bool RecursiveWatch; - - void* pSystemData; }; namespace FileSystem { - using FindResultsArray = std::vector; -#ifdef __ANDROID__ -/// Sets the instance for the FileHelpers Java class, used for storage access framework -/// file access on Android. -void SetAndroidFileHelper(void* jvm, void* env, void* object); -#endif - -class ChangeNotifier -{ -public: - enum ChangeEvent - { - ChangeEvent_FileAdded = (1 << 0), - ChangeEvent_FileRemoved = (1 << 1), - ChangeEvent_FileModified = (1 << 2), - ChangeEvent_RenamedOldName = (1 << 3), - ChangeEvent_RenamedNewName = (1 << 4), - }; - - struct ChangeInfo - { - const char* Path; - u32 Event; - }; - -public: - virtual ~ChangeNotifier(); - - const String& GetDirectoryPath() const { return m_directoryPath; } - const bool GetRecursiveWatch() const { return m_recursiveWatch; } - - typedef void (*EnumerateChangesCallback)(const ChangeInfo* pChangeInfo, void* pUserData); - virtual void EnumerateChanges(EnumerateChangesCallback callback, void* pUserData) = 0; - -private: - template - static void EnumerateChangesTrampoline(const ChangeInfo* pChangeInfo, void* pUserData) - { - CALLBACK_TYPE* pRealCallback = reinterpret_cast(pUserData); - (*pRealCallback)(pChangeInfo); - } - -public: - template - void EnumerateChanges(CALLBACK_TYPE callback) - { - CALLBACK_TYPE* pCallback = &callback; - EnumerateChanges(&ChangeNotifier::EnumerateChangesTrampoline, reinterpret_cast(pCallback)); - } - -protected: - ChangeNotifier(const String& directoryPath, bool recursiveWatch); - - String m_directoryPath; - bool m_recursiveWatch; -}; - -// create a change notifier -std::unique_ptr CreateChangeNotifier(const char* path, bool recursiveWatch); - -// canonicalize a path string (i.e. replace .. with actual folder name, etc), if OS path is used, on windows, the -// separators will be \, otherwise / -void CanonicalizePath(char* Destination, u32 cbDestination, const char* Path, bool OSPath = true); -void CanonicalizePath(String& Destination, const char* Path, bool OSPath = true); -void CanonicalizePath(String& Destination, bool OSPath = true); -void CanonicalizePath(std::string& path, bool OSPath = true); - -// translates the specified path into a string compatible with the hosting OS -void BuildOSPath(char* Destination, u32 cbDestination, const char* Path); -void BuildOSPath(String& Destination, const char* Path); -void BuildOSPath(String& Destination); - -// builds a path relative to the specified file -std::string BuildRelativePath(const std::string_view& filename, const std::string_view& new_filename); - -// sanitizes a filename for use in a filesystem. -void SanitizeFileName(char* Destination, u32 cbDestination, const char* FileName, bool StripSlashes = true); -void SanitizeFileName(String& Destination, const char* FileName, bool StripSlashes = true); -void SanitizeFileName(String& Destination, bool StripSlashes = true); -void SanitizeFileName(std::string& Destination, bool StripSlashes = true); - -/// Returns true if the specified path is an absolute path (C:\Path on Windows or /path on Unix). -bool IsAbsolutePath(const std::string_view& path); - -/// Removes the extension of a filename. -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. +/// Returns the display name of a filename. Usually this is the same as the path. std::string GetDisplayNameFromPath(const std::string_view& path); -/// Returns the directory component of a filename. -std::string_view GetPathDirectory(const std::string_view& path); - -/// Returns the filename component of a filename. -std::string_view GetFileNameFromPath(const std::string_view& path); - -/// Returns the file title (less the extension and path) from a filename. -std::string_view GetFileTitleFromPath(const std::string_view& path); - /// Returns a list of "root directories" (i.e. root/home directories on Linux, drive letters on Windows). std::vector GetRootDirectoryList(); -// search for files -bool FindFiles(const char* Path, const char* Pattern, u32 Flags, FindResultsArray* pResults); +/// Search for files +bool FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results); -// stat file -bool StatFile(const char* Path, FILESYSTEM_STAT_DATA* pStatData); +/// Stat file +bool StatFile(const char* path, struct stat* st); +bool StatFile(std::FILE* fp, struct stat* st); +bool StatFile(const char* path, FILESYSTEM_STAT_DATA* pStatData); bool StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* pStatData); +s64 GetPathFileSize(const char* path); -// file exists? -bool FileExists(const char* Path); +/// File exists? +bool FileExists(const char* path); -// directory exists? -bool DirectoryExists(const char* Path); +/// Directory exists? +bool DirectoryExists(const char* path); -// delete file -bool DeleteFile(const char* Path); +/// Directory does not contain any files? +bool DirectoryIsEmpty(const char* path); -// rename file +/// Delete file +bool DeleteFile(const char* path); + +/// Rename file bool RenamePath(const char* OldPath, const char* NewPath); +/// open files using ManagedCFilePtr = std::unique_ptr; ManagedCFilePtr OpenManagedCFile(const char* filename, const char* mode); std::FILE* OpenCFile(const char* filename, const char* mode); int FSeek64(std::FILE* fp, s64 offset, int whence); s64 FTell64(std::FILE* fp); +s64 FSize64(std::FILE* fp); + +int OpenFDFile(const char* filename, int flags, int mode); + +/// Sharing modes for OpenSharedCFile(). +enum class FileShareMode +{ + DenyReadWrite, /// Exclusive access. + DenyWrite, /// Other processes can read from this file. + DenyRead, /// Other processes can write to this file. + DenyNone, /// Other processes can read and write to this file. +}; + +/// Opens a file in shareable mode (where other processes can access it concurrently). +/// Only has an effect on Windows systems. +ManagedCFilePtr OpenManagedSharedCFile(const char* filename, const char* mode, FileShareMode share_mode); +std::FILE* OpenSharedCFile(const char* filename, const char* mode, FileShareMode share_mode); std::optional> ReadBinaryFile(const char* filename); std::optional> ReadBinaryFile(std::FILE* fp); std::optional ReadFileToString(const char* filename); std::optional ReadFileToString(std::FILE* fp); bool WriteBinaryFile(const char* filename, const void* data, size_t data_length); -bool WriteFileToString(const char* filename, const std::string_view& sv); +bool WriteStringToFile(const char* filename, const std::string_view& sv); -// creates a directory in the local filesystem -// if the directory already exists, the return value will be true. -// if Recursive is specified, all parent directories will be created -// if they do not exist. -bool CreateDirectory(const char* Path, bool Recursive); +/// creates a directory in the local filesystem +/// if the directory already exists, the return value will be true. +/// if Recursive is specified, all parent directories will be created +/// if they do not exist. +bool CreateDirectory(const char* path, bool recursive); -// deletes a directory in the local filesystem -// if the directory has files, unless the recursive flag is set, it will fail -bool DeleteDirectory(const char* Path, bool Recursive); +/// Creates a directory if it doesn't already exist. +/// Returns false if it does not exist and creation failed. +bool EnsureDirectoryExists(const char* path, bool recursive); + +/// Removes a directory. +bool DeleteDirectory(const char* path); + +/// Recursively removes a directory and all subdirectories/files. +bool RecursiveDeleteDirectory(const char* path); + +/// Copies one file to another, optionally replacing it if it already exists. +bool CopyFilePath(const char* source, const char* destination, bool replace); /// Returns the path to the current executable. std::string GetProgramPath(); @@ -217,4 +143,9 @@ std::string GetWorkingDirectory(); /// Sets the current working directory. Returns true if successful. bool SetWorkingDirectory(const char* path); +/// Enables/disables NTFS compression on a file or directory. +/// Does not apply the compression flag recursively if called for a directory. +/// Does nothing and returns false on non-Windows platforms. +bool SetPathCompression(const char* path, bool enable); + }; // namespace FileSystem diff --git a/src/common/gl/context.cpp b/src/common/gl/context.cpp index 8f7816c75..47f629584 100644 --- a/src/common/gl/context.cpp +++ b/src/common/gl/context.cpp @@ -1,6 +1,6 @@ #include "context.h" #include "../log.h" -#include "glad.h" +#include "loader.h" #include #ifdef __APPLE__ #include diff --git a/src/common/gl/context_agl.h b/src/common/gl/context_agl.h index 25d69fe66..459bf2fd9 100644 --- a/src/common/gl/context_agl.h +++ b/src/common/gl/context_agl.h @@ -1,6 +1,6 @@ #pragma once #include "context.h" -#include +#include "loader.h" #if defined(__APPLE__) && defined(__OBJC__) #import diff --git a/src/common/gl/context_agl.mm b/src/common/gl/context_agl.mm index 1af1a0854..95632f9f9 100644 --- a/src/common/gl/context_agl.mm +++ b/src/common/gl/context_agl.mm @@ -1,7 +1,7 @@ #include "context_agl.h" #include "../assert.h" #include "../log.h" -#include "glad.h" +#include "loader.h" #include Log_SetChannel(GL::ContextAGL); diff --git a/src/common/gl/context_wgl.cpp b/src/common/gl/context_wgl.cpp index c24e37027..9fc8599cd 100644 --- a/src/common/gl/context_wgl.cpp +++ b/src/common/gl/context_wgl.cpp @@ -1,11 +1,11 @@ #include "context_wgl.h" #include "../assert.h" #include "../log.h" -#include "glad.h" -#include "glad_wgl.h" +#include "loader.h" Log_SetChannel(GL::ContextWGL); // TODO: get rid of this +#include "glad_wgl.h" #pragma comment(lib, "opengl32.lib") static void* GetProcAddressCallback(const char* name) diff --git a/src/common/gl/context_wgl.h b/src/common/gl/context_wgl.h index 6303235e2..bc8a83ae3 100644 --- a/src/common/gl/context_wgl.h +++ b/src/common/gl/context_wgl.h @@ -1,7 +1,7 @@ #pragma once #include "../windows_headers.h" #include "context.h" -#include +#include "loader.h" namespace GL { diff --git a/src/common/gl/loader.h b/src/common/gl/loader.h new file mode 100644 index 000000000..f355bbd41 --- /dev/null +++ b/src/common/gl/loader.h @@ -0,0 +1,8 @@ +#pragma once + +// Fix glad.h including windows.h +#ifdef _WIN32 +#include "../windows_headers.h" +#endif + +#include "glad.h" \ No newline at end of file diff --git a/src/common/gl/program.h b/src/common/gl/program.h index d877a4622..4ee9cf651 100644 --- a/src/common/gl/program.h +++ b/src/common/gl/program.h @@ -1,6 +1,6 @@ #pragma once #include "../types.h" -#include "glad.h" +#include "loader.h" #include #include diff --git a/src/common/gl/stream_buffer.h b/src/common/gl/stream_buffer.h index 78958dcf2..4b97bf2cb 100644 --- a/src/common/gl/stream_buffer.h +++ b/src/common/gl/stream_buffer.h @@ -1,6 +1,6 @@ #pragma once #include "../types.h" -#include +#include "loader.h" #include #include #include diff --git a/src/common/gl/texture.h b/src/common/gl/texture.h index 1a7016a09..d275afcd2 100644 --- a/src/common/gl/texture.h +++ b/src/common/gl/texture.h @@ -1,6 +1,6 @@ #pragma once #include "../types.h" -#include +#include "loader.h" namespace GL { class Texture diff --git a/src/common/path.h b/src/common/path.h new file mode 100644 index 000000000..75c99cea9 --- /dev/null +++ b/src/common/path.h @@ -0,0 +1,68 @@ +#pragma once + +#include "types.h" + +#include +#include +#include + +namespace Path { +/// Converts any forward slashes to backslashes on Win32. +std::string ToNativePath(const std::string_view& path); +void ToNativePath(std::string* path); + +/// Builds a path relative to the specified file +std::string BuildRelativePath(const std::string_view& filename, const std::string_view& new_filename); + +/// Joins path components together, producing a new path. +std::string Combine(const std::string_view& base, const std::string_view& next); + +/// Removes all .. and . components from a path. +std::string Canonicalize(const std::string_view& path); +void Canonicalize(std::string* path); + +/// Sanitizes a filename for use in a filesystem. +void SanitizeFileName(char* Destination, u32 cbDestination, const char* FileName, bool StripSlashes /* = true */); +void SanitizeFileName(std::string& Destination, bool StripSlashes = true); + +/// Returns true if the specified path is an absolute path (C:\Path on Windows or /path on Unix). +bool IsAbsolute(const std::string_view& path); + +/// Makes the specified path relative to another (e.g. /a/b/c, /a/b -> ../c). +/// Both paths must be relative, otherwise this function will just return the input path. +std::string MakeRelative(const std::string_view& path, const std::string_view& relative_to); + +/// Returns a view of the extension of a filename. +std::string_view GetExtension(const std::string_view& path); + +/// Removes the extension of a filename. +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 directory component of a filename. +std::string_view GetDirectory(const std::string_view& path); + +/// Returns the filename component of a filename. +std::string_view GetFileName(const std::string_view& path); + +/// Returns the file title (less the extension and path) from a filename. +std::string_view GetFileTitle(const std::string_view& path); + +/// Changes the filename in a path. +std::string ChangeFileName(const std::string_view& path, const std::string_view& new_filename); +void ChangeFileName(std::string* path, const std::string_view& new_filename); + +/// Appends a directory to a path. +std::string AppendDirectory(const std::string_view& path, const std::string_view& new_dir); +void AppendDirectory(std::string* path, const std::string_view& new_dir); + +/// Splits a path into its components, handling both Windows and Unix separators. +std::vector SplitWindowsPath(const std::string_view& path); +std::string JoinWindowsPath(const std::vector& components); + +/// Splits a path into its components, only handling native separators. +std::vector SplitNativePath(const std::string_view& path); +std::string JoinNativePath(const std::vector& components); +} // namespace Path diff --git a/src/common/timestamp.cpp b/src/common/timestamp.cpp deleted file mode 100644 index b0e831d27..000000000 --- a/src/common/timestamp.cpp +++ /dev/null @@ -1,331 +0,0 @@ -#include "timestamp.h" -#include -#include -#include - -#if defined(_WIN32) - -static void UnixTimeToSystemTime(time_t t, LPSYSTEMTIME pst); -static time_t SystemTimeToUnixTime(const SYSTEMTIME* pst); - -#endif - -Timestamp::Timestamp() -{ -#if defined(_WIN32) - m_value.wYear = 1970; - m_value.wMonth = 1; - m_value.wDayOfWeek = 0; - m_value.wDay = 1; - m_value.wHour = 0; - m_value.wMinute = 0; - m_value.wSecond = 0; - m_value.wMilliseconds = 0; -#else - m_value.tv_sec = 0; - m_value.tv_usec = 0; -#endif -} - -Timestamp::Timestamp(const Timestamp& copy) -{ -#if defined(_WIN32) - std::memcpy(&m_value, ©.m_value, sizeof(m_value)); -#else - std::memcpy(&m_value, ©.m_value, sizeof(m_value)); -#endif -} - -double Timestamp::DifferenceInSeconds(Timestamp& other) const -{ -#if defined(_WIN32) - FILETIME lft, rft; - SystemTimeToFileTime(&m_value, &lft); - SystemTimeToFileTime(&other.m_value, &rft); - - u64 lval = ((u64)lft.dwHighDateTime) << 32 | (u64)lft.dwLowDateTime; - u64 rval = ((u64)rft.dwHighDateTime) << 32 | (u64)rft.dwLowDateTime; - s64 diff = ((s64)lval - (s64)rval); - return double(diff / 10000000ULL) + (double(diff % 10000000ULL) / 10000000.0); - -#else - return (double)(m_value.tv_sec - other.m_value.tv_sec) + - (((double)(m_value.tv_usec - other.m_value.tv_usec)) / 1000000.0); -#endif -} - -s64 Timestamp::DifferenceInSecondsInt(Timestamp& other) const -{ -#if defined(_WIN32) - FILETIME lft, rft; - SystemTimeToFileTime(&m_value, &lft); - SystemTimeToFileTime(&other.m_value, &rft); - - u64 lval = ((u64)lft.dwHighDateTime) << 32 | (u64)lft.dwLowDateTime; - u64 rval = ((u64)rft.dwHighDateTime) << 32 | (u64)rft.dwLowDateTime; - s64 diff = ((s64)lval - (s64)rval); - return diff / 10000000ULL; - -#else - return static_cast(m_value.tv_sec - other.m_value.tv_sec); -#endif -} - -Timestamp::UnixTimestampValue Timestamp::AsUnixTimestamp() const -{ -#if defined(_WIN32) - return (UnixTimestampValue)SystemTimeToUnixTime(&m_value); -#else - return (UnixTimestampValue)m_value.tv_sec; -#endif -} - -Timestamp::ExpandedTime Timestamp::AsExpandedTime() const -{ - ExpandedTime et; - -#if defined(_WIN32) - et.Year = m_value.wYear; - et.Month = m_value.wMonth; - et.DayOfMonth = m_value.wDay; - et.DayOfWeek = m_value.wDayOfWeek; - et.Hour = m_value.wHour; - et.Minute = m_value.wMinute; - et.Second = m_value.wSecond; - et.Milliseconds = m_value.wMilliseconds; -#else - struct tm t; - time_t unixTime = (time_t)m_value.tv_sec; - gmtime_r(&unixTime, &t); - et.Year = t.tm_year + 1900; - et.Month = t.tm_mon + 1; - et.DayOfMonth = t.tm_mday; - et.DayOfWeek = t.tm_wday; - et.Hour = t.tm_hour; - et.Minute = t.tm_min; - et.Second = t.tm_sec; - et.Milliseconds = m_value.tv_usec / 1000; -#endif - - return et; -} - -void Timestamp::SetNow() -{ -#if defined(_WIN32) - GetSystemTime(&m_value); -#else - gettimeofday(&m_value, NULL); -#endif -} - -void Timestamp::SetUnixTimestamp(UnixTimestampValue value) -{ -#if defined(_WIN32) - UnixTimeToSystemTime((time_t)value, &m_value); -#else - m_value.tv_sec = (time_t)value; - m_value.tv_usec = 0; -#endif -} - -void Timestamp::SetExpandedTime(const ExpandedTime& value) -{ -#if defined(_WIN32) - // bit of a hacky way to fill in the missing fields - SYSTEMTIME st; - st.wYear = (WORD)value.Year; - st.wMonth = (WORD)value.Month; - st.wDay = (WORD)value.DayOfMonth; - st.wDayOfWeek = (WORD)0; - st.wHour = (WORD)value.Hour; - st.wMinute = (WORD)value.Minute; - st.wSecond = (WORD)value.Second; - st.wMilliseconds = (WORD)value.Milliseconds; - FILETIME ft; - SystemTimeToFileTime(&st, &ft); - FileTimeToSystemTime(&ft, &m_value); -#else - struct tm t; - std::memset(&t, 0, sizeof(t)); - t.tm_sec = value.Second; - t.tm_min = value.Minute; - t.tm_hour = value.Hour; - t.tm_mday = value.DayOfMonth; - t.tm_mon = value.Month - 1; - t.tm_year = value.Year - 1900; - time_t unixTime = mktime(&t); - SetUnixTimestamp((UnixTimestampValue)unixTime); -#endif -} - -String Timestamp::ToString(const char* format) const -{ - SmallString destination; - ToString(destination, format); - return String(destination); -} - -void Timestamp::ToString(String& destination, const char* format) const -{ - time_t unixTime = (time_t)AsUnixTimestamp(); - tm localTime; - -#if defined(_WIN32) - localtime_s(&localTime, &unixTime); -#else - localtime_r(&unixTime, &localTime); -#endif - - char buffer[256]; - strftime(buffer, countof(buffer) - 1, format, &localTime); - buffer[countof(buffer) - 1] = 0; - - destination.Clear(); - destination.AppendString(buffer); -} - -Timestamp Timestamp::Now() -{ - Timestamp t; - t.SetNow(); - return t; -} - -Timestamp Timestamp::FromUnixTimestamp(UnixTimestampValue value) -{ - Timestamp t; - t.SetUnixTimestamp(value); - return t; -} - -Timestamp Timestamp::FromExpandedTime(const ExpandedTime& value) -{ - Timestamp t; - t.SetExpandedTime(value); - return t; -} - -bool Timestamp::operator==(const Timestamp& other) const -{ -#if defined(_WIN32) - return std::memcmp(&m_value, &other.m_value, sizeof(m_value)) == 0; -#else - return std::memcmp(&m_value, &other.m_value, sizeof(m_value)) == 0; -#endif -} - -bool Timestamp::operator!=(const Timestamp& other) const -{ - return !operator==(other); -} - -bool Timestamp::operator<(const Timestamp& other) const -{ -#if defined(_WIN32) - return std::tie(m_value.wYear, m_value.wMonth, m_value.wDay, m_value.wHour, m_value.wMinute, m_value.wSecond, - m_value.wMilliseconds) < std::tie(other.m_value.wYear, other.m_value.wMonth, other.m_value.wDay, - other.m_value.wHour, other.m_value.wMinute, other.m_value.wSecond, - other.m_value.wMilliseconds); -#else - return std::tie(m_value.tv_sec, m_value.tv_usec) < std::tie(other.m_value.tv_sec, other.m_value.tv_usec); -#endif -} - -bool Timestamp::operator<=(const Timestamp& other) const -{ -#if defined(_WIN32) - return std::tie(m_value.wYear, m_value.wMonth, m_value.wDay, m_value.wHour, m_value.wMinute, m_value.wSecond, - m_value.wMilliseconds) <= std::tie(other.m_value.wYear, other.m_value.wMonth, other.m_value.wDay, - other.m_value.wHour, other.m_value.wMinute, other.m_value.wSecond, - other.m_value.wMilliseconds); -#else - return std::tie(m_value.tv_sec, m_value.tv_usec) <= std::tie(other.m_value.tv_sec, other.m_value.tv_usec); -#endif -} - -bool Timestamp::operator>(const Timestamp& other) const -{ -#if defined(_WIN32) - return std::tie(m_value.wYear, m_value.wMonth, m_value.wDay, m_value.wHour, m_value.wMinute, m_value.wSecond, - m_value.wMilliseconds) > std::tie(other.m_value.wYear, other.m_value.wMonth, other.m_value.wDay, - other.m_value.wHour, other.m_value.wMinute, other.m_value.wSecond, - other.m_value.wMilliseconds); -#else - return std::tie(m_value.tv_sec, m_value.tv_usec) > std::tie(other.m_value.tv_sec, other.m_value.tv_usec); -#endif -} - -bool Timestamp::operator>=(const Timestamp& other) const -{ -#if defined(_WIN32) - return std::tie(m_value.wYear, m_value.wMonth, m_value.wDay, m_value.wHour, m_value.wMinute, m_value.wSecond, - m_value.wMilliseconds) >= std::tie(other.m_value.wYear, other.m_value.wMonth, other.m_value.wDay, - other.m_value.wHour, other.m_value.wMinute, other.m_value.wSecond, - other.m_value.wMilliseconds); -#else - return std::tie(m_value.tv_sec, m_value.tv_usec) >= std::tie(other.m_value.tv_sec, other.m_value.tv_usec); -#endif -} - -Timestamp& Timestamp::operator=(const Timestamp& other) -{ -#if defined(_WIN32) - std::memcpy(&m_value, &other.m_value, sizeof(m_value)); -#else - std::memcpy(&m_value, &other.m_value, sizeof(m_value)); -#endif - - return *this; -} - -#if defined(_WIN32) - -// https://docs.microsoft.com/en-us/windows/win32/sysinfo/converting-a-time-t-value-to-a-file-time -static void UnixTimeToFileTime(time_t t, LPFILETIME pft) -{ - ULARGE_INTEGER time_value; - time_value.QuadPart = (t * 10000000LL) + 116444736000000000LL; - pft->dwLowDateTime = time_value.LowPart; - pft->dwHighDateTime = time_value.HighPart; -} -static void UnixTimeToSystemTime(time_t t, LPSYSTEMTIME pst) -{ - FILETIME ft; - UnixTimeToFileTime(t, &ft); - FileTimeToSystemTime(&ft, pst); -} -static time_t FileTimeToUnixTime(const FILETIME* pft) -{ - LONGLONG ll = ((LONGLONG)pft->dwHighDateTime) << 32 | (LONGLONG)pft->dwLowDateTime; - ll -= 116444736000000000ULL; - ll /= 10000000ULL; - return (time_t)ll; -} -static time_t SystemTimeToUnixTime(const SYSTEMTIME* pst) -{ - FILETIME ft; - SystemTimeToFileTime(pst, &ft); - return FileTimeToUnixTime(&ft); -} - -FILETIME Timestamp::AsFileTime() -{ - FILETIME ft; - SystemTimeToFileTime(&m_value, &ft); - return ft; -} - -void Timestamp::SetWindowsFileTime(const FILETIME* pFileTime) -{ - FileTimeToSystemTime(pFileTime, &m_value); -} - -Timestamp Timestamp::FromWindowsFileTime(const FILETIME* pFileTime) -{ - Timestamp ts; - ts.SetWindowsFileTime(pFileTime); - return ts; -} - -#endif diff --git a/src/common/timestamp.h b/src/common/timestamp.h deleted file mode 100644 index 7f46036e8..000000000 --- a/src/common/timestamp.h +++ /dev/null @@ -1,75 +0,0 @@ -#pragma once -#include "types.h" -#include "string.h" - -#if defined(_WIN32) -#include "windows_headers.h" -#else -#include -#endif - -class Timestamp -{ -public: - using UnixTimestampValue = u64; - struct ExpandedTime - { - u32 Year; // 0-... - u32 Month; // 1-12 - u32 DayOfMonth; // 1-31 - u32 DayOfWeek; // 0-6, starting at Sunday - u32 Hour; // 0-23 - u32 Minute; // 0-59 - u32 Second; // 0-59 - u32 Milliseconds; // 0-999 - }; - -public: - Timestamp(); - Timestamp(const Timestamp& copy); - - // readers - UnixTimestampValue AsUnixTimestamp() const; - ExpandedTime AsExpandedTime() const; - - // calculators - double DifferenceInSeconds(Timestamp& other) const; - s64 DifferenceInSecondsInt(Timestamp& other) const; - - // setters - void SetNow(); - void SetUnixTimestamp(UnixTimestampValue value); - void SetExpandedTime(const ExpandedTime& value); - - // string conversion - String ToString(const char* format) const; - void ToString(String& destination, const char* format) const; - - // creators - static Timestamp Now(); - static Timestamp FromUnixTimestamp(UnixTimestampValue value); - static Timestamp FromExpandedTime(const ExpandedTime& value); - -// windows-specific -#ifdef _WIN32 - FILETIME AsFileTime(); - void SetWindowsFileTime(const FILETIME* pFileTime); - static Timestamp FromWindowsFileTime(const FILETIME* pFileTime); -#endif - - // operators - bool operator==(const Timestamp& other) const; - bool operator!=(const Timestamp& other) const; - bool operator<(const Timestamp& other) const; - bool operator<=(const Timestamp& other) const; - bool operator>(const Timestamp& other) const; - bool operator>=(const Timestamp& other) const; - Timestamp& operator=(const Timestamp& other); - -private: -#if defined(_WIN32) - SYSTEMTIME m_value; -#else - struct timeval m_value; -#endif -}; diff --git a/src/common/vulkan/shader_cache.cpp b/src/common/vulkan/shader_cache.cpp index 85dfa2764..e7f1a8c1b 100644 --- a/src/common/vulkan/shader_cache.cpp +++ b/src/common/vulkan/shader_cache.cpp @@ -356,7 +356,7 @@ bool ShaderCache::FlushPipelineCache() // Save disk writes if it hasn't changed, think of the poor SSDs. FILESYSTEM_STAT_DATA sd; - if (!FileSystem::StatFile(m_pipeline_cache_filename.c_str(), &sd) || sd.Size != static_cast(data_size)) + if (!FileSystem::StatFile(m_pipeline_cache_filename.c_str(), &sd) || sd.Size != static_cast(data_size)) { Log_InfoPrintf("Writing %zu bytes to '%s'", data_size, m_pipeline_cache_filename.c_str()); if (!FileSystem::WriteBinaryFile(m_pipeline_cache_filename.c_str(), data.data(), data.size())) diff --git a/src/core/cheevos.cpp b/src/core/cheevos.cpp index ffa3e1934..333fcbb86 100644 --- a/src/core/cheevos.cpp +++ b/src/core/cheevos.cpp @@ -5,10 +5,10 @@ #include "common/http_downloader.h" #include "common/log.h" #include "common/md5_digest.h" +#include "common/path.h" #include "common/platform.h" #include "common/state_wrapper.h" #include "common/string_util.h" -#include "common/timestamp.h" #include "core/bios.h" #include "core/bus.h" #include "core/cpu_core.h" @@ -30,6 +30,10 @@ Log_SetChannel(Cheevos); #ifdef WITH_RAINTEGRATION +// RA_Interface ends up including windows.h, with its silly macros. +#ifdef _WIN32 +#include "common/windows_headers.h" +#endif #include "RA_Interface.h" #endif @@ -711,16 +715,16 @@ std::string Cheevos::GetBadgeImageFilename(const char* badge_name, bool locked, { if (!cache_path) { - return StringUtil::StdStringFromFormat("%s%s.png", badge_name, locked ? "_lock" : ""); + return fmt::format("%s%s.png", badge_name, locked ? "_lock" : ""); } else { // well, this comes from the internet.... :) - SmallString clean_name(badge_name); - FileSystem::SanitizeFileName(clean_name); + std::string clean_name(badge_name); + Path::SanitizeFileName(clean_name); return g_host_interface->GetUserDirectoryRelativePath("cache" FS_OSPATH_SEPARATOR_STR "achievement_badge" FS_OSPATH_SEPARATOR_STR "%s%s.png", - clean_name.GetCharArray(), locked ? "_lock" : ""); + clean_name.c_str(), locked ? "_lock" : ""); } } diff --git a/src/core/gpu_hw_opengl.h b/src/core/gpu_hw_opengl.h index 3f407b11f..ecb07c8a9 100644 --- a/src/core/gpu_hw_opengl.h +++ b/src/core/gpu_hw_opengl.h @@ -1,9 +1,9 @@ #pragma once +#include "common/gl/loader.h" #include "common/gl/program.h" #include "common/gl/shader_cache.h" #include "common/gl/stream_buffer.h" #include "common/gl/texture.h" -#include "glad.h" #include "gpu_hw.h" #include "texture_replacements.h" #include diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp index aaebb6288..253c2760c 100644 --- a/src/core/gpu_hw_shadergen.cpp +++ b/src/core/gpu_hw_shadergen.cpp @@ -1,7 +1,6 @@ #include "gpu_hw_shadergen.h" #include "common/assert.h" #include -#include GPU_HW_ShaderGen::GPU_HW_ShaderGen(HostDisplay::RenderAPI render_api, u32 resolution_scale, u32 multisamples, bool per_sample_shading, bool true_color, bool scaled_dithering, diff --git a/src/core/host_interface.cpp b/src/core/host_interface.cpp index 73ae20d0f..045f47b39 100644 --- a/src/core/host_interface.cpp +++ b/src/core/host_interface.cpp @@ -6,6 +6,7 @@ #include "common/file_system.h" #include "common/image.h" #include "common/log.h" +#include "common/path.h" #include "common/string_util.h" #include "controller.h" #include "cpu_code_cache.h" @@ -34,7 +35,7 @@ HostInterface::HostInterface() g_host_interface = this; // we can get the program directory at construction time - m_program_directory = FileSystem::GetPathDirectory(FileSystem::GetProgramPath()); + m_program_directory = Path::GetDirectory(FileSystem::GetProgramPath()); } HostInterface::~HostInterface() @@ -896,7 +897,7 @@ void HostInterface::SetUserDirectoryToProgramDirectory() if (program_path.empty()) Panic("Failed to get program path."); - std::string program_directory(FileSystem::GetPathDirectory(program_path)); + std::string program_directory(Path::GetDirectory(program_path)); if (program_directory.empty()) Panic("Program path is not valid"); diff --git a/src/core/imgui_fullscreen.cpp b/src/core/imgui_fullscreen.cpp index 6f9f2a8d0..d5b0736f4 100644 --- a/src/core/imgui_fullscreen.cpp +++ b/src/core/imgui_fullscreen.cpp @@ -4,8 +4,9 @@ #include "IconsFontAwesome5.h" #include "common/assert.h" #include "common/easing.h" -#include "common/lru_cache.h" #include "common/file_system.h" +#include "common/lru_cache.h" +#include "common/path.h" #include "common/string.h" #include "common/string_util.h" #include "common/timer.h" @@ -999,7 +1000,6 @@ static ImGuiID s_enum_choice_button_id = 0; static s32 s_enum_choice_button_value = 0; static bool s_enum_choice_button_set = false; - bool EnumChoiceButtonImpl(const char* title, const char* summary, s32* value_pointer, const char* (*to_display_name_function)(s32 value, void* opaque), void* opaque, u32 count, bool enabled, float height, ImFont* font, ImFont* summary_font) @@ -1221,7 +1221,7 @@ static void PopulateFileSelectorItems() if (sep_pos != std::string::npos) { parent_path = s_file_selector_current_directory.substr(0, sep_pos); - FileSystem::CanonicalizePath(parent_path, true); + Path::Canonicalize(&parent_path); } s_file_selector_items.emplace_back(ICON_FA_FOLDER_OPEN " ", std::move(parent_path), false); diff --git a/src/core/memory_card_image.cpp b/src/core/memory_card_image.cpp index 1a0230da0..ca03e9810 100644 --- a/src/core/memory_card_image.cpp +++ b/src/core/memory_card_image.cpp @@ -2,6 +2,7 @@ #include "common/byte_stream.h" #include "common/file_system.h" #include "common/log.h" +#include "common/path.h" #include "common/shiftjis.h" #include "common/state_wrapper.h" #include "common/string_util.h" @@ -655,7 +656,8 @@ static bool ImportSaveWithDirectoryFrame(DataArray* data, const char* filename, static bool ImportRawSave(DataArray* data, const char* filename, const FILESYSTEM_STAT_DATA& sd) { - std::string save_name(FileSystem::GetFileTitleFromPath(filename)); + const std::string display_name(FileSystem::GetDisplayNameFromPath(filename)); + std::string save_name(Path::GetFileTitle(filename)); if (save_name.length() == 0) { Log_ErrorPrintf("Invalid filename: '%s'", filename); diff --git a/src/core/psf_loader.cpp b/src/core/psf_loader.cpp index 9e5ae80a3..a488d273d 100644 --- a/src/core/psf_loader.cpp +++ b/src/core/psf_loader.cpp @@ -3,6 +3,7 @@ #include "common/assert.h" #include "common/file_system.h" #include "common/log.h" +#include "common/path.h" #include "system.h" #include "zlib.h" #include @@ -182,7 +183,7 @@ static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0) std::optional lib_name(file.GetTagString("_lib")); if (lib_name.has_value()) { - const std::string lib_path(FileSystem::BuildRelativePath(path, lib_name.value())); + const std::string lib_path(Path::BuildRelativePath(path, lib_name.value())); Log_InfoPrintf("Loading main parent PSF '%s'", lib_path.c_str()); // We should use the initial SP/PC from the **first** parent lib. @@ -214,7 +215,7 @@ static bool LoadLibraryPSF(const char* path, bool use_pc_sp, u32 depth = 0) if (!lib_name.has_value()) break; - const std::string lib_path(FileSystem::BuildRelativePath(path, lib_name.value())); + const std::string lib_path(Path::BuildRelativePath(path, lib_name.value())); Log_InfoPrintf("Loading parent PSF '%s'", lib_path.c_str()); if (!LoadLibraryPSF(lib_path.c_str(), false, depth + 1)) { diff --git a/src/core/shadergen.cpp b/src/core/shadergen.cpp index 176795167..d4cad4d82 100644 --- a/src/core/shadergen.cpp +++ b/src/core/shadergen.cpp @@ -1,13 +1,14 @@ #include "shadergen.h" #include "common/assert.h" +#include "common/gl/loader.h" #include "common/log.h" #include #include -#include Log_SetChannel(ShaderGen); ShaderGen::ShaderGen(HostDisplay::RenderAPI render_api, bool supports_dual_source_blend) - : m_render_api(render_api), m_glsl(render_api != HostDisplay::RenderAPI::D3D11 && render_api != HostDisplay::RenderAPI::D3D12), + : m_render_api(render_api), + m_glsl(render_api != HostDisplay::RenderAPI::D3D11 && render_api != HostDisplay::RenderAPI::D3D12), m_supports_dual_source_blend(supports_dual_source_blend), m_use_glsl_interface_blocks(false) { if (m_glsl) diff --git a/src/core/system.cpp b/src/core/system.cpp index 767bd35c9..59d216711 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -9,9 +9,9 @@ #include "common/iso_reader.h" #include "common/log.h" #include "common/make_array.h" +#include "common/path.h" #include "common/state_wrapper.h" #include "common/string_util.h" -#include "common/timestamp.h" #include "controller.h" #include "cpu_code_cache.h" #include "cpu_core.h" @@ -705,8 +705,8 @@ std::unique_ptr OpenCDImage(const char* path, Common::Error* error, boo if (check_for_patches) { - const std::string ppf_filename(FileSystem::BuildRelativePath( - path, FileSystem::ReplaceExtension(FileSystem::GetDisplayNameFromPath(path), "ppf"))); + const std::string ppf_filename( + Path::BuildRelativePath(path, Path::ReplaceExtension(FileSystem::GetDisplayNameFromPath(path), "ppf"))); if (FileSystem::FileExists(ppf_filename.c_str())) { media = CDImage::OverlayPPFPatch(ppf_filename.c_str(), std::move(media)); @@ -1754,7 +1754,7 @@ static bool LoadEXEToRAM(const char* filename, bool patch_bios) bool LoadEXE(const char* filename) { - const std::string libps_path(FileSystem::BuildRelativePath(filename, "libps.exe")); + const std::string libps_path(Path::BuildRelativePath(filename, "libps.exe")); if (!libps_path.empty() && FileSystem::FileExists(libps_path.c_str()) && !LoadEXEToRAM(libps_path.c_str(), false)) { Log_ErrorPrintf("Failed to load libps.exe from '%s'", libps_path.c_str()); @@ -1951,7 +1951,7 @@ static std::unique_ptr GetMemoryCardForSlot(u32 slot, MemoryCardType case MemoryCardType::PerGameFileTitle: { const std::string display_name(FileSystem::GetDisplayNameFromPath(s_running_game_path)); - const std::string_view file_title(FileSystem::GetFileTitleFromPath(display_name)); + const std::string_view file_title(Path::GetFileTitle(display_name)); if (file_title.empty()) { g_host_interface->AddFormattedOSDMessage( diff --git a/src/duckstation-qt/gamelistmodel.cpp b/src/duckstation-qt/gamelistmodel.cpp index e7cd82cef..58a57693e 100644 --- a/src/duckstation-qt/gamelistmodel.cpp +++ b/src/duckstation-qt/gamelistmodel.cpp @@ -1,5 +1,6 @@ #include "gamelistmodel.h" #include "common/file_system.h" +#include "common/path.h" #include "common/string_util.h" #include "core/system.h" #include @@ -182,7 +183,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const case Column_FileTitle: { - const std::string_view file_title(FileSystem::GetFileTitleFromPath(ge.path)); + const std::string_view file_title(Path::GetFileTitle(ge.path)); return QString::fromUtf8(file_title.data(), static_cast(file_title.length())); } @@ -248,7 +249,7 @@ QVariant GameListModel::data(const QModelIndex& index, int role) const case Column_FileTitle: { - const std::string_view file_title(FileSystem::GetFileTitleFromPath(ge.path)); + const std::string_view file_title(Path::GetFileTitle(ge.path)); return QString::fromUtf8(file_title.data(), static_cast(file_title.length())); } @@ -427,8 +428,8 @@ bool GameListModel::lessThan(const QModelIndex& left_index, const QModelIndex& r case Column_FileTitle: { - const std::string_view file_title_left(FileSystem::GetFileTitleFromPath(left.path)); - const std::string_view file_title_right(FileSystem::GetFileTitleFromPath(right.path)); + const std::string_view file_title_left(Path::GetFileTitle(left.path)); + const std::string_view file_title_right(Path::GetFileTitle(right.path)); if (file_title_left == file_title_right) return titlesLessThan(left_row, right_row); diff --git a/src/duckstation-qt/qthostinterface.cpp b/src/duckstation-qt/qthostinterface.cpp index 5c39cb9d7..691dd9818 100644 --- a/src/duckstation-qt/qthostinterface.cpp +++ b/src/duckstation-qt/qthostinterface.cpp @@ -4,6 +4,7 @@ #include "common/byte_stream.h" #include "common/file_system.h" #include "common/log.h" +#include "common/path.h" #include "common/string_util.h" #include "core/cheats.h" #include "core/controller.h" @@ -1153,7 +1154,7 @@ void QtHostInterface::populateGameListContextMenu(const GameListEntry* entry, QW { const std::string display_name(FileSystem::GetDisplayNameFromPath(entry->path)); paths[i] = QString::fromStdString(GetGameMemoryCardPath( - MemoryCard::SanitizeGameTitleForFileName(FileSystem::GetFileTitleFromPath(display_name)).c_str(), i)); + MemoryCard::SanitizeGameTitleForFileName(Path::GetFileTitle(display_name)).c_str(), i)); } break; default: diff --git a/src/duckstation-regtest/regtest_host_interface.cpp b/src/duckstation-regtest/regtest_host_interface.cpp index e8b87a90f..62c1273b5 100644 --- a/src/duckstation-regtest/regtest_host_interface.cpp +++ b/src/duckstation-regtest/regtest_host_interface.cpp @@ -4,6 +4,7 @@ #include "common/byte_stream.h" #include "common/file_system.h" #include "common/log.h" +#include "common/path.h" #include "common/string_util.h" #include "core/system.h" #include "frontend-common/game_database.h" @@ -105,7 +106,7 @@ void RegTestHostInterface::GetGameInfo(const char* path, CDImage* image, std::st *code = System::GetGameCodeForImage(image, true); } - *title = FileSystem::GetFileTitleFromPath(path); + *title = Path::GetFileTitle(path); } void RegTestHostInterface::OnRunningGameChanged(const std::string& path, CDImage* image, const std::string& game_code, @@ -204,7 +205,7 @@ void RegTestHostInterface::LoadGameSettingsDatabase() return; } - const std::string data(FileSystem::ReadStreamToString(stream.get())); + const std::string data(ByteStream::ReadStreamToString(stream.get())); if (data.empty() || !s_game_settings_db.Load(data)) { Log_ErrorPrintf("Failed to load game settings database from '%s'. This could cause compatibility issues.", path); @@ -238,7 +239,7 @@ std::string RegTestHostInterface::GetBIOSDirectory() std::unique_ptr RegTestHostInterface::OpenPackageFile(const char* path, u32 flags) { std::string full_path(GetProgramDirectoryRelativePath("%s", path)); - return ByteStream_OpenFileStream(full_path.c_str(), flags); + return ByteStream::OpenFile(full_path.c_str(), flags); } bool RegTestHostInterface::AcquireHostDisplay() diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index 6ebb77c7d..582ad20d9 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -6,6 +6,7 @@ #include "common/crash_handler.h" #include "common/file_system.h" #include "common/log.h" +#include "common/path.h" #include "common/string_util.h" #include "controller_interface.h" #include "core/cdrom.h" @@ -3074,7 +3075,7 @@ void CommonHostInterface::RenameCurrentSaveStateToBackup(const char* filename) if (!FileSystem::FileExists(filename)) return; - const std::string backup_filename(FileSystem::ReplaceExtension(filename, "bak")); + const std::string backup_filename(Path::ReplaceExtension(filename, "bak")); if (!FileSystem::RenamePath(filename, backup_filename.c_str())) { Log_ErrorPrintf("Failed to rename save state backup '%s'", backup_filename.c_str()); @@ -3094,8 +3095,7 @@ std::vector CommonHostInterface::GetAvailabl if (!FileSystem::StatFile(path.c_str(), &sd)) return; - si.push_back(SaveStateInfo{std::move(path), static_cast(sd.ModificationTime.AsUnixTimestamp()), - static_cast(slot), global}); + si.push_back(SaveStateInfo{std::move(path), sd.ModificationTime, static_cast(slot), global}); }; if (game_code && std::strlen(game_code) > 0) @@ -3120,7 +3120,7 @@ std::optional CommonHostInterface::GetSaveSt if (!FileSystem::StatFile(path.c_str(), &sd)) return std::nullopt; - return SaveStateInfo{std::move(path), static_cast(sd.ModificationTime.AsUnixTimestamp()), slot, global}; + return SaveStateInfo{std::move(path), sd.ModificationTime, slot, global}; } std::optional @@ -3193,7 +3193,7 @@ CommonHostInterface::GetExtendedSaveStateInfo(const char* game_code, s32 slot) return std::nullopt; ssi->path = std::move(path); - ssi->timestamp = sd.ModificationTime.AsUnixTimestamp(); + ssi->timestamp = sd.ModificationTime; ssi->slot = slot; ssi->global = global; @@ -3650,7 +3650,8 @@ void CommonHostInterface::GetGameInfo(const char* path, CDImage* image, std::str *code = System::GetGameCodeForImage(image, true); } - *title = FileSystem::GetFileTitleFromPath(path); + const std::string display_name(FileSystem::GetDisplayNameFromPath(path)); + *title = Path::GetFileTitle(display_name); } bool CommonHostInterface::SaveResumeSaveState() diff --git a/src/frontend-common/fullscreen_ui.cpp b/src/frontend-common/fullscreen_ui.cpp index aab2a315e..8224f4b31 100644 --- a/src/frontend-common/fullscreen_ui.cpp +++ b/src/frontend-common/fullscreen_ui.cpp @@ -6,6 +6,7 @@ #include "common/file_system.h" #include "common/log.h" #include "common/make_array.h" +#include "common/path.h" #include "common/string.h" #include "common/string_util.h" #include "common_host_interface.h" @@ -688,7 +689,7 @@ static void DoChangeDiscFromFile() }; OpenFileSelector(ICON_FA_COMPACT_DISC " Select Disc Image", false, std::move(callback), GetDiscImageFilters(), - std::string(FileSystem::GetPathDirectory(System::GetMediaFileName()))); + std::string(Path::GetDirectory(System::GetMediaFileName()))); } static void DoChangeDisc() @@ -1120,7 +1121,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(std::move(value)))); + std::string(Path::GetDirectory(std::move(value)))); } return false; @@ -2604,7 +2605,7 @@ void DrawQuickMenu(MainWindowType type) SmallString subtitle; if (!code.empty()) subtitle.Format("%s - ", code.c_str()); - subtitle.AppendString(FileSystem::GetFileNameFromPath(System::GetRunningPath())); + subtitle.AppendString(Path::GetFileTitle(System::GetRunningPath())); const ImVec2 title_size( g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits::max(), -1.0f, title.c_str())); @@ -3092,7 +3093,7 @@ void DrawGameListWindow() else summary.Format("%s - %s - ", entry->code.c_str(), Settings::GetDiscRegionName(entry->region)); - summary.AppendString(FileSystem::GetFileNameFromPath(entry->path)); + summary.AppendString(Path::GetFileName(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)); diff --git a/src/frontend-common/game_list.cpp b/src/frontend-common/game_list.cpp index 66769cf23..dd4667d6a 100644 --- a/src/frontend-common/game_list.cpp +++ b/src/frontend-common/game_list.cpp @@ -6,6 +6,7 @@ #include "common/iso_reader.h" #include "common/log.h" #include "common/make_array.h" +#include "common/path.h" #include "common/progress_callback.h" #include "common/string_util.h" #include "core/bios.h" @@ -91,7 +92,7 @@ bool GameList::GetExeListEntry(const std::string& path, GameListEntry* entry) const std::string display_name(FileSystem::GetDisplayNameFromPath(path)); entry->code.clear(); - entry->title = FileSystem::StripExtension(display_name); + entry->title = Path::StripExtension(display_name); entry->region = BIOS::GetPSExeDiscRegion(header); entry->total_size = ZeroExtend64(file_size); entry->type = GameListEntryType::PSExe; @@ -133,7 +134,7 @@ bool GameList::GetPsfListEntry(const std::string& path, GameListEntry* entry) else { const std::string display_name(FileSystem::GetDisplayNameFromPath(path)); - entry->title += FileSystem::StripExtension(display_name); + entry->title += Path::StripExtension(display_name); } return true; @@ -160,9 +161,11 @@ bool GameList::GetGameListEntry(const std::string& path, GameListEntry* entry) GameDatabaseEntry dbentry; if (!m_database.GetEntryForDisc(cdi.get(), &dbentry)) { + const std::string display_name(FileSystem::GetDisplayNameFromPath(path)); + // no game code, so use the filename title entry->code = System::GetGameCodeForImage(cdi.get(), true); - entry->title = FileSystem::GetFileTitleFromPath(path); + entry->title = Path::GetFileTitle(display_name); entry->compatibility_rating = GameListCompatibilityRating::Unknown; entry->release_date = 0; entry->min_players = 0; @@ -492,20 +495,19 @@ void GameList::ScanDirectory(const char* path, bool recursive, ProgressCallback* if (!IsScannableFilename(ffd.FileName) || IsPathExcluded(ffd.FileName) || GetEntryForPath(ffd.FileName.c_str())) continue; - const u64 modified_time = ffd.ModificationTime.AsUnixTimestamp(); - if (AddFileFromCache(ffd.FileName, modified_time)) + if (AddFileFromCache(ffd.FileName, ffd.ModificationTime)) continue; // ownership of fp is transferred progress->SetFormattedStatusText("Scanning '%s'...", FileSystem::GetDisplayNameFromPath(ffd.FileName).c_str()); - ScanFile(std::move(ffd.FileName), modified_time); + ScanFile(std::move(ffd.FileName), ffd.ModificationTime); } progress->SetProgressValue(static_cast(files.size())); progress->PopState(); } -bool GameList::AddFileFromCache(const std::string& path, u64 timestamp) +bool GameList::AddFileFromCache(const std::string& path, std::time_t timestamp) { if (std::any_of(m_entries.begin(), m_entries.end(), [&path](const GameListEntry& other) { return other.path == path; })) @@ -515,14 +517,14 @@ bool GameList::AddFileFromCache(const std::string& path, u64 timestamp) } GameListEntry entry; - if (!GetGameListEntryFromCache(path, &entry) || entry.last_modified_time != timestamp) + if (!GetGameListEntryFromCache(path, &entry) || entry.last_modified_time != static_cast(timestamp)) return false; m_entries.push_back(std::move(entry)); return true; } -bool GameList::ScanFile(std::string path, u64 timestamp) +bool GameList::ScanFile(std::string path, std::time_t timestamp) { Log_DevPrintf("Scanning '%s'...", path.c_str()); @@ -531,7 +533,7 @@ bool GameList::ScanFile(std::string path, u64 timestamp) return false; entry.path = std::move(path); - entry.last_modified_time = timestamp; + entry.last_modified_time = static_cast(timestamp); if (m_cache_write_stream || OpenCacheForWriting()) { @@ -1078,7 +1080,8 @@ std::string GameList::GetCoverImagePath(const std::string& path, const std::stri for (const char* extension : extensions) { // use the file title if it differs (e.g. modded games) - const std::string_view file_title(FileSystem::GetFileTitleFromPath(path)); + const std::string display_name(FileSystem::GetDisplayNameFromPath(path)); + const std::string_view file_title(Path::GetFileTitle(display_name)); if (!file_title.empty() && title != file_title) { cover_path.Clear(); diff --git a/src/frontend-common/game_list.h b/src/frontend-common/game_list.h index 279738e24..67512a92f 100644 --- a/src/frontend-common/game_list.h +++ b/src/frontend-common/game_list.h @@ -3,6 +3,7 @@ #include "core/types.h" #include "game_database.h" #include "game_settings.h" +#include #include #include #include @@ -152,8 +153,8 @@ private: bool GetGameListEntry(const std::string& path, GameListEntry* entry); bool GetGameListEntryFromCache(const std::string& path, GameListEntry* entry); void ScanDirectory(const char* path, bool recursive, ProgressCallback* progress); - bool AddFileFromCache(const std::string& path, u64 timestamp); - bool ScanFile(std::string path, u64 timestamp); + bool AddFileFromCache(const std::string& path, std::time_t timestamp); + bool ScanFile(std::string path, std::time_t timestamp); void LoadCache(); bool LoadEntriesFromCache(ByteStream* stream); diff --git a/src/frontend-common/imgui_impl_opengl3.cpp b/src/frontend-common/imgui_impl_opengl3.cpp index d9d84bc77..51dfb8a74 100644 --- a/src/frontend-common/imgui_impl_opengl3.cpp +++ b/src/frontend-common/imgui_impl_opengl3.cpp @@ -76,7 +76,7 @@ #endif // GL includes -#include +#include "common/gl/loader.h" // OpenGL Data static char g_GlslVersionString[32] = ""; diff --git a/src/frontend-common/opengl_host_display.h b/src/frontend-common/opengl_host_display.h index 23e8d7009..9ab293fad 100644 --- a/src/frontend-common/opengl_host_display.h +++ b/src/frontend-common/opengl_host_display.h @@ -1,14 +1,6 @@ #pragma once - -// GLAD has to come first so that Qt doesn't pull in the system GL headers, which are incompatible with glad. -#include - -// Hack to prevent Apple's glext.h headers from getting included via qopengl.h, since we still want to use glad. -#ifdef __APPLE__ -#define __glext_h_ -#endif - #include "common/gl/context.h" +#include "common/gl/loader.h" #include "common/gl/program.h" #include "common/gl/stream_buffer.h" #include "common/gl/texture.h" diff --git a/src/frontend-common/save_state_selector_ui.cpp b/src/frontend-common/save_state_selector_ui.cpp index 214418d56..8001af8cc 100644 --- a/src/frontend-common/save_state_selector_ui.cpp +++ b/src/frontend-common/save_state_selector_ui.cpp @@ -1,7 +1,6 @@ #include "save_state_selector_ui.h" #include "common/log.h" #include "common/string_util.h" -#include "common/timestamp.h" #include "core/host_display.h" #include "core/system.h" #include "fmt/chrono.h"