From fc948e87f6bcbd403b2a5935ceae8b9c42751dfc Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Wed, 24 Apr 2024 10:48:47 +0300 Subject: [PATCH] Add symlink API support for Windows, expose symlink methods. --- core/io/dir_access.cpp | 4 ++ doc/classes/DirAccess.xml | 26 +++++++++++ drivers/windows/dir_access_windows.cpp | 60 ++++++++++++++++++++++++++ drivers/windows/dir_access_windows.h | 6 +-- 4 files changed, 93 insertions(+), 3 deletions(-) diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index e99885befab..5df67b1103c 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -582,6 +582,10 @@ void DirAccess::_bind_methods() { ClassDB::bind_method(D_METHOD("remove", "path"), &DirAccess::remove); ClassDB::bind_static_method("DirAccess", D_METHOD("remove_absolute", "path"), &DirAccess::remove_absolute); + ClassDB::bind_method(D_METHOD("is_link", "path"), &DirAccess::is_link); + ClassDB::bind_method(D_METHOD("read_link", "path"), &DirAccess::read_link); + ClassDB::bind_method(D_METHOD("create_link", "source", "target"), &DirAccess::create_link); + ClassDB::bind_method(D_METHOD("set_include_navigational", "enable"), &DirAccess::set_include_navigational); ClassDB::bind_method(D_METHOD("get_include_navigational"), &DirAccess::get_include_navigational); ClassDB::bind_method(D_METHOD("set_include_hidden", "enable"), &DirAccess::set_include_hidden); diff --git a/doc/classes/DirAccess.xml b/doc/classes/DirAccess.xml index 03d7f68f439..9c71addf0c5 100644 --- a/doc/classes/DirAccess.xml +++ b/doc/classes/DirAccess.xml @@ -94,6 +94,16 @@ Static version of [method copy]. Supports only absolute paths. + + + + + + Creates symbolic link between files or folders. + [b]Note:[/b] On Windows, this method works only if the application is running with elevated privileges or Developer Mode is enabled. + [b]Note:[/b] This method is implemented on macOS, Linux, and Windows. + + @@ -212,6 +222,14 @@ [b]Note:[/b] This method is implemented on macOS, Linux (for EXT4 and F2FS filesystems only) and Windows. On other platforms, it always returns [code]true[/code]. + + + + + Returns [code]true[/code] if the file or directory is a symbolic link, directory junction, or other reparse point. + [b]Note:[/b] This method is implemented on macOS, Linux, and Windows. + + @@ -264,6 +282,14 @@ Returns [code]null[/code] if opening the directory failed. You can use [method get_open_error] to check the error that occurred. + + + + + Returns target of the symbolic link. + [b]Note:[/b] This method is implemented on macOS, Linux, and Windows. + + diff --git a/drivers/windows/dir_access_windows.cpp b/drivers/windows/dir_access_windows.cpp index 43dd62cdf63..63ba6a6c968 100644 --- a/drivers/windows/dir_access_windows.cpp +++ b/drivers/windows/dir_access_windows.cpp @@ -396,6 +396,66 @@ bool DirAccessWindows::is_case_sensitive(const String &p_path) const { } } +bool DirAccessWindows::is_link(String p_file) { + String f = p_file; + + if (!f.is_absolute_path()) { + f = get_current_dir().path_join(f); + } + f = fix_path(f); + + DWORD attr = GetFileAttributesW((LPCWSTR)(f.utf16().get_data())); + if (attr == INVALID_FILE_ATTRIBUTES) { + return false; + } + + return (attr & FILE_ATTRIBUTE_REPARSE_POINT); +} + +String DirAccessWindows::read_link(String p_file) { + String f = p_file; + + if (!f.is_absolute_path()) { + f = get_current_dir().path_join(f); + } + f = fix_path(f); + + HANDLE hfile = CreateFileW((LPCWSTR)(f.utf16().get_data()), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + if (hfile == INVALID_HANDLE_VALUE) { + return f; + } + + DWORD ret = GetFinalPathNameByHandleW(hfile, nullptr, 0, VOLUME_NAME_DOS | FILE_NAME_NORMALIZED); + if (ret == 0) { + return f; + } + Char16String cs; + cs.resize(ret + 1); + GetFinalPathNameByHandleW(hfile, (LPWSTR)cs.ptrw(), ret, VOLUME_NAME_DOS | FILE_NAME_NORMALIZED); + CloseHandle(hfile); + + return String::utf16((const char16_t *)cs.ptr(), ret).trim_prefix(R"(\\?\)"); +} + +Error DirAccessWindows::create_link(String p_source, String p_target) { + if (p_target.is_relative_path()) { + p_target = get_current_dir().path_join(p_target); + } + + p_source = fix_path(p_source); + p_target = fix_path(p_target); + + DWORD file_attr = GetFileAttributesW((LPCWSTR)(p_source.utf16().get_data())); + bool is_dir = (file_attr & FILE_ATTRIBUTE_DIRECTORY); + + DWORD flags = ((is_dir) ? SYMBOLIC_LINK_FLAG_DIRECTORY : 0) | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE; + if (CreateSymbolicLinkW((LPCWSTR)p_target.utf16().get_data(), (LPCWSTR)p_source.utf16().get_data(), flags) != 0) { + return OK; + } else { + return FAILED; + } +} + 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 576ba18d9a1..46755cbf33c 100644 --- a/drivers/windows/dir_access_windows.h +++ b/drivers/windows/dir_access_windows.h @@ -77,9 +77,9 @@ public: virtual Error rename(String p_path, String p_new_path) override; virtual Error remove(String p_path) override; - virtual bool is_link(String p_file) override { return false; }; - virtual String read_link(String p_file) override { return p_file; }; - virtual Error create_link(String p_source, String p_target) override { return FAILED; }; + virtual bool is_link(String p_file) override; + virtual String read_link(String p_file) override; + virtual Error create_link(String p_source, String p_target) override; uint64_t get_space_left() override;