From 5717941c0cb1ad38bc7cc4575489dfecf8bc9178 Mon Sep 17 00:00:00 2001 From: Tuupertunut Date: Mon, 23 Mar 2026 23:11:05 +0200 Subject: [PATCH 1/4] Fix hidden folder checking on Unix-based OS Fixes a bug where all paths that had a hidden folder as a parent were ignored, even if the whole software was inside a hidden folder. On Linux the default config is found inside `$HOME/.config` so the whole config folder was ignored, causing a wide range of issues. The fix is to only ignore hidden folders that are children of the path being searched. --- .../playshared/Runtime/FileScanner/FileScanner.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/UltraStar Play/Packages/playshared/Runtime/FileScanner/FileScanner.cs b/UltraStar Play/Packages/playshared/Runtime/FileScanner/FileScanner.cs index 2b6eafa915..f1ff775c76 100644 --- a/UltraStar Play/Packages/playshared/Runtime/FileScanner/FileScanner.cs +++ b/UltraStar Play/Packages/playshared/Runtime/FileScanner/FileScanner.cs @@ -52,7 +52,7 @@ public static List GetFiles(string folder, FileScannerConfig config) List filteredResult = unfilteredResult // Ignore hidden files and folders .Where(filePath => (!config.ExcludeHiddenFiles || !IsHiddenFile(filePath)) - && (!config.ExcludeHiddenFolders || !IsInsideHiddenFolder(filePath))) + && (!config.ExcludeHiddenFolders || !IsInsideHiddenFolder(filePath, folder))) .ToList(); return filteredResult; @@ -64,10 +64,11 @@ private static bool IsHiddenFile(string filePath) return Path.GetFileName(filePath).StartsWith("."); } - private static bool IsInsideHiddenFolder(string filePath) + private static bool IsInsideHiddenFolder(string filePath, string rootPath) { // On Unix based operating systems, files and folders that start with a dot are hidden. - string folderPath = Path.GetDirectoryName(filePath); - return folderPath.Contains("\\.") || folderPath.Contains("/."); + // Only check for hidden folders that are children of the root path, not the root path itself or its parents. + string folderPath = Path.GetDirectoryName(filePath).Substring(rootPath.Length); + return folderPath.StartsWith(".") || folderPath.Contains("\\.") || folderPath.Contains("/."); } } From 879a550cb2607264629651d8acf892c27668dd82 Mon Sep 17 00:00:00 2001 From: Tuupertunut Date: Mon, 20 Apr 2026 22:53:26 +0300 Subject: [PATCH 2/4] Fix error in FileScanner when path had trailing or duplicate slashes Also a test was added for this scenario. FileScanner tests all pass. --- .../Editor/Tests/FileScannerTest/FileScannerTest.cs | 8 ++++++++ .../playshared/Runtime/FileScanner/FileScanner.cs | 8 ++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/UltraStar Play/Assets/Editor/Tests/FileScannerTest/FileScannerTest.cs b/UltraStar Play/Assets/Editor/Tests/FileScannerTest/FileScannerTest.cs index 657f23cd42..7391b6682f 100644 --- a/UltraStar Play/Assets/Editor/Tests/FileScannerTest/FileScannerTest.cs +++ b/UltraStar Play/Assets/Editor/Tests/FileScannerTest/FileScannerTest.cs @@ -54,6 +54,14 @@ public void RecursiveExcludeHiddenFolders() Assert.AreEqual(4, files.Count); } + [Test] + public void RecursiveExcludeHiddenFoldersExtraSlashes() + { + List files = FileScanner.GetFiles($"{Application.dataPath}/Editor/Tests//FileScannerTest//", + new FileScannerConfig(txtFilePattern) { ExcludeHiddenFiles = false, ExcludeHiddenFolders = true, Recursive = true }); + Assert.AreEqual(4, files.Count); + } + [Test] public void RecursiveExcludeHiddenFiles() { diff --git a/UltraStar Play/Packages/playshared/Runtime/FileScanner/FileScanner.cs b/UltraStar Play/Packages/playshared/Runtime/FileScanner/FileScanner.cs index f1ff775c76..1d90109fa7 100644 --- a/UltraStar Play/Packages/playshared/Runtime/FileScanner/FileScanner.cs +++ b/UltraStar Play/Packages/playshared/Runtime/FileScanner/FileScanner.cs @@ -67,8 +67,12 @@ private static bool IsHiddenFile(string filePath) private static bool IsInsideHiddenFolder(string filePath, string rootPath) { // On Unix based operating systems, files and folders that start with a dot are hidden. - // Only check for hidden folders that are children of the root path, not the root path itself or its parents. - string folderPath = Path.GetDirectoryName(filePath).Substring(rootPath.Length); + // Only check for hidden folders that are children of the root path, not the root path + // itself or its parents. + // We know rootPath is always a prefix of filePath, so it can be cut out with Substring(). + // We also need to cut out the filename. Path.GetDirectoryName() can not be used here + // because it removes duplicate and trailing slashes, changing the string length. + string folderPath = filePath.Substring(rootPath.Length, filePath.LastIndexOfAny(new[] { '/', '\\' }) + 1 - rootPath.Length); return folderPath.StartsWith(".") || folderPath.Contains("\\.") || folderPath.Contains("/."); } } From db0b71c447eea6e5cb9ab2d98e9c350f50571f9c Mon Sep 17 00:00:00 2001 From: Tuupertunut Date: Thu, 7 May 2026 03:12:02 +0300 Subject: [PATCH 3/4] Add tests for hidden folders as parents --- .../FileFInSubInHidden.txt | 0 .../FileFInSubInHidden.txt.meta | 0 .../Tests/FileScannerTest/FileScannerTest.cs | 22 ++++++++++++++++--- 3 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 UltraStar Play/Assets/Editor/Tests/FileScannerTest/.HiddenFolder/SubfolderInHiddenFolder/FileFInSubInHidden.txt create mode 100644 UltraStar Play/Assets/Editor/Tests/FileScannerTest/.HiddenFolder/SubfolderInHiddenFolder/FileFInSubInHidden.txt.meta diff --git a/UltraStar Play/Assets/Editor/Tests/FileScannerTest/.HiddenFolder/SubfolderInHiddenFolder/FileFInSubInHidden.txt b/UltraStar Play/Assets/Editor/Tests/FileScannerTest/.HiddenFolder/SubfolderInHiddenFolder/FileFInSubInHidden.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UltraStar Play/Assets/Editor/Tests/FileScannerTest/.HiddenFolder/SubfolderInHiddenFolder/FileFInSubInHidden.txt.meta b/UltraStar Play/Assets/Editor/Tests/FileScannerTest/.HiddenFolder/SubfolderInHiddenFolder/FileFInSubInHidden.txt.meta new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UltraStar Play/Assets/Editor/Tests/FileScannerTest/FileScannerTest.cs b/UltraStar Play/Assets/Editor/Tests/FileScannerTest/FileScannerTest.cs index 7391b6682f..07312a3b98 100644 --- a/UltraStar Play/Assets/Editor/Tests/FileScannerTest/FileScannerTest.cs +++ b/UltraStar Play/Assets/Editor/Tests/FileScannerTest/FileScannerTest.cs @@ -35,7 +35,7 @@ public void Recursive() { List files = FileScanner.GetFiles(folderPath, new FileScannerConfig(txtFilePattern) { ExcludeHiddenFiles = false, ExcludeHiddenFolders = false, Recursive = true }); - Assert.AreEqual(6, files.Count); + Assert.AreEqual(7, files.Count); } [Test] @@ -43,7 +43,7 @@ public void RecursiveMultiplePatterns() { List files = FileScanner.GetFiles(folderPath, new FileScannerConfig(patterns) { ExcludeHiddenFiles = false, ExcludeHiddenFolders = false, Recursive = true }); - Assert.AreEqual(12, files.Count); + Assert.AreEqual(14, files.Count); } [Test] @@ -62,11 +62,27 @@ public void RecursiveExcludeHiddenFoldersExtraSlashes() Assert.AreEqual(4, files.Count); } + [Test] + public void RecursiveDoNotExcludeHiddenFolderBeingScanned() + { + List files = FileScanner.GetFiles($"{folderPath}.HiddenFolder", + new FileScannerConfig(txtFilePattern) { ExcludeHiddenFiles = false, ExcludeHiddenFolders = true, Recursive = true }); + Assert.AreEqual(3, files.Count); + } + + [Test] + public void RecursiveDoNotExcludeHiddenFoldersInParents() + { + List files = FileScanner.GetFiles($"{folderPath}.HiddenFolder/SubfolderInHiddenFolder", + new FileScannerConfig(txtFilePattern) { ExcludeHiddenFiles = false, ExcludeHiddenFolders = true, Recursive = true }); + Assert.AreEqual(1, files.Count); + } + [Test] public void RecursiveExcludeHiddenFiles() { List files = FileScanner.GetFiles(folderPath, new FileScannerConfig(txtFilePattern) { ExcludeHiddenFiles = true, ExcludeHiddenFolders = false, Recursive = true }); - Assert.AreEqual(5, files.Count); + Assert.AreEqual(6, files.Count); } } From bfd4f0e9e1beb0e62b8652434f5f289177674986 Mon Sep 17 00:00:00 2001 From: Tuupertunut Date: Thu, 7 May 2026 03:15:44 +0300 Subject: [PATCH 4/4] Fix hidden folder checking also for DirectoryScanner The same bug with hidden folders as parents that was in FileScanner was also in DirectoryScanner. It is now fixed and tests added to prevent it from happening again. --- .../FolderC_A/FolderC_A_A/empty.txt | 0 .../DirectoryScannerTest.cs | 30 +++++++++++++++++-- .../DirectoryScanner/DirectoryScanner.cs | 10 +++++-- 3 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 UltraStar Play/Assets/Editor/Tests/DirectoryScannerTest/.HiddenFolderC/FolderC_A/FolderC_A_A/empty.txt diff --git a/UltraStar Play/Assets/Editor/Tests/DirectoryScannerTest/.HiddenFolderC/FolderC_A/FolderC_A_A/empty.txt b/UltraStar Play/Assets/Editor/Tests/DirectoryScannerTest/.HiddenFolderC/FolderC_A/FolderC_A_A/empty.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UltraStar Play/Assets/Editor/Tests/DirectoryScannerTest/DirectoryScannerTest.cs b/UltraStar Play/Assets/Editor/Tests/DirectoryScannerTest/DirectoryScannerTest.cs index 7c7143048a..df63a771ba 100644 --- a/UltraStar Play/Assets/Editor/Tests/DirectoryScannerTest/DirectoryScannerTest.cs +++ b/UltraStar Play/Assets/Editor/Tests/DirectoryScannerTest/DirectoryScannerTest.cs @@ -13,7 +13,7 @@ public void NonRecursive() { List folders = DirectoryScanner.GetFolders(folderPath, new DirectoryScannerConfig() { ExcludeHiddenFolders = false, Recursive = false }); - Assert.AreEqual(2, folders.Count); + Assert.AreEqual(3, folders.Count); } [Test] @@ -29,7 +29,7 @@ public void Recursive() { List folders = DirectoryScanner.GetFolders(folderPath, new DirectoryScannerConfig() { ExcludeHiddenFolders = false, Recursive = true }); - Assert.AreEqual(5, folders.Count); + Assert.AreEqual(8, folders.Count); } [Test] @@ -37,7 +37,7 @@ public void RecursiveSearchPattern() { List folders = DirectoryScanner.GetFolders(folderPath, new DirectoryScannerConfig(searchPattern) { ExcludeHiddenFolders = false, Recursive = true }); - Assert.AreEqual(2, folders.Count); + Assert.AreEqual(4, folders.Count); } [Test] @@ -47,4 +47,28 @@ public void RecursiveExcludeHiddenFolders() new DirectoryScannerConfig() { ExcludeHiddenFolders = true, Recursive = true }); Assert.AreEqual(4, folders.Count); } + + [Test] + public void RecursiveExcludeHiddenFoldersExtraSlashes() + { + List folders = DirectoryScanner.GetFolders($"{Application.dataPath}/Editor/Tests//DirectoryScannerTest//", + new DirectoryScannerConfig() { ExcludeHiddenFolders = true, Recursive = true }); + Assert.AreEqual(4, folders.Count); + } + + [Test] + public void RecursiveDoNotExcludeHiddenFolderBeingScanned() + { + List folders = DirectoryScanner.GetFolders($"{folderPath}.HiddenFolderC", + new DirectoryScannerConfig() { ExcludeHiddenFolders = true, Recursive = true }); + Assert.AreEqual(2, folders.Count); + } + + [Test] + public void RecursiveDoNotExcludeHiddenFoldersInParents() + { + List folders = DirectoryScanner.GetFolders($"{folderPath}.HiddenFolderC/FolderC_A", + new DirectoryScannerConfig() { ExcludeHiddenFolders = true, Recursive = true }); + Assert.AreEqual(1, folders.Count); + } } diff --git a/UltraStar Play/Packages/playshared/Runtime/DirectoryScanner/DirectoryScanner.cs b/UltraStar Play/Packages/playshared/Runtime/DirectoryScanner/DirectoryScanner.cs index 2de9acb7b2..739a3341ab 100644 --- a/UltraStar Play/Packages/playshared/Runtime/DirectoryScanner/DirectoryScanner.cs +++ b/UltraStar Play/Packages/playshared/Runtime/DirectoryScanner/DirectoryScanner.cs @@ -37,15 +37,19 @@ public static List GetFolders(string folder, DirectoryScannerConfig conf List filteredResult = unfilteredResult // Ignore hidden folders - .Where(folderPath => (!config.ExcludeHiddenFolders || !IsHiddenFolder(folderPath))) + .Where(folderPath => (!config.ExcludeHiddenFolders || !IsHiddenFolder(folderPath, folder))) .ToList(); return filteredResult; } - private static bool IsHiddenFolder(string folderPath) + private static bool IsHiddenFolder(string folderPath, string rootPath) { // On Unix based operating systems, files and folders that start with a dot are hidden. - return folderPath.Contains("\\.") || folderPath.Contains("/."); + // Only check for hidden folders that are children of the root path, not the root path + // itself or its parents. + // We know rootPath is always a prefix of filePath, so it can be cut out with Substring(). + folderPath = folderPath.Substring(rootPath.Length); + return folderPath.StartsWith(".") || folderPath.Contains("\\.") || folderPath.Contains("/."); } }