From 97bcd8a631b4096b61d14371b26743331233e343 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Sat, 7 Oct 2023 14:36:41 +0300 Subject: [PATCH] Add method to check if filesystem is case sensitive. --- core/io/dir_access.cpp | 6 ++++ core/io/dir_access.h | 2 ++ doc/classes/DirAccess.xml | 8 +++++ drivers/windows/dir_access_windows.cpp | 48 ++++++++++++++++++++++++++ drivers/windows/dir_access_windows.h | 1 + editor/filesystem_dock.cpp | 12 +++---- platform/macos/dir_access_macos.h | 1 + platform/macos/dir_access_macos.mm | 17 +++++++++ platform/windows/detect.py | 2 ++ 9 files changed, 91 insertions(+), 6 deletions(-) diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index 22b63a09290e8..40c1a539588b9 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -546,6 +546,10 @@ bool DirAccess::get_include_hidden() const { return include_hidden; } +bool DirAccess::is_case_sensitive(const String &p_path) const { + return true; +} + void DirAccess::_bind_methods() { ClassDB::bind_static_method("DirAccess", D_METHOD("open", "path"), &DirAccess::_open); ClassDB::bind_static_method("DirAccess", D_METHOD("get_open_error"), &DirAccess::get_open_error); @@ -583,6 +587,8 @@ void DirAccess::_bind_methods() { ClassDB::bind_method(D_METHOD("set_include_hidden", "enable"), &DirAccess::set_include_hidden); ClassDB::bind_method(D_METHOD("get_include_hidden"), &DirAccess::get_include_hidden); + ClassDB::bind_method(D_METHOD("is_case_sensitive", "path"), &DirAccess::is_case_sensitive); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_navigational"), "set_include_navigational", "get_include_navigational"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "include_hidden"), "set_include_hidden", "get_include_hidden"); } diff --git a/core/io/dir_access.h b/core/io/dir_access.h index 52ed688debf3a..4ee69571f256a 100644 --- a/core/io/dir_access.h +++ b/core/io/dir_access.h @@ -159,6 +159,8 @@ class DirAccess : public RefCounted { void set_include_hidden(bool p_enable); bool get_include_hidden() const; + virtual bool is_case_sensitive(const String &p_path) const; + DirAccess() {} virtual ~DirAccess() {} }; diff --git a/doc/classes/DirAccess.xml b/doc/classes/DirAccess.xml index 9c72bc2247e5d..204bd89aa4cbe 100644 --- a/doc/classes/DirAccess.xml +++ b/doc/classes/DirAccess.xml @@ -204,6 +204,14 @@ Returns the available space on the current directory's disk, in bytes. Returns [code]0[/code] if the platform-specific method to query the available space fails. + + + + + Returns [code]true[/code] if the file system or directory use case sensitive file names. + [b]Note:[/b] This method is implemented on macOS and Windows. On other platforms, it always returns [code]true[/code]. + + diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp index 26b8881c39faf..8bf83823a03e2 100644 --- a/drivers/windows/dir_access_windows.cpp +++ b/drivers/windows/dir_access_windows.cpp @@ -32,6 +32,7 @@ #include "dir_access_windows.h" +#include "core/config/project_settings.h" #include "core/os/memory.h" #include "core/string/print_string.h" @@ -40,6 +41,26 @@ #define WIN32_LEAN_AND_MEAN #include +typedef struct _NT_IO_STATUS_BLOCK { + union { + LONG Status; + PVOID Pointer; + } DUMMY; + ULONG_PTR Information; +} NT_IO_STATUS_BLOCK; + +typedef struct _NT_FILE_CASE_SENSITIVE_INFO { + ULONG Flags; +} NT_FILE_CASE_SENSITIVE_INFO; + +typedef enum _NT_FILE_INFORMATION_CLASS { + FileCaseSensitiveInformation = 71, +} NT_FILE_INFORMATION_CLASS; + +#define NT_FILE_CS_FLAG_CASE_SENSITIVE_DIR 0x00000001 + +extern "C" NTSYSAPI LONG NTAPI NtQueryInformationFile(HANDLE FileHandle, NT_IO_STATUS_BLOCK *IoStatusBlock, PVOID FileInformation, ULONG Length, NT_FILE_INFORMATION_CLASS FileInformationClass); + struct DirAccessWindowsPrivate { HANDLE h; // handle for FindFirstFile. WIN32_FIND_DATA f; @@ -340,6 +361,33 @@ String DirAccessWindows::get_filesystem_type() const { ERR_FAIL_V(""); } +bool DirAccessWindows::is_case_sensitive(const String &p_path) const { + String f = p_path; + if (!f.is_absolute_path()) { + f = get_current_dir().path_join(f); + } + f = fix_path(f); + + HANDLE h_file = ::CreateFileW((LPCWSTR)(f.utf16().get_data()), 0, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + if (h_file == INVALID_HANDLE_VALUE) { + return false; + } + + NT_IO_STATUS_BLOCK io_status_block; + NT_FILE_CASE_SENSITIVE_INFO file_info; + LONG out = NtQueryInformationFile(h_file, &io_status_block, &file_info, sizeof(NT_FILE_CASE_SENSITIVE_INFO), FileCaseSensitiveInformation); + ::CloseHandle(h_file); + + if (out >= 0) { + return file_info.Flags & NT_FILE_CS_FLAG_CASE_SENSITIVE_DIR; + } else { + return false; + } +} + DirAccessWindows::DirAccessWindows() { p = memnew(DirAccessWindowsPrivate); p->h = INVALID_HANDLE_VALUE; diff --git a/drivers/windows/dir_access_windows.h b/drivers/windows/dir_access_windows.h index 1e55917756ade..1dcab84c9d53f 100644 --- a/drivers/windows/dir_access_windows.h +++ b/drivers/windows/dir_access_windows.h @@ -84,6 +84,7 @@ class DirAccessWindows : public DirAccess { uint64_t get_space_left() override; virtual String get_filesystem_type() const override; + virtual bool is_case_sensitive(const String &p_path) const override; DirAccessWindows(); ~DirAccessWindows(); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index be06a3932c3d9..2bedb090758b4 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1776,12 +1776,12 @@ void FileSystemDock::_rename_operation_confirm() { // Present a more user friendly warning for name conflict. Ref da = DirAccess::create(DirAccess::ACCESS_RESOURCES); -#if defined(WINDOWS_ENABLED) - // Workaround case insensitivity on Windows. - if ((da->file_exists(new_path) || da->dir_exists(new_path)) && new_path.to_lower() != old_path.to_lower()) { -#else - if (da->file_exists(new_path) || da->dir_exists(new_path)) { -#endif + + bool new_exist = (da->file_exists(new_path) || da->dir_exists(new_path)); + if (!da->is_case_sensitive(new_path.get_base_dir())) { + new_exist = new_exist && (new_path.to_lower() != old_path.to_lower()); + } + if (new_exist) { EditorNode::get_singleton()->show_warning(TTR("A file or folder with this name already exists.")); s->set_text(col_index, old_name); return; diff --git a/platform/macos/dir_access_macos.h b/platform/macos/dir_access_macos.h index 64556999a78ad..167c162200db9 100644 --- a/platform/macos/dir_access_macos.h +++ b/platform/macos/dir_access_macos.h @@ -49,6 +49,7 @@ class DirAccessMacOS : public DirAccessUnix { virtual String get_drive(int p_drive) override; virtual bool is_hidden(const String &p_name) override; + virtual bool is_case_sensitive(const String &p_path) const override; }; #endif // UNIX ENABLED diff --git a/platform/macos/dir_access_macos.mm b/platform/macos/dir_access_macos.mm index 2413d7bcf344e..66d81f2687e8c 100644 --- a/platform/macos/dir_access_macos.mm +++ b/platform/macos/dir_access_macos.mm @@ -30,6 +30,8 @@ #include "dir_access_macos.h" +#include "core/config/project_settings.h" + #if defined(UNIX_ENABLED) #include @@ -78,4 +80,19 @@ return [hidden boolValue]; } +bool DirAccessMacOS::is_case_sensitive(const String &p_path) const { + String f = p_path; + if (!f.is_absolute_path()) { + f = get_current_dir().path_join(f); + } + f = fix_path(f); + + NSURL *url = [NSURL fileURLWithPath:@(f.utf8().get_data())]; + NSNumber *cs = nil; + if (![url getResourceValue:&cs forKey:NSURLVolumeSupportsCaseSensitiveNamesKey error:nil]) { + return false; + } + return [cs boolValue]; +} + #endif // UNIX_ENABLED diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 7caa0153d7b27..bdacdbb9ba32a 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -419,6 +419,7 @@ def configure_msvc(env, vcvars_msvc_config): "dwmapi", "dwrite", "wbemuuid", + "ntdll", ] if env.debug_features: @@ -610,6 +611,7 @@ def configure_mingw(env): "dwmapi", "dwrite", "wbemuuid", + "ntdll", ] )