diff --git a/Platforms/Basic/CMakeLists.txt b/Platforms/Basic/CMakeLists.txt index 72b9aa733..331e1d418 100644 --- a/Platforms/Basic/CMakeLists.txt +++ b/Platforms/Basic/CMakeLists.txt @@ -15,12 +15,16 @@ set(INTERFACE interface/DebugUtilities.hpp ) +set(INCLUDE + include/SearchRecursive.inl +) + if(PLATFORM_LINUX OR PLATFORM_WIN32 OR PLATFORM_APPLE OR PLATFORM_EMSCRIPTEN) list(APPEND SOURCE src/StandardFile.cpp) list(APPEND INTERFACE interface/StandardFile.hpp) endif() -add_library(Diligent-BasicPlatform STATIC ${SOURCE} ${INTERFACE}) +add_library(Diligent-BasicPlatform STATIC ${SOURCE} ${INTERFACE} ${INCLUDE}) set_common_target_properties(Diligent-BasicPlatform) target_include_directories(Diligent-BasicPlatform @@ -37,6 +41,7 @@ PUBLIC source_group("src" FILES ${SOURCE}) source_group("interface" FILES ${INTERFACE}) +source_group("include" FILES ${INCLUDE}) set_target_properties(Diligent-BasicPlatform PROPERTIES FOLDER DiligentCore/Platforms diff --git a/Platforms/Basic/include/SearchRecursive.inl b/Platforms/Basic/include/SearchRecursive.inl new file mode 100644 index 000000000..0568e4cc2 --- /dev/null +++ b/Platforms/Basic/include/SearchRecursive.inl @@ -0,0 +1,81 @@ +/* + * Copyright 2023 Diligent Graphics LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * In no event and under no legal theory, whether in tort (including negligence), + * contract, or otherwise, unless required by applicable law (such as deliberate + * and grossly negligent acts) or agreed to in writing, shall any Contributor be + * liable for any damages, including any direct, indirect, special, incidental, + * or consequential damages of any character arising as a result of this License or + * out of the use or inability to use the software (including but not limited to damages + * for loss of goodwill, work stoppage, computer failure or malfunction, or any and + * all other commercial damages or losses), even if such Contributor has been advised + * of the possibility of such damages. + */ + +#pragma once + +#include +#include + +namespace Diligent +{ + +template +void SearchRecursiveImpl(const std::string& BaseDir, const std::string& SubDir, const Char* SearchPattern, std::vector& Res) +{ + std::string Dir = BaseDir + SubDir; + + auto Files = FileSystem::Search((Dir + SearchPattern).c_str()); + Res.reserve(Res.size() + Files.size()); + for (auto& File : Files) + { + File.Name = SubDir + File.Name; + Res.emplace_back(std::move(File)); + } + + auto AllFiles = FileSystem::Search((Dir + '*').c_str()); + for (const auto& File : AllFiles) + { + if (File.IsDirectory) + { + SearchRecursiveImpl(BaseDir, SubDir + File.Name + FileSystem::SlashSymbol, SearchPattern, Res); + } + } +} + +template +typename FileSystem::SearchFilesResult SearchRecursive(const Char* Dir, const Char* SearchPattern) +{ + if (Dir == nullptr || Dir[0] == '\0') + { + UNEXPECTED("Directory must not be null or empty"); + return {}; + } + if (SearchPattern == nullptr || SearchPattern[0] == '\0') + { + UNEXPECTED("Search pattern must not be null or empty"); + return {}; + } + + std::string BaseDir = Dir; + if (BaseDir.back() != FileSystem::SlashSymbol) + BaseDir += FileSystem::SlashSymbol; + + typename FileSystem::SearchFilesResult Res; + SearchRecursiveImpl(BaseDir, "", SearchPattern, Res); + return Res; +} + +} // namespace Diligent diff --git a/Platforms/Basic/interface/BasicFileSystem.hpp b/Platforms/Basic/interface/BasicFileSystem.hpp index e62ed9808..cf1a79b39 100644 --- a/Platforms/Basic/interface/BasicFileSystem.hpp +++ b/Platforms/Basic/interface/BasicFileSystem.hpp @@ -133,10 +133,8 @@ struct FileDialogAttribs struct FindFileData { - virtual const Char* Name() const = 0; - virtual bool IsDirectory() const = 0; - - virtual ~FindFileData() {} + String Name; + bool IsDirectory = false; }; struct BasicFileSystem @@ -148,6 +146,8 @@ struct BasicFileSystem static constexpr Char SlashSymbol = '/'; #endif + using SearchFilesResult = std::vector; + static BasicFile* OpenFile(FileOpenAttribs& OpenAttribs); static void ReleaseFile(BasicFile*); diff --git a/Platforms/Linux/interface/LinuxFileSystem.hpp b/Platforms/Linux/interface/LinuxFileSystem.hpp index bb582d017..347a80f02 100644 --- a/Platforms/Linux/interface/LinuxFileSystem.hpp +++ b/Platforms/Linux/interface/LinuxFileSystem.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 Diligent Graphics LLC + * Copyright 2019-2023 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -54,7 +54,8 @@ struct LinuxFileSystem : public BasicFileSystem static void ClearDirectory(const Char* strPath, bool Recursive = false); static void DeleteFile(const Char* strPath); - static std::vector> Search(const Char* SearchPattern); + static SearchFilesResult Search(const Char* SearchPattern); + static SearchFilesResult SearchRecursive(const Char* Dir, const Char* SearchPattern); // Thread-safe popen/pclose static FILE* popen(const char* command, const char* type); diff --git a/Platforms/Linux/src/LinuxFileSystem.cpp b/Platforms/Linux/src/LinuxFileSystem.cpp index 9de2bd1cb..8019ad03b 100644 --- a/Platforms/Linux/src/LinuxFileSystem.cpp +++ b/Platforms/Linux/src/LinuxFileSystem.cpp @@ -40,6 +40,7 @@ #include "../interface/LinuxFileSystem.hpp" #include "Errors.hpp" #include "DebugUtilities.hpp" +#include "../../Basic/include/SearchRecursive.inl" namespace Diligent { @@ -166,24 +167,9 @@ bool LinuxFileSystem::IsDirectory(const Char* strPath) return S_ISDIR(StatBuff.st_mode); } -struct LinuxFindFileData : public FindFileData +LinuxFileSystem::SearchFilesResult LinuxFileSystem::Search(const Char* SearchPattern) { - virtual const Char* Name() const override { return m_Name.c_str(); } - - virtual bool IsDirectory() const override { return m_IsDirectory; } - - const std::string m_Name; - const bool m_IsDirectory; - - LinuxFindFileData(std::string _Name, bool _IsDirectory) : - m_Name{std::move(_Name)}, - m_IsDirectory{_IsDirectory} - {} -}; - -std::vector> LinuxFileSystem::Search(const Char* SearchPattern) -{ - std::vector> SearchRes; + LinuxFileSystem::SearchFilesResult SearchRes; #if PLATFORM_LINUX || PLATFORM_APPLE glob_t glob_result = {}; @@ -198,7 +184,7 @@ std::vector> LinuxFileSystem::Search(const Char* S std::string FileName; GetPathComponents(path, nullptr, &FileName); - SearchRes.emplace_back(std::make_unique(std::move(FileName), S_ISDIR(StatBuff.st_mode))); + SearchRes.emplace_back(FindFileData{std::move(FileName), S_ISDIR(StatBuff.st_mode)}); } } globfree(&glob_result); @@ -209,6 +195,16 @@ std::vector> LinuxFileSystem::Search(const Char* S return SearchRes; } +LinuxFileSystem::SearchFilesResult LinuxFileSystem::SearchRecursive(const Char* Dir, const Char* SearchPattern) +{ +#if PLATFORM_LINUX || PLATFORM_APPLE + return Diligent::SearchRecursive(Dir, SearchPattern); +#else + UNSUPPORTED("Not implemented"); + return {}; +#endif +} + // popen/pclose are not thread-safe static std::mutex g_popen_mtx{}; diff --git a/Platforms/Win32/interface/Win32FileSystem.hpp b/Platforms/Win32/interface/Win32FileSystem.hpp index 7b519201d..4b9553ba1 100644 --- a/Platforms/Win32/interface/Win32FileSystem.hpp +++ b/Platforms/Win32/interface/Win32FileSystem.hpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 Diligent Graphics LLC + * Copyright 2019-2023 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,7 +56,8 @@ struct WindowsFileSystem : public BasicFileSystem static void DeleteDirectory(const Char* strPath); static bool IsDirectory(const Char* strPath); - static std::vector> Search(const Char* SearchPattern); + static SearchFilesResult Search(const Char* SearchPattern); + static SearchFilesResult SearchRecursive(const Char* Dir, const Char* SearchPattern); static std::string FileDialog(const FileDialogAttribs& DialogAttribs); static std::string OpenFolderDialog(const char* Title); diff --git a/Platforms/Win32/src/Win32FileSystem.cpp b/Platforms/Win32/src/Win32FileSystem.cpp index 50ff9f975..8d8093cf4 100644 --- a/Platforms/Win32/src/Win32FileSystem.cpp +++ b/Platforms/Win32/src/Win32FileSystem.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 Diligent Graphics LLC + * Copyright 2019-2023 Diligent Graphics LLC * Copyright 2015-2019 Egor Yusov * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,6 +28,7 @@ #include "Win32FileSystem.hpp" #include "Errors.hpp" #include "../../Common/interface/StringTools.hpp" +#include "../../Basic/include/SearchRecursive.inl" // We can't use namespace Diligent before #including because Diligent::INTERFACE_ID will conflict with windows InterfaceID //using namespace Diligent; @@ -420,21 +421,9 @@ void WindowsFileSystem::SetWorkingDirectory(const Char* strWorkingDir) WindowsPathHelper::SetWorkingDirectory(strWorkingDir); } -struct WndFindFileData : public FindFileData +WindowsFileSystem::SearchFilesResult WindowsFileSystem::Search(const Char* SearchPattern) { - virtual const Char* Name() const override { return ffd.cFileName; } - - virtual bool IsDirectory() const override { return (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; } - - WIN32_FIND_DATAA ffd; - - WndFindFileData(const WIN32_FIND_DATAA& _ffd) : - ffd{_ffd} {} -}; - -std::vector> WindowsFileSystem::Search(const Char* SearchPattern) -{ - std::vector> SearchRes; + SearchFilesResult SearchRes; WIN32_FIND_DATAA ffd; // Find the first file in the directory. @@ -452,7 +441,7 @@ std::vector> WindowsFileSystem::Search(const Char* if (IsDot(ffd.cFileName) || IsDblDot(ffd.cFileName)) continue; - SearchRes.emplace_back(std::make_unique(ffd)); + SearchRes.emplace_back(FindFileData{ffd.cFileName, (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0}); } while (FindNextFileA(hFind, &ffd) != 0); auto dwError = GetLastError(); @@ -466,6 +455,11 @@ std::vector> WindowsFileSystem::Search(const Char* return SearchRes; } +WindowsFileSystem::SearchFilesResult WindowsFileSystem::SearchRecursive(const Char* Dir, const Char* SearchPattern) +{ + return Diligent::SearchRecursive(Dir, SearchPattern); +} + static DWORD FileDialogFlagsToOFNFlags(FILE_DIALOG_FLAGS FileDialogFlags) { DWORD OFNFlags = 0; diff --git a/Tests/DiligentCoreTest/src/Platforms/FileSystemTest.cpp b/Tests/DiligentCoreTest/src/Platforms/FileSystemTest.cpp index 244d93365..5f6b38343 100644 --- a/Tests/DiligentCoreTest/src/Platforms/FileSystemTest.cpp +++ b/Tests/DiligentCoreTest/src/Platforms/FileSystemTest.cpp @@ -493,6 +493,19 @@ TEST(Platforms_FileSystem, Directories) EXPECT_TRUE(FileSystem::PathExists(TmpDirPath.c_str())); } +static void CreateTestFile(const std::string& Dir, const std::string& FileName) +{ + const auto& Path = Dir + FileSystem::SlashSymbol + FileName; + { + FileWrapper File{Path.c_str(), EFileAccessMode::Overwrite}; + ASSERT_TRUE(File); + + std::vector Data(512); + EXPECT_TRUE(File->Write(Data.data(), Data.size() * sizeof(Data[0]))); + } + EXPECT_TRUE(FileSystem::FileExists(Path.c_str())); +} + TEST(Platforms_FileSystem, Search) { TempDirectory TmpDir; @@ -501,19 +514,6 @@ TEST(Platforms_FileSystem, Search) EXPECT_TRUE(FileSystem::IsDirectory(TmpDirPath.c_str())); std::unordered_set FileNames = {"File1.ext", "File2.ext", "File3.ext"}; - auto CreateTestFile = [](const std::string& Dir, const std::string& FileName) // - { - const auto& Path = Dir + FileSystem::SlashSymbol + FileName; - { - FileWrapper File{Path.c_str(), EFileAccessMode::Overwrite}; - ASSERT_TRUE(File); - - std::vector Data(512); - EXPECT_TRUE(File->Write(Data.data(), Data.size() * sizeof(Data[0]))); - } - EXPECT_TRUE(FileSystem::FileExists(Path.c_str())); - }; - for (auto& Name : FileNames) { CreateTestFile(TmpDirPath, Name); @@ -534,12 +534,12 @@ TEST(Platforms_FileSystem, Search) EXPECT_EQ(SearchRes.size(), FileNames.size() + DirNames.size()); for (const auto& Res : SearchRes) { - if (FileNames.find(Res->Name()) != FileNames.end()) - EXPECT_FALSE(Res->IsDirectory()); - else if (DirNames.find(Res->Name()) != DirNames.end()) - EXPECT_TRUE(Res->IsDirectory()); + if (FileNames.find(Res.Name) != FileNames.end()) + EXPECT_FALSE(Res.IsDirectory); + else if (DirNames.find(Res.Name) != DirNames.end()) + EXPECT_TRUE(Res.IsDirectory); else - GTEST_FAIL() << Res->Name(); + GTEST_FAIL() << Res.Name; } FileSystem::ClearDirectory(TmpDirPath.c_str(), false); @@ -548,7 +548,7 @@ TEST(Platforms_FileSystem, Search) EXPECT_EQ(SearchRes.size(), DirNames.size()); for (const auto& Res : SearchRes) { - EXPECT_TRUE(DirNames.find(Res->Name()) != DirNames.end()); + EXPECT_TRUE(DirNames.find(Res.Name) != DirNames.end()); } FileSystem::ClearDirectory(TmpDirPath.c_str(), true); @@ -556,6 +556,55 @@ TEST(Platforms_FileSystem, Search) EXPECT_TRUE(SearchRes.empty()); } +TEST(Platforms_FileSystem, SearchRecursive) +{ + TempDirectory TmpDir; + ASSERT_TRUE(FileSystem::PathExists(TmpDir.Get().c_str())); + auto BaseDir = TmpDir.Get() + FileSystem::SlashSymbol + "SearchRecursive"; + if (FileSystem::PathExists(BaseDir.c_str())) + FileSystem::DeleteDirectory(BaseDir.c_str()); + EXPECT_FALSE(FileSystem::PathExists(BaseDir.c_str())); + std::string SubDir1 = "Subdir1"; + std::string SubDir2 = "Subdir2"; + std::string SubDir3 = SubDir1 + FileSystem::SlashSymbol + "Subdir3"; + BaseDir += FileSystem::SlashSymbol; + EXPECT_TRUE(FileSystem::CreateDirectory((BaseDir + SubDir1).c_str())); + EXPECT_TRUE(FileSystem::CreateDirectory((BaseDir + SubDir2).c_str())); + EXPECT_TRUE(FileSystem::CreateDirectory((BaseDir + SubDir3).c_str())); + std::unordered_set TxtFiles = { + SubDir1 + FileSystem::SlashSymbol + "File1.txt", + SubDir2 + FileSystem::SlashSymbol + "File2.txt", + SubDir3 + FileSystem::SlashSymbol + "File3.txt", + }; + std::unordered_set DatFiles = { + SubDir1 + FileSystem::SlashSymbol + "File1.dat", + SubDir2 + FileSystem::SlashSymbol + "File2.dat", + SubDir3 + FileSystem::SlashSymbol + "File3.dat", + }; + for (const auto& File : TxtFiles) + CreateTestFile(BaseDir, File); + for (const auto& File : DatFiles) + CreateTestFile(BaseDir, File); + { + auto TxtSearchRes = FileSystem::SearchRecursive(BaseDir.c_str(), "*.txt"); + EXPECT_EQ(TxtSearchRes.size(), TxtFiles.size()); + for (auto& Res : TxtSearchRes) + { + EXPECT_TRUE(TxtFiles.find(Res.Name) != TxtFiles.end()); + EXPECT_FALSE(Res.IsDirectory); + } + } + { + auto DatSearchRes = FileSystem::SearchRecursive(BaseDir.c_str(), "*.dat"); + EXPECT_EQ(DatSearchRes.size(), TxtFiles.size()); + for (auto& Res : DatSearchRes) + { + EXPECT_TRUE(DatFiles.find(Res.Name) != DatFiles.end()); + EXPECT_FALSE(Res.IsDirectory); + } + } +} + void TestGetLocalAppDataDirectory(const char* AppName) { const auto AppDataDir = FileSystem::GetLocalAppDataDirectory(AppName);