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